• Microsoft .NET
  • LINQ
  • LINQ to SQL - отделение сущностей при помощи Detach

LINQ to SQL - отделение сущностей при помощи Detach

ОГЛАВЛЕНИЕ

Все они являются здравыми вопросами при составлении структуры. По умолчанию, LINQ to SQL не торопится разъединять данные сущности и не видит причины, почему они должны быть отделены от его контекста. PLINQO позволяет с легкостью отделить сущности LINQ to SQL. Далее мы вам продемонстрируем как это можно выполнить при помощи PLINQO , вручную отделяя функциональность используя LINQ to SQL.

Подготовка

Мы будем использовать базу данных Petshop для демонстрации отделения сущностей LINQ to SQL. Колонка RowVersion типа timestamp была добавлена в каждую таблицу в базе данных Petshop. LINQ to SQL использует колонку RowVersion для выполнения оптимистичной проверки совпадений и не позволит вам добавить сущности к DataContext без колонки RowVersion или установления UpdateCheck в Never (по умолчанию - значение равно Always) для каждого свойства сущности в DBML.

Отделение из DataContext

Чтобы обеспечить полное отделение сущности, нам необходимо изучить каждую дочернюю сущность, список дочерних сущностей и каждое свойство, которое загружается с опозданием (delay) или из отложенной списковой структуры (lazy). Мы пройдемся по процессу реализации отделения (detach) по сущности Product. Вот как выглядит сущность Product и все ее зависимости в дизайнере DBML.

 

Сущность Product содержит в себе все различные сценарии, с которыми мы встретимся при отделении сущностей. Category является дочерней сущностью , Item является списком дочерних сущностей и мы настроили Descn на загрузку типа delay или lazy.

Для отделения свойства Product, первым шагом является создание частичного класса Product. Чтобы создать частичный класс для сущности Product щелкните правой кнопкой мыши по сущности Product в дизайнере LINQ to SQL, выберите "View Code" (просмотреть код) и частичный класс будет создан для сущности Product. Мы добавим следующий метод в частичный класс Product:

partial class Product
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;

        PropertyChanging = null;
        PropertyChanged = null;
    }
}

Во-первых совершается проверка на то, что сущность привязана к контексту. Это может показаться неким хакерством, но сущности LINQ to SQL участвуют в оповещениях об изменении DataContext посредством их обработчиков событий PropertyChanged и PropertyChanging. DataContext из LINQ to SQL отслеживает объекты используя интерфейсы INotifyPropertyChanging и INotifyPropertyChanged. Это означает что обработчики PropertyChanged и PropertyChanging управляют привязкой к DataContext. Проверка удостоверится в том, что событие обрабатывается и не оповестит о том, что сущность привязана к datacontext.

if (null == PropertyChanging)     return;

Если сущность не привязана к datacontext, то нам ничего не надо делать. Также данная проверка избавляет нас от возможности наличия циклической ссылки, вызывающей какие-либо проблемы с переполнением стека. Если сущность привязана к datacontext, то тогда обработчики для событий PropertyChanging и PropertyChanged будут удалены.

PropertyChanging = null;
PropertyChanged = null;

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

Для реализации отделения для всех дочерних сущностей, списков и свойств с отложенной загрузкой, нам необходимо создать базовый класс abstract названный LinqEntityBase , который будут наследовать все сущности.

Поскольку мы реализуем некоторые основные методы для получения возможности повторного использования, где понадобится использовать метод Detach() из каждой сущности, необходим метод abstract detach в классе LinqEntityBase. Мы уже видели метод Detach() для сущности Product в Petshop. Каждая из других сущностей в Petshop.dbml должна обладать detach конкретно для данной сущности. Итак, Product теперь наследует LinqEntityBase.

partial class Product : LinqEntityBase

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

protected static System.Data.Linq.EntityRef<TEntity> Detach<TEntity>
    (System.Data.Linq.EntityRef<TEntity> entity)
        where TEntity : LinqEntityBase
{
    if (!entity.HasLoadedOrAssignedValue || entity.Entity == null)
        return new System.Data.Linq.EntityRef<TEntity>();
        entity.Entity.Detach();
    return new System.Data.Linq.EntityRef<TEntity>(entity.Entity);
}

