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

StackOverflow https://stackoverflow.com/questions/45045

  •  09-06-2019
  •  | 
  •  

Вопрос

При выполнении подчиненных в DataContext после обновления пары свойств с помощью подключения LINQ с 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» нет дополнительной информации о том, почему это не удалось.Я также подтвердил, что это исключение генерируется ровно с одной записью в базе данных (запись, которую я пытаюсь обновить).

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

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

Это было полезно?

Решение

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

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

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

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

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

Другие советы

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

Создайте помощник для упаковки ваших 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
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top