Question

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?

Was it helpful?

Solution

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);

OTHER TIPS

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);
        }
    }
}

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.

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!

Thanks to @vzczc. I found the example you gave very helpful but that I needed to call SubmitChanges again after resolving. Here are my modified methods - hope it helps someone.

    /// <summary>
    /// Submits changes and, if there are any conflicts, the database changes are auto-merged for 
    /// members that client has not modified (client wins, but database changes are preserved if possible)
    /// </summary>
    public void SubmitKeepChanges()
    {
        this.Submit(RefreshMode.KeepChanges);
    }

    /// <summary>
    /// Submits changes and, if there are any conflicts, simply overwrites what is in the database (client wins).
    /// </summary>
    public void SubmitOverwriteDatabase()
    {
        this.Submit(RefreshMode.KeepCurrentValues);
    }

    /// <summary>
    /// Submits changes and, if there are any conflicts, all database values overwrite
    /// current values (client loses).
    /// </summary>
    public void SubmitUseDatabase()
    {
        this.Submit(RefreshMode.OverwriteCurrentValues);
    }

    /// <summary>
    /// Submits the changes using the specified refresh mode.
    /// </summary>
    /// <param name="refreshMode">The refresh mode.</param>
    private void Submit(RefreshMode refreshMode)
    {
        bool moreToSubmit = true;
        do
        {
            try
            {
                this.SubmitChanges(ConflictMode.ContinueOnConflict);
                moreToSubmit = false;
            }
            catch (ChangeConflictException)
            {
                foreach (ObjectChangeConflict occ in this.ChangeConflicts)
                {
                    occ.Resolve(refreshMode);
                }
            }
        }
        while (moreToSubmit);

    }

row not found or changed is most of times a concurrency problem

If a different user is changing the same record those errors popup because the record is already changed by that other user. So when you want to eliminate those errors you should handle concurrency in your application. If you handle concurrency well you won't get these errors anymore. The above code samples are a way to handle concurrency errors. Whats missing is that in case of a concurrency error you should put a refresh variable in those methods so when refresh is true the data needs to be refreshed on screen after the update so you will also see the update made by the other user.

    /// <remarks>
    ///     linq has optimistic concurrency, so objects can be changed by other users, while
    ///     submitted keep database changes but make sure users changes are also submitted
    ///     and refreshed with the changes already made by other users.
    /// </remarks>
    /// <returns>return if a refresh is needed.</returns>
    public bool SubmitKeepChanges()
    {
        // try to submit changes to the database.
        bool refresh = false;
        try
        {
            base.SubmitChanges(ConflictMode.ContinueOnConflict);
        }

        /* 
         * assume a "row not found or changed" exception, if thats the case:
         * - keep the database changes already made by other users and make sure
         * - this users changes are also written to the database
         */
        catch (ChangeConflictException)
        {
            // show where the conflicts are in debug mode
            ShowConflicts();

            // get database values and combine with user changes 
            base.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);

            // submit those combined changes again to the database.
            base.SubmitChanges();

            // a refresh is needed
            refresh = true;
        }

        // return if a refresh is needed.
        return refresh;
    }

"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)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top