Что я могу сделать, чтобы разрешить исключение "строка не найдена или изменена" в LINQ to SQL в базе данных SQL Server Compact Edition?


при выполнении SubmitChanges в DataContext после обновления пары свойств с соединением LINQ to SQL (против SQL Server Compact Edition) я получаю "строка не найдена или изменена."ChangeConflictException.

var ctx = new Data.MobileServerDataDataContext(Common.DatabasePath);
var deviceSessionRecord = ctx.Sessions.First(sess => sess.SessionRecId == args.DeviceSessionId);

deviceSessionRecord.IsActive = false;
deviceSessionRecord.Disconnected = DateTime.Now;

ctx.SubmitChanges();

запрос генерирует следующий SQL:

UPDATE [Sessions]
SET [Is_Active] = @p0, [Disconnected] = @p1
WHERE 0 = 1
-- @p0: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p1: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:12:02 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

очевидная проблема-это где 0=1, после того, как запись была загружена, я подтвердил, что все свойства в "deviceSessionRecord" правильно включают первичный ключ. Также при перехвате "ChangeConflictException" нет никакой дополнительной информации о том, почему это не удалось. Я также подтвердил, что это исключение get выбрасывается ровно с одной записью в базе данных (запись, которую я пытаюсь обновить)

странно, что у меня есть очень похожий оператор обновления в другом разделе кода, и он генерирует следующий SQL и действительно обновляет мою базу данных SQL Server Compact Edition.

UPDATE [Sessions]
SET [Is_Active] = @p4, [Disconnected] = @p5
WHERE ([Session_RecId] = @p0) AND ([App_RecId] = @p1) AND ([Is_Active] = 1) AND ([Established] = @p2) AND ([Disconnected] IS NULL) AND ([Member_Id] IS NULL) AND ([Company_Id] IS NULL) AND ([Site] IS NULL) AND (NOT ([Is_Device] = 1)) AND ([Machine_Name] = @p3)
-- @p0: Input Guid (Size = 0; Prec = 0; Scale = 0) [0fbbee53-cf4c-4643-9045-e0a284ad131b]
-- @p1: Input Guid (Size = 0; Prec = 0; Scale = 0) [7a174954-dd18-406e-833d-8da650207d3d]
-- @p2: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:50 PM]
-- @p3: Input String (Size = 0; Prec = 0; Scale = 0) [CWMOBILEDEV]
-- @p4: Input Boolean (Size = 0; Prec = 0; Scale = 0) [False]
-- @p5: Input DateTime (Size = 0; Prec = 0; Scale = 0) [9/4/2008 5:20:52 PM]
-- Context: SqlProvider(SqlCE) Model: AttributedMetaModel Build: 3.5.21022.8

у меня есть подтверждено, что соответствующие значения первичных полей были определены как в схеме базы данных, так и в DBML, который генерирует классы LINQ.

Я думаю, что это почти две части вопроса:

  1. почему выбрасывается исключение?
  2. после просмотра второго набора сгенерированных SQL, кажется, что для обнаружения конфликтов было бы неплохо проверить все поля, но я думаю, что это было бы довольно неэффективно. Это всегда так работает? Есть ли параметр, чтобы просто проверить первичный ключ?

я боролся с этим в течение последних двух часов, так что любая помощь будет оценили.

13 86

13 ответов:

это противно, но просто:

проверьте, соответствуют ли типы данных для всех полей в O/R-Designer типам данных в вашей таблице SQL. двойная проверка на значение null! столбец должен быть либо nullable в обоих O / R-Designer и SQL, либо not nullable в обоих.

например, столбец NVARCHAR "title" помечается как NULLable в вашей базе данных и содержит значение NULL. Даже если столбец помечен как не допускающий значение null в ваш o/r-отображение, LINQ, которая будет загружать он успешно и установил строку столбца в null.

  • теперь вы что-то меняете и звоните SubmitChanges ().
  • LINQ создаст SQL-запрос содержащий "WHERE [title] is NULL", чтобы убедиться, что заголовок не был изменен кем-то другим.
  • LINQ, которая выглядит свойств [название] в отображении.
  • LINQ, которая будет найти [название] не допускает значения null.
  • так как [название] не может быть аннулировано, by логика это никогда не могло быть Нуль!
  • таким образом, оптимизацию запросов с помощью LINQ заменяет его на "где 0 = 1" , SQL эквивалент "никогда".

