» » Расширения Entity Framework 6, о которых вы могли и не знать

 

Расширения Entity Framework 6, о которых вы могли и не знать

Автор: admin от 24-01-2017, 14:30, посмотрело: 549

Расширения Entity Framework 6, о которых вы могли и не знать

Многие программисты делают записи, описывают трудности, красивые и не очень решения, с которыми приходится сталкиваться по долгу службы. Это может быть собственный технический блог, рабочая вики, или даже обычный блокнот — суть одна. Постепенно, из маленьких Evernote-заметок может вырасти целая статья на Хабр. Но время идет, перемена места работы сулит изменения в стеке разработки, да и технологии не стоят на месте (кстати, EF Core уже пару месяцев как в версии 1.1). С другой стороны, Entity Framework 6 был и остается "рабочей лошадкой" для доступа к данным в корпоративных приложениях на стеке .net, не в последнюю очередь благодаря своей стабильности, низкому порогу входа и широкой известности. Поэтому, я надеюсь, статья всё еще окажется кому-то полезной.


Содержание:



  • Database First без EDMX

  • Работа с отсоединенными графами

  • Модификация SQL. Добавление табличных указаний

  • Кэширование данных за пределами времени жизни DbContext

  • Retry при ошибках от SQL Server

  • Подменяем DbContext, изолируемся от реальной БД

  • Быстрая вставка


  • Database First без EDMX


    Не хотелось бы начинать очередной раунд спора "Code First vs. Database First". Лучше просто расскажу, как облегчить себе жизнь, если вы предпочитаете Database First. Многие разработчики, использующие этот подход, отмечают неудобство работы с громоздким EDMX-файлом. Этот файл может превратить в ад командную разработку, сильно затрудняя слияние параллельных изменений вследствие постоянного "перемешивания" своей внутренней структуры. Среди прочих недостатков, для моделей с несколькими сотнями сущностей (обычный такой legacy-монолит), вы можете столкнуться с сильным падением скорости любого действия, работая со стандартным EDMX-дизайнером.


    Решение кажется очевидным — необходимо отказаться от EDMX в пользу альтернативных средств генерации POCO и хранения метаданных. Задача-то несложная, и в EF есть "из коробки" — это пункт "Generate Code First From Database", доступный в Visual Studio (VS2015 точно). Но, на практике — очень неудобно настраивать и обновлять полученную модель, используя этот инструмент. Далее, кто работает с EF достаточно давно — может помнить расширение Entity Framework Power Tools, решающее схожие задачи — но, увы, проект более не развивается (на VS2015 без хака не поставить), а часть разработчиков этого инструмента ныне работает непосредственно в команде EF.


    Казалось бы, все плохо — и тут мы нашли EntityFramework Reverse POCO Generator. Это Т4-шаблон для генерации POCO на основе существующей БД с большим количеством настроек и открытым исходным кодом. Поддерживаются все основные фичи EDMX, и есть ряд дополнительных вкусностей: генерация FakeDbContext/FakeDbSet для юнит-тестирования, покрытие моделей атрибутами (напр. DataContract/DataMember) и др. Не говоря уже о полном контроле за генерацией кода, которую дает Т4 (который есть и в EDMX, конечно же). Резюмируя: работает стабильно, команде нравится, легко мигрировать существующие проекты.


    Работа с отсоединенными графами


    Прикрепить к DbContext новый, либо ранее полученный в другом контексте единичный объект обычно не составляет труда. Проблемы начинаются в случае, собственно, отсоединенных графов — EF "из коробки" не отслеживает изменения в navigation properties вновь присоединяемой к контексту сущности. Для отслеживания изменений, для каждой сущности во время жизни контекста должен существовать соответствующий entry — объект со служебной информацией, в том числе состоянием сущности (Added, Modified, Deleted и т.п.). Заполнить entry для присоединения графа — возможно как минимум 2 путями:



  • Хранить состояние в самих сущностях, самостоятельно отслеживая изменения. Таким образом, наш отсоединенный граф будет содержать в себе всю необходимую информацию.

  • Ничего не делать заранее, а при подсоединении отсоединенного графа — подтянуть из БД исходный граф и проставить состояния entry на основании сравнения двух графов.


  • Пример решения #1 можно найти, например, в свежем Pluralsight-курсе от Julie Lerman, известного специалиста по EF. Для его самостоятельной generic-реализации необходимо большое количество телодвижений. Все сущности должны реализовать интерфейс IStateObject:


    public interface IStateObject
    {
        ObjectState State { get; set; }
    }

    Тем или иным способом, необходимо обеспечить актуальность значений State, чтобы после присоединения все сущностей в графе к контексту:


    context.Foos.Attach(foo);

    пройти по всем entry, редактируя состояние:


    IStateObject entity = entry.Entity;
    entry.State = ConvertState(entity.State);

    В этом случае нам не требуются дополнительные обращения к БД, но решение получается объемное, хрупкое, и потенциально нерабочее для отношений "многие-ко-многим". Еще и модели замусорили (к слову, требование реализации интерфейса можно решить путем модификации шаблонов генерации POCO из предыдущего раздела статьи).


    Рассмотрим решение #2. Буду краток:


    context.UpdateGraph(root, map => map.OwnedCollection(r => r.Childs));

    вызов сей — добавит в контекст сущность root, обновив при этом navigation property с коллекцией дочерних объектов Childs, ценой SELECT-а к БД одного лишь. Что стало возможным благодаря библиотеке GraphDiff, автор которой сделал всю черную работу и выловил основные баги.


    Модификация SQL. Добавление табличных указаний


    Генерация, казалось бы, простой инструкции SELECT… FROM Table WITH (UPDLOCK) не поддерживается EF. Зато есть interceptor'ы, позволяющие модифицировать генерируемый SQL любым подходящим способом, например, с помощью регулярных выражений. Например, добавим UPDLOCK на каждый генерируемый SELECT в пределах времени жизни контекста (естественно, гранулярность — не обязательно контекст, всё зависит от вашей реализации):


    using (var ctx = new MyDbContext().With(SqlLockMode.UpdLock)) {}

    Для этого, объявим метод With внутри контекста и зарегистрируем interceptor:


    public interface ILockContext
    {
        SqlLockMode LockMode { get; set; }
    
        MyDbContext With(SqlLockMode lockMode);
    }
    
    public class MyDbConfig : DbConfiguration
    {
        public MyDbConfig()
        {
            AddInterceptor(new LockInterceptor());
        }
    }
    
    [DbConfigurationType(typeof(MyDbConfig))]
    public partial class MyDbContext : ILockContext
    {
        public SqlLockMode LockMode { get; set; }
    
        public MyDbContext With(SqlLockMode lockMode)
        {
            LockMode = lockMode;
            return this;
        }
    
        private static void MyDbContextStaticPartial() { }
    }




    Кэширование данных за пределами времени жизни DbContext


    EF кэширует такие вещи, как:



    • Query Plan.

    • Metadata.

    • Compiled Queries.


    Кэширование данных есть только в пределах жизни контекста (вспомним метод Find), да и полноценным кэшем это язык назвать не повернется. Как нам организовать единый для всех контекстов, управляемый кэш в памяти процесса? Используем EntityFramework.Plus, либо ее "бедную" альтернативу EntityFramework.Cache:


    public void SelectWithCache()
    {
        using (var ctx = new MyDbContext())
        {
            ctx.Customers.FromCache().ToList();
        }
    }
    
    [Test]
    public void SelectWithCache_Test()
    {
        TimeSpan expiration = TimeSpan.FromSeconds(5);
        var options = new CacheItemPolicy() { SlidingExpiration = expiration };
        QueryCacheManager.DefaultCacheItemPolicy = options;
    
        SelectWithCache(); //запрос к БД
        SelectWithCache(); //из кэша
        Thread.Sleep(expiration);
        SelectWithCache(); //запрос к БД
    }

    Достаточно запустить SQL-профайлер, чтобы убедиться — 2-й вызов SelectWithCache() не затрагивает БД. Lazy-обращения также будут кэшированы.


    Более того, возможна интеграция EF и с распределенным кэшем. Например, через самописный cache manager на базе Sytem.Runtime.Caching.ObjectCache, подключенный к EntityFramework.Plus. В то же время, NCache поддерживает интеграцию с EF "из коробки" (тут конкретикой не могу поделиться — не пробовал).


    Retry при ошибках от SQL Server


    public class SchoolConfiguration : DbConfiguration
    {
        public SchoolConfiguration()
        {
            SetExecutionStrategy("System.Data.SqlClient", () => 
                new SqlAzureExecutionStrategy(maxRetryCount: 3, maxDelay: TimeSpan.FromSeconds(10)));
        }
    }

    SqlAzureExecutionStrategy — данная стратегия содержится в EF6 (отключена по-умолчанию). При ее использовании — получение определенного кода ошибки в ответе сервера приведет к повторной отправке SQL-инструкции на сервер.



    Интересности:



    • На базе исходного кода SqlAzureExecutionStrategy возможно написать свою стратегию, переопределив коды ошибок, приводящие к retry.

    • Использование retry-стратегий, включая SqlAzureExecutionStrategy — накладывает ряд ограничений, самым серьезным из которых является несовместимость с пользовательскими транзакциями. Для явного объявления транзакции — стратегию отключаем через обращение к System.Runtime.Remoting.Messaging.CallContext:

    • Стратегию можно даже покрыть интеграционными тестами (вновь спасибо Julie Lerman, подробно осветившей этот вопрос).


    Подменяем DbContext, изолируемся от реальной БД


    В целях тестирования, подменим DbContext прозрачно для вызывающего кода, и заполнить поддельный DbSet тестовыми данными. Приведу несколько способов решения задачи.
    Cпособ #1 (длинный): вручную создать заглушки для IMyDbContext и DbSet, явно прописать необходимое поведение. Вот как это может выглядеть с использованием библиотеки Moq:


    По этой теме есть базовая статья с MSDN: Entity Framework Testing with a Mocking Framework (EF6 onwards). А меня этот подход когда-то настолько впечатлил, что получился демо-проект на гитхабе (с использованием EF6 DbFirst, SQL Server, Moq, Ninject). Кстати, в уже упоминавшемся курсе Entity Framework in the Enterprise тестированию посвящена целая глава.


    Способ #2 (короткий): использовать уже упоминавшийся Reverse POCO Generator, который по умолчанию создает заглушки для ваших DbContext и всех DbSet (внутри FakeDbSet будет обычная in-memory коллекция).

    Быстрая вставка


    Для одновременной вставки в БД SQL Server тысяч новых записей — крайне эффективно использовать BULK-операции вместо стандартного построчного INSERT. Проблематику я освещал подробнее в отдельной статье, поэтому приведу ниже только готовые к использованию решения на основе SqlBulkCopy:


    -> EntityFramework.Utilities
    -> Entity Framework Extensions


    На этом у меня всё. Делитесь своими рецептами и хитростями в комментариях =)



    Источник: Хабрахабр

    Категория: Программирование, Microsoft

    Уважаемый посетитель, Вы зашли на сайт как незарегистрированный пользователь.
    Мы рекомендуем Вам зарегистрироваться либо войти на сайт под своим именем.

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
    Введите два слова, показанных на изображении: *