tags:

views:

1495

answers:

6

Hi, sorry if this is a really dumb question but I've not had a chance to really look deeply into it yet and I wondered whether somebody had a solution to the problem.

When making changes using SubmitChanges() LINQ sometimes dies with a ChangeConflictException exception with the error message 'Row not found or changed', without any indication of either the row that has the conflict or the fields with changes that are in conflict, when another user has changed some data in that row.

Is there any way to determine which row has a conflict and which fields they occur in, and also is there a way of getting LINQ to ignore the issue and simply commit the data regardless?

Additionally, does anybody know whether this exception occurs when any data in the row has changed, or only when data has been changed in a field that LINQ is attempting to alter?

Thanks in advance :-)

+3  A: 

I've gotten this error in a circumstance completely unrelated to what the error message describes.

What I did was load a LINQ object via one DataContext, and then tried to SubmitChanges() for the object via a different DataContext - gave this exact same error.

What I had to do was call DataContext.Table.Attach(myOldObject), and then call SubmitChanges(), worked like a charm.

Worth a look, especially if you're of the opinion that there really shouldn't be any conflicts at all.

Greg Hurlman
+8  A: 

Here's a way to see where the conflicts are (this is an MSDN example, so you'll need to heavily customize):

try
{
    db.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException e)
{
    Console.WriteLine("Optimistic concurrency error.");
    Console.WriteLine(e.Message);
    Console.ReadLine();
    foreach (ObjectChangeConflict occ in db.ChangeConflicts)
    {
        MetaTable metatable = db.Mapping.GetTable(occ.Object.GetType());
        Customer entityInConflict = (Customer)occ.Object;
        Console.WriteLine("Table name: {0}", metatable.TableName);
        Console.Write("Customer ID: ");
        Console.WriteLine(entityInConflict.CustomerID);
        foreach (MemberChangeConflict mcc in occ.MemberConflicts)
        {
            object currVal = mcc.CurrentValue;
            object origVal = mcc.OriginalValue;
            object databaseVal = mcc.DatabaseValue;
            MemberInfo mi = mcc.Member;
            Console.WriteLine("Member: {0}", mi.Name);
            Console.WriteLine("current value: {0}", currVal);
            Console.WriteLine("original value: {0}", origVal);
            Console.WriteLine("database value: {0}", databaseVal);
        }
    }
}

To make it ignore the problem and commit anyway:

db.SubmitChanges(ConflictMode.ContinueOnConflict);
DannySmurf
I'm going to have to add this to my code. I just pushed out an application that ran into a "Row not found" exception.
toast
Careful there, ConflictMode.ContinueOnConflict does not make it commit anyway, what it does is let it try to do ALL the updates, so you get ALL the conflicts at once, rather than stop on the first one, but it does rollback anyway.
Daniel Magliola
To make it ignore the problem, you need to call ResolveAll(KeepChanges) on your catch{}
Daniel Magliola
+8  A: 

These (which you could add in a partial class to your datacontext might help you understand how this works:

public void SubmitKeepChanges()
{
    try
    {
        this.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
        foreach (ObjectChangeConflict occ in this.ChangeConflicts)
        {
            //Keep current values that have changed, 
//updates other values with database values

            occ.Resolve(RefreshMode.KeepChanges);
        }
    }
}

public void SubmitOverwrite()
{
    try
    {
        this.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
        foreach (ObjectChangeConflict occ in this.ChangeConflicts)
        {
            // All database values overwrite current values with 
//values from database

            occ.Resolve(RefreshMode.OverwriteCurrentValues);
        }
    }
}

public void SubmitKeepCurrent()
{
    try
    {
        this.SubmitChanges(ConflictMode.ContinueOnConflict);
    }
    catch (ChangeConflictException e)
    {
        foreach (ObjectChangeConflict occ in this.ChangeConflicts)
        {
            //Swap the original values with the values retrieved from the database. No current value is modified
            occ.Resolve(RefreshMode.KeepCurrentValues);
        }
    }
}
vzczc
You'll need to use System.Data.Linq and System.Data.Linq.Mapping.
mattruma
A: 

"and also is there a way of getting LINQ to ignore the issue and simply commit the data regardless?"

You can set the 'Update Check' property on your entity to 'Never' to stop that field being used for optimistic concurrency checking.

You can also use:

db.SubmitChanges(ConflictMode.ContinueOnConflict)
liammclennan
ContinueOnConflict won't make LINQ ignore it. It'll let it continue, throw all the exceptions, and then roll it all back.
Daniel Magliola
+1  A: 

The error "Row not found or changed" also will appear sometimes when the columns or types in the O/R-Designer do not match the columns in the SQL database, especially if one column is NULLable in SQL but not nullable in the O/R-Designer.

So check if your table mapping in the O/R-Designer matches your SQL database!

Sam
A: 

Those methods are not complete? Would you not want to retry the submit or wrap it in some kind of loop otherwise there remains no guarantee that the changes are comitted upon conflict?

nick