Что можно сделать, чтобы устранить исключение «Строка не найдена или изменена» в LINQ to SQL в базе данных SQL Server Compact Edition? - PullRequest
89 голосов
/ 05 сентября 2008

При выполнении 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.

Полагаю, это почти вопрос из двух частей:

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

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

Ответы [ 13 ]

179 голосов
/ 17 сентября 2008

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

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

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

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

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

16 голосов
/ 29 августа 2015

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

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

Создание помощника для переноса сумм. Изменения:

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
}
15 голосов
/ 03 сентября 2010

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

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

10 голосов
/ 23 мая 2011

Это также может быть вызвано использованием более одного 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();
    }
}

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

Один из способов предотвратить это - написать любой код, который может быть вызван как библиотечный метод, таким образом, чтобы он использовал необязательный 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);
        }
    }
}

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

10 голосов
/ 04 февраля 2009

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

3 голосов
/ 03 июля 2012

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

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

Это на Windows Phone 7.5.

3 голосов
/ 19 марта 2009

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

2 голосов
/ 18 августа 2016

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

            try
            {
                _db.SubmitChanges(ConflictMode.ContinueOnConflict);
            }
            catch (ChangeConflictException e)
            {
                foreach (ObjectChangeConflict occ in _db.ChangeConflicts)
                {
                    occ.Resolve(RefreshMode.KeepChanges);
                }
            }
0 голосов
/ 03 октября 2016

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

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

0 голосов
/ 26 октября 2015

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

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

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

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...