Значения кэширования данных Linq - главная проблема параллелизма?
Вот небольшой эксперимент, который я сделал:
MyClass obj = dataContext.GetTable<MyClass>().Where(x => x.ID = 1).Single();
Console.WriteLine(obj.MyProperty); // output = "initial"
Console.WriteLine("Waiting..."); // put a breakpoint after this line
obj = null;
obj = dataContext.GetTable<MyClass>().Where(x => x.ID = 1).Single(); // same as before, but reloaded
Console.WriteLine(obj.MyProperty); // output still = "initial"
obj.MyOtherProperty = "foo";
dataContext.SubmitChanges(); // throws concurrency exception
Когда я достигаю точки останова после строки 3, я перехожу к окну SQL-запроса и вручную изменяю значение на "обновленный". Затем я продолжаю бежать. Linq не перезагружает мой объект, но повторно использует тот, который был ранее в памяти! Это огромная проблема для параллелизма данных!
Как отключить этот скрытый кеш объектов, который Linq явно хранит в памяти?
РЕДАКТИРОВАТЬ - Поразмыслив, это просто немыслимо, что Microsoft могла бы оставить такой разрыв в рамках Linq. Приведенный выше код представляет собой дурацкую версию того, что я на самом деле делаю, и, возможно, я пропустил некоторые тонкости. Короче говоря, я был бы признателен, если бы вы провели свои собственные эксперименты, чтобы убедиться, что мои выводы выше верны. Альтернативно, должен быть какой-то "секретный переключатель", который делает Linq устойчивым к одновременным обновлениям данных. Но что?
4 ответа
Это не проблема, с которой я сталкивался раньше (так как я не склонен держать DataContexts открытым в течение длительных периодов времени), но похоже, что кто-то другой имеет:
http://www.rocksthoughts.com/blog/archive/2008/01/14/linq-to-sql-caching-gotcha.aspx
LinqToSql имеет широкий спектр инструментов для решения проблем параллелизма.
Первый шаг, однако, должен признать, что есть проблема параллелизма, которая должна быть решена!
Во-первых, предполагаемый жизненный цикл объекта DataContext должен соответствовать UnitOfWork. Если вы держитесь за один в течение продолжительных периодов, вам придется работать намного усерднее, потому что класс не предназначен для такого использования.
Во-вторых, DataContext отслеживает две копии каждого объекта. Один - это исходное состояние, а другой - измененное / изменяемое состояние. Если вы запросите MyClass с Id = 1, он вернет вам тот же экземпляр, который был предоставлен вам в прошлый раз, который является измененной / изменяемой версией... не оригинальной. Это должно быть сделано для предотвращения проблем параллелизма с экземплярами в памяти... LinqToSql не позволяет одному DataContext знать о двух изменяемых версиях MyClass(Id = 1).
В-третьих, DataContext не знает, происходит ли изменение в памяти до или после изменения базы данных, и поэтому не может рассматривать конфликт параллелизма без какого-либо руководства. Все, что он видит, это:
- Я прочитал MyClass (Id = 1) из базы данных.
- Программист изменил MyClass(Id = 1).
- Я отправил MyClass (Id = 1) обратно в базу данных (посмотрите на этот sql, чтобы увидеть оптимистичный параллелизм в предложении where)
- Обновление будет выполнено успешно, если версия базы данных будет соответствовать оригиналу (оптимистичный параллелизм).
- Обновление не будет выполнено с исключением параллелизма, если версия базы данных не совпадает с оригинальной.
Хорошо, теперь, когда проблема установлена, вот несколько способов справиться с ней.
Вы можете выбросить DataContext и начать все сначала. Это немного тяжело для некоторых, но, по крайней мере, это легко реализовать.
Вы можете запросить обновление исходного или измененного / изменяемого экземпляра значением базы данных, вызвав DataContext.Refresh(RefreshMode, target)
( ссылки на документы со многими хорошими ссылками параллелизма в разделе "Замечания"). Это внесет изменения на стороне клиента и позволит вашему коду определить, каким должен быть конечный результат.
Вы можете отключить проверку параллелизма в dbml ( ColumnAttribute.UpdateCheck). Это отключает оптимистичный параллелизм, и ваш код будет вытеснен другими изменениями. Также тяжелые руки, также легко реализовать.
Установите для свойства ObjectTrackingEnabled объекта DataContext значение false.
Когда ObjectTrackingEnabled имеет значение true, DataContext ведет себя как единица работы. Он будет сохранять любой объект загруженным в память, чтобы он мог отслеживать изменения в нем. DataContext должен помнить объект, как вы его первоначально загрузили, чтобы знать, были ли внесены какие-либо изменения.
Если вы работаете в сценарии только для чтения, вам следует отключить отслеживание объектов. Это может быть достойное улучшение производительности.
Если вы не работаете в сценарии только для чтения, тогда я не уверен, почему вы хотите, чтобы он работал таким образом. Если вы внесли изменения, то зачем вам тянуть их в измененном состоянии из базы данных?
LINQ to SQL использует шаблон разработки карты идентификации, что означает, что он всегда будет возвращать один и тот же экземпляр объекта для данного первичного ключа (если вы не отключите отслеживание объекта).
Решение состоит в том, чтобы просто использовать второй контекст данных, если вы не хотите, чтобы он мешал первому экземпляру, или обновить первый, если вы это сделаете.