Нам сначала необходимо определить, была ли загружена сущность. Задача заключается в том, что необходимо постараться не задействовать загрузку сущностей. Метод HasLoadedOrAssignedValue говорит нам о том, была ли  загружена сущность, и мы можем избежать любую задержанную загрузку сущностей. Как только мы поймем, что сущность была загружена, сущность будет отделена и возвращена в качестве целевого объекта нового экземпляра EntityRef. Если сущность не была загружена, то свойство устанавливается в новый пустой экземпляр EntityRef.

entity.Entity.Detach();

Данная строка вызывает реализацию Detach , которая является специфической для сущности Category в данном случае. Опять-таки, каждое свойство требует наличия своего собственного метода Detach , специфического для данной сущности . Мы реализовали метод Detach() для каждой сущности аналогично процессу, который мы использовали для сущности Product.

 partial class Category : LinqEntityBase
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;
               
        PropertyChanging = null;
        PropertyChanged = null;
        this._Products = Detach(this._Products, attach_Products, detach_Products);
    }
}

Все дочерние списки для данной сущности должны быть также отделены. Свойство ItemList в сущности Product является EntitySet. Каждый список ItemList в EntitySet должен быть отделен и следующий метод как раз выполняет это.

 protected static System.Data.Linq.EntitySet<TEntity> Detach<TEntity>
    (System.Data.Linq.EntitySet<TEntity> set, Action<TEntity> onAdd,
    Action<TEntity> onRemove)
        where TEntity : LinqEntityBase
{
    if (set == null || !set.HasLoadedOrAssignedValues)
        return new System.Data.Linq.EntitySet<TEntity>(onAdd, onRemove);
            
    // копирование списка и отделение всех сущностей
    var list = set.ToList();
    list.ForEach(t => t.Detach());
        
    var newSet = new System.Data.Linq.EntitySet<TEntity>(onAdd, onRemove);
    newSet.Assign(list);
    return newSet;
}

Как мы уже упоминали ранее, HasLoadedOrAssignedValue используется для определения того, загружен ли список, предостерегает от поздних загрузок списка. Каждый элемент в списке ItemList должен быть отделен и скопирован в новый EntitySet , который не привязан к datacontext.

Наконец, любые свойства, загружаемые с задержкой (delay loaded) также должны быть отделены. Обновив DBML, мы сконфигурировали свойство Descn сущности Product таким образом, чтобы оно загружалось с задержкой (delay loaded).

 

Любые загружаемые с задержкой свойства, которые также связаны с datacontext , которые должны быть отделены и используют третий и последний метод Detach, необходимы нам в базовом классе.

protected static System.Data.Linq.Link<T> Detach<T>(System.Data.Linq.Link<T> value)
{
    if (!value.HasLoadedOrAssignedValue)
        return default(System.Data.Linq.Link<T>);
           
    return new System.Data.Linq.Link<T>(value.Value);
}

Так же, как и раньше, мы проверяем, загружен ли был объект,  и если нет, то мы возвращаем экземпляр по умолчанию. В противном случае мы вернем новый экземпляр Link со значением объекта в качестве целевого объекта для экземпляра.

Теперь, когда мы добавили необходимые базовые методы для отделения дочерних сущностей, наборы дочерних сущностей и свойств с поздней загрузкой, мы можем завершить метод Product Detach путем добавления вызова Detach для свойств Category, ItemList и Desc. Далее следует полноценный метод Product Detach():

partial class Product : LinqEntityBase
{
    public override void Detach()
    {
        if (null == PropertyChanging)
            return;
           
        PropertyChanging = null;
        PropertyChanged = null;
           
        this._Category = Detach(this._Category);
        this._Items = Detach(this._Items, attach_Items, detach_Items);
        this._Descn = Detach(this._Descn);
    }
}