тот же симптом появится, когда типы данных поля не соответствуют типу данных в SQL, или если поля отсутствуют, так как LINQ не сможет убедиться, что данные SQL не изменились с момента чтения данных.

есть метод на DataContext называется обновить что может помочь здесь. Он позволяет перезагрузить запись базы данных перед отправкой изменений и предлагает различные режимы, чтобы определить, какие значения следует сохранить. "KeepChanges" кажется самым умным для моих целей, он предназначен для слияния моих изменений с любыми неконфликтными изменениями, которые произошли в базе данных в то же время.

Если я правильно понимаю. :)

во-первых, полезно знать, что вызывает проблему. Решение Googling должно помочь, вы можете зарегистрировать детали (таблица, столбец, старое значение, новое значение) о конфликте, чтобы найти лучшее решение для решения конфликта позже:

public class ChangeConflictExceptionWithDetails : ChangeConflictException
{
    public ChangeConflictExceptionWithDetails(ChangeConflictException inner, DataContext context)
        : base(inner.Message + " " + GetChangeConflictExceptionDetailString(context))
    {
    }

    /// <summary>
    /// Code from following link
    /// https://ittecture.wordpress.com/2008/10/17/tip-of-the-day-3/
    /// </summary>
    /// <param name="context"></param>
    /// <returns></returns>
    static string GetChangeConflictExceptionDetailString(DataContext context)
    {
        StringBuilder sb = new StringBuilder();

        foreach (ObjectChangeConflict changeConflict in context.ChangeConflicts)
        {
            System.Data.Linq.Mapping.MetaTable metatable = context.Mapping.GetTable(changeConflict.Object.GetType());

            sb.AppendFormat("Table name: {0}", metatable.TableName);
            sb.AppendLine();

            foreach (MemberChangeConflict col in changeConflict.MemberConflicts)
            {
                sb.AppendFormat("Column name : {0}", col.Member.Name);
                sb.AppendLine();
                sb.AppendFormat("Original value : {0}", col.OriginalValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Current value : {0}", col.CurrentValue.ToString());
                sb.AppendLine();
                sb.AppendFormat("Database value : {0}", col.DatabaseValue.ToString());
                sb.AppendLine();
                sb.AppendLine();
            }
        }

        return sb.ToString();
    }
}

создать помощник для обертывания sumbitChanges:

public static class DataContextExtensions
{
    public static void SubmitChangesWithDetailException(this DataContext dataContext)
    {   
        try
        {         
            dataContext.SubmitChanges();
        }
        catch (ChangeConflictException ex)
        {
            throw new ChangeConflictExceptionWithDetails(ex, dataContext);
        }           
    }
}

а затем вызовите submit changes code:

Datamodel.SubmitChangesWithDetailException();

наконец, зарегистрируйте исключение в своем глобальном обработчике исключений:

protected void Application_Error(object sender, EventArgs e)
{         
    Exception ex = Server.GetLastError();
    //TODO
}

Я решил эту ошибку, перерисовав таблицу из проводника сервера в конструктор и перестроив ее.

Это также может быть вызвано использованием более одного DbContext.

например:

protected async Task loginUser(string username)
{
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);
        user.LastLogin = DateTime.UtcNow;
        await db.SaveChangesAsync();
    }
}

protected async Task doSomething(object obj)
{
    string username = "joe";
    using(var db = new Db())
    {
        var user = await db.Users
            .SingleAsync(u => u.Username == username);

        if (DateTime.UtcNow - user.LastLogin >
            new TimeSpan(0, 30, 0)
        )
            loginUser(username);

        user.Something = obj;
        await db.SaveChangesAsync();
    }
}

