В предыдущей статье я представил несколько проблем, с которыми вы можете столкнуться при использовании LINQ to SQL для операций обновления. На самом деле, это не проблема, с которой я столкнулся один. Когда я искал ответы в Интернете, я обнаружил, что многие люди публиковали подобные статьи на эту тему. Но что меня не устраивает, так это то, что они хотя и подняли проблему, но не провели детальный анализ. Они лишь дали решения (типа добавления столбцов RowVersion, удаления ассоциаций и т. д.), но не объяснили, почему они должны это сделать. . Это также первоначальная цель написания предыдущей статьи. Я надеюсь найти решение проблемы шаг за шагом путем анализа исходного кода LINQ to SQL. В этой статье мы обсудим эти методы один за другим.
Вариант 1: Переназначение В фреймворке с открытым исходным кодом Ezsocio от TerryLee, Anytao, Ding Xue и других в некоторых местах принято переназначение. Внутри метода Update получите сущности в базе данных на основе первичного ключа, а затем присвойте значения их свойствам по одному с сущностями в параметрах.
общественный недействительный UpdateProfile (Профиль p)
{
используя (RepositoryContext db = новый RepositoryContext())
{
var Profile = db.GetTable<Profile>().First<Profile>(u => u.ID == p.ID);
профиль.День Рождения = p.День Рождения;
профиль.Пол = p.Пол;
профиль.Родной город = р.Родной город;
профиль.MSN = p.MSN;
профиль.НикИмя = p.НикИмя;
профиль.PhoneNumber = p.PhoneNumber;
профиль.QQ = p.QQ;
профиль.Состояние = p.State;
профиль.TrueName = p.TrueName;
профиль.StateRefreshTime = p.StateRefreshTime;
профиль.Аватар = p.Аватар;
профиль.Веб-сайт = p.Веб-сайт;
БД.SubmitChanges();
}
}
Брат Ян Го также предоставил для этого решения метод отражения, позволяющий добиться автоматического копирования значений атрибутов.
Но я лично считаю, что эта схема избегает реальности и избегает реальности. Она не использует API, предоставляемый LINQ to SQL, для операций обновления, а применяет обходную стратегию. На самом деле это компромисс. Не потому ли, что метод Attach «непрост в использовании», поэтому мы его не используем? хе-хе.
Вариант 2. Отключить отслеживание объектов. В связи с этим Леа предположил, что правильное обновление может быть достигнуто путем установки свойства ObjectTrackingEnabled DataContext в значение false.
общедоступный продукт GetProduct (int id)
{
NorthwindDataContext db = новый NorthwindDataContext ();
db.ObjectTrackingEnabled = ложь;
return db.Products.SingleOrDefault(p => p.ProductID == id);
}
Никакой другой код не меняется.
Почему он может нормально обновляться после отключения отслеживания объектов? Давайте найдем ответ в исходном коде.
общедоступный bool ObjectTrackingEnabled
{
получать
{
это.CheckDispose();
верните this.objectTrackingEnabled;
}
набор
{
это.CheckDispose();
если (this.Services.HasCachedObjects)
{
throw System.Data.Linq.Error.OptionsCannotBeModifiedAfterQuery();
}
this.objectTrackingEnabled = значение;
если (!this.objectTrackingEnabled)
{
this.deferredLoadingEnabled = ложь;
}
this.services.ResetServices();
}
}
Оказывается, когда для ObjectTrackingEnabled установлено значение false, для DeferredLoadingEnabled одновременно будет установлено значение false. Таким образом, при выполнении запроса для сущности не будут загружены никакие данные, требующие отложенного запроса, поэтому во время Присоединения не будет выдано никаких исключений (см. анализ в предыдущей статье).
В MSDN мы также получаем следующую полезную информацию: Установка свойства ObjectTrackingEnable в значение false может улучшить производительность поиска, поскольку уменьшает количество отслеживаемых элементов. Это очень заманчивая функция.
Однако при отключении отслеживания объектов особое внимание следует обратить на два момента: (1) Его необходимо отключить перед выполнением запроса. (2) После отключения методы Attach и SubmitChanges больше нельзя вызывать. В противном случае будет выброшено исключение.
Вариант 3. Удалите связь. В предыдущей статье был представлен неубедительный метод, который заключается в том, чтобы вручную установить для категории, связанной с продуктом, значение null в методе GetProduct. Мы можем извлечь эту часть кода и поместить ее в метод Detach. Поскольку этот Detach является методом сущности, можно использовать частичные классы:
общедоступный частичный класс Product
{
общественная недействительность Detach()
{
this._Category = default(EntityRef<Category>);
}
}
Категория публичного частичного класса
{
общественная недействительность Detach()
{
foreach (продукт var в this.Products)
{
продукт.Отсоединить();
}
}
}Но этот метод определения Detach для каждого объекта слишком громоздкий. По мере увеличения количества сущностей отношения становятся все более и более сложными, и легко появляются недостающие атрибуты. Чжан И предложил очень элегантный метод абстрагирования этой логики с помощью отражения:
частная пустота Detach (объект TEntity)
{
foreach (FieldInfo fi в сущности.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
{
если (fi.FieldType.ToString().Contains("EntityRef"))
{
значение var = fi.GetValue(entity);
если (значение!= ноль)
{
fi.SetValue(сущность, ноль);
}
}
если (fi.FieldType.ToString().Contains("EntitySet"))
{
значение var = fi.GetValue(entity);
если (значение!= ноль)
{
MethodInfo mi = value.GetType().GetMethod("Очистить");
если (ми != ноль)
{
mi.Invoke (значение, ноль);
}
fi.SetValue(сущность, значение);
}
}
}
}
Некоторые люди также думают, что события PropertyChanging и PropertyChanged должны быть установлены в нулевое значение во время Detach, но общая идея та же.
Вариант 4: использовать делегирование. Это метод, предложенный ZC29 в комментариях к моей последней статье. Лично я считаю, что ему стоит поучиться.
public void UpdateProductWithDelegate (Expression<Func<Product, предикат bool>>, действие Action<Product>)
{
NorthwindDataContext db = новый NorthwindDataContext ();
вар продукт = db.Products.SingleOrDefault(предикат);
действие (продукт);
БД.SubmitChanges();
}
//Код клиента
Репозиторий ProductRepository = новый ProductRepository();
репозиторий.UpdateProductWithDelegate(p => p.ProductID == 1, p =>
{
p.ProductName = "Изменено";
});
Используйте лямбда-выражения для внедрения логики GetProduct в UpdateProduct и используйте делегаты для отсрочки выполнения логики обновления. Это разумно помещает поиск и обновление в DataContext, тем самым минуя Attach. Однако API этого метода слишком сложен и требует слишком высокого уровня клиентских программистов. Более того, логика Get должна быть выполнена снова в Update. Хотя потеря производительности минимальна, у людей всегда возникает ощущение, что она недостаточно СУХАЯ.
Вариант 5. Используйте оператор UPDATE. В исходном коде Ezsocio я нашел метод RepositoryBase.UpdateEntity. Объединение операторов SQL выполняется внутри метода, и обновляются только измененные столбцы. Поскольку ITable здесь больше не используется и требуется полная поддержка фреймворка, никаких дальнейших комментариев делаться не будет. Подробности см. в исходном коде Ezsocio.
Резюме В этой статье перечислено несколько решений, которые я нашел в Интернете за последние дни. У каждого из них есть плюсы и минусы. Какое из них лучше или хуже, зависит от мнения разных людей. В следующей статье я сравню производительность этих методов, чтобы найти оптимальное решение.