Подтвердить что ты не робот

Что я могу сделать, чтобы разрешить исключение "Row not found or changed" в 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

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

Что странно, что у меня очень похожий оператор обновления в другом разделе кода, и он генерирует следующий 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.

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

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

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

4b9b3361

Ответ 1

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

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

Например, столбец NVARCHAR "title" помечен как NULLable в вашей базе данных и содержит значение NULL. Несмотря на то, что столбец помечен как NOT NULLable в вашем O/R-Mapping, LINQ будет загружать его успешно и установить для столбца-String значение null.

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

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

Итак, например:

protected void loginUser(string username)
{
    var db = new AppDbContext();
    var user = db.Users.Single(u => u.Username == username);
    user.LastLogin = DateTime.UtcNow;
    db.SubmitChanges();
}

protected void doSomething(object obj)
{
    string username = "joe";
    var db = new AppDbContext();
    var user = db.Users.Single(u => u.Username == username);

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

    user.Something = obj;
    db.SubmitChanges();
}

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

Было бы неплохо, если бы был инструмент, который мог бы отслеживать их. Очевидно, это было бы ужасно для производительности в Production, но это могут быть очень коварные ошибки, когда у вас есть несколько уровней логики, взаимодействующих с одной и той же базой данных. Я предполагаю, что это будет обертка, которая поддерживает поточно-безопасную хэш-таблицу отслеживаемых объектов и не работает раньше, как только увидит 2 контекста, отслеживающих один и тот же объект одновременно (и выдаст явное исключение из-за того, почему и какой объект).

Ответ 5

Во-первых, полезно знать, что вызывает проблему. Решение 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);
        }           
    }
}

И затем вызовите команду "Отправить изменения":

Datamodel.SubmitChangesWithDetailException();

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

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

Ответ 6

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

Ответ 7

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

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

Это на Windows Phone 7.5.

Ответ 8

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

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

В элементе select() моего объекта данных используется кеширование. Когда дело дошло до обновления объекта, возникла ошибка Row Not Found or Changed.

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

Ответ 9

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

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

Ответ 10

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

Ответ 11

В моем случае проблема была в настройках сервера. После:

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

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

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

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

Сброс параметров для исключения 512-битного исправления проблемы.

Ответ 12

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

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

Ответ 13

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

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