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