этот код будет отказывать время от времени, способами, которые кажутся непредсказуемыми, потому что пользователь используется в обоих контекстах, изменяется и сохраняется в одном, а затем сохраняется в другом. Представление в памяти пользователя, которому принадлежит "что-то", не соответствует тому, что находится в базе данных, и поэтому вы получаете эту скрытую ошибку.

один из способов предотвратить это-написать любой код, который может когда-либо вызываться как библиотечный метод таким образом, что он принимает необязательный DbContext:

protected async Task loginUser(string username, Db _db = null)
{
    await EFHelper.Using(_db, async db =>
    {
        var user = await db.Users...
        ... // Rest of loginUser code goes here
    });
}

public class EFHelper
{
    public static async Task Using<T>(T db, Func<T, Task> action)
        where T : DbContext, new()
    {
        if (db == null)
        {
            using (db = new T())
            {
                await action(db);
            }
        }
        else
        {
            await action(db);
        }
    }
}

Итак, теперь ваш метод берет дополнительную базу данных, и если ее нет, идет и делает ее сам. Если он просто использует то, что передается. Вспомогательный метод позволяет легко использовать этот шаблон в вашем приложении.

Я не знаю, нашли ли вы какие-либо удовлетворительные ответы на ваш вопрос, но я опубликовал аналогичный вопрос и в конечном итоге ответил на него сам. Оказалось, что для базы данных была включена опция подключения по умолчанию NOCOUNT, что вызывало исключение ChangeConflictException для каждого обновления, выполненного с помощью Linq to Sql. Вы можете обратиться к моему сообщению по адресу здесь.

я исправил это путем добавления (UpdateCheck = UpdateCheck.Never) для всех [Column] определений.

не похоже на подходящее решение, хотя. В моем случае это, по-видимому, связано с тем, что эта таблица имеет связь с другой таблицей, из которой удаляется строка.

это на Windows Phone 7.5.

Это то, что вам нужно, чтобы переопределить эту ошибку на C# code:

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }

Я знаю, что на этот вопрос уже давно ответили, но здесь я провел последние несколько часов, стуча головой о стену, и я просто хотел поделиться своим решением, которое оказалось не связанным ни с одним из элементов в этой теме:

кэширование!

часть select () моего объекта данных использовала кэширование. Когда дело дошло до обновления объекта строка не найдена или изменена ошибка появлялась.

некоторые из ответов упоминается, используя различные DataContext и в ретроспективе это, вероятно, то, что происходило, но это не сразу заставило меня думать о кэшировании, поэтому, надеюсь, это поможет кому-то!

недавно я столкнулся с этой ошибкой и обнаружил, что проблема была не с моим контекстом данных, а с оператором обновления, срабатывающим внутри триггера после того, как фиксация вызывалась в контексте. Триггер пытался обновить ненулевое поле с нулевым значением, и это вызывало ошибку контекста с сообщением, упомянутым выше.

Я добавляю этот ответ исключительно для того, чтобы помочь другим справиться с этой ошибкой и не найти решение в ответах выше.

Я также получил эту ошибку из-за использования двух различных контекстах. Я решил эту проблему с помощью одного контекста данных.

в моем случае проблема была с общесерверными пользовательскими опциями. Следующее:

https://msdn.microsoft.com/en-us/library/ms190763.aspx

Я включил опцию NOCOUNT в надежде получить некоторые преимущества производительности:

EXEC sys.sp_configure 'user options', 512;
RECONFIGURE;

и это, оказывается, нарушает проверки Linq для затронутых строк (насколько я могу понять из источников .NET), что приводит к ChangeConflictException

сброс параметров исключите 512 бит Исправлена проблема.

после использования ответа qub1n я обнаружил, что проблема для меня заключалась в том, что я случайно объявил столбец базы данных десятичным(18,0). Я назначал десятичное значение, но база данных изменяла его, снимая десятичную часть. Это привело к изменению строки проблемы.

просто добавьте это, если кто-то еще столкнется с подобной проблемой.