Что я могу сделать, чтобы устранить исключение «Строка не найдена или изменена» в LINQ to SQL в базе данных SQL Server Compact Edition?
-
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.
Я думаю, это почти вопрос из двух частей:
- Почему выдается исключение?
- После рассмотрения второго набора сгенерированных 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).Я присваивал десятичное значение, но база данных меняла его, удаляя десятичную часть.Это привело к проблеме с изменением строки.
Просто добавьте это, если кто-то еще столкнется с подобной проблемой.