Русский
Русский
English
Статистика
Реклама

Entity framework core

Полезные приемы для работы с EntityFramework Core

24.08.2020 10:10:39 | Автор: admin
Всем привет.

Я недавно обнаружил, что не все, кто работают с EF, умеют его готовить. Более того, не горят желанием разбираться. Сталкиваются с проблемами на самых ранних этапах настройке.
Даже после успешной настройки появляются проблемы с запросами данных. Не потому, что люди не знают LINQ, а потому что не все можно смаппить из объектов в реляционные модели. Потому что работая с линком люди думают таблицами. Рисуют SQL запросы и пытаются перевести их в LINQ.

Об этом и, возможно, о чем-то еще я и хочу поговорить в статье.

Настройка


Пришел человек на проект, впервые увидел там EF, подивился такому чуду, научился пользоваться, решил использовать в личных целях. Создал новый проект, А дальше что делать?

Начинается гуглёшь, пробы, ошибки. Для того, чтобы просто подключить EF к проекту, разработчик сталкивается с непонятного рода проблемами.

1. А как настроить его на работу с нужной СУБД?
2. А как настроить работу миграций?

Уверен, проблем больше, но это, наверное, самые частые. Давайте обо всем по порядку.

1. Ну тут именно гуглить. :) Ваша задача найти провайдера EntityFramework Core для вашей СУБД.
Так же в описании провайдера вы найдете инструкции по настройке.

Для PostgreSQL, например, нужно установить Nuget пакет Npgsql.EntityFrameworkCore.PostgreSQL
Создать собственный контекст, принимающий опции DbContextOptions и создать все это дело таким вот образом

var opts = new DbContextOptionsBuilder<MyDbContext>()        .UseNpgsql(constring);var ctx = new MyDbContext(opts.Options);


Если у вас ASP .NET Core приложение, контекст регистрируется в контейнере по другому. Но это уже вы можете в своем рабочем проекте подглядеть. Или в гугле.

2. Если нянек в вашей компании нет, то вы, скорее всего, в курсе, как установить необходимые инструменты для работы с миграциями. Хот для диспетчера пакетов, хоть NET Core CLI.

Но вот о чем вы, возможно, не в курсе, так это о том, что выбранный стартовый проект (--startup-project) при работе с миграциями запускается. Это значит, что если стартовым проектом для миграций является тот же проект, который запускает ваше приложение и при запуске вы зачем-то накатываете миграции через ctx.Database.Migrate(), то при попытке создать удалить ранее созданную миграцию или создать еще одну, то последняя созданная миграция накатится на базу. СЮРПРИЗ!

Но, возможно, при попытке создать первую миграцию вы словите что-то типа этого

No database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.


А все потому, что инструмент, который работает с миграциями, создает их на основе вашего контекста, но для этого ему нужен экземпляр. И чтобы его предоставить, вам нужно в стартовом проекте реализовать интерфейс IDesignTimeDbContextFactoryЭто не сложно, там всего один метод, который должен вернуть экземпляр вашего контекста.

Настройка моделей


Вот вы уже создали вашу первую миграцию, но она почему то пустая. Хотя у вас есть модели.

Дело в том, что вы не научили ваш контекст транслировать ваши модели в вашу БД.

Для того, чтобы EF создал миграцию для создания таблицы для вашей модели в БД, необходимо как минимум завести свойство типа DbSet<MyEntity> в вашем контексте.

например, по такому вот свойству
public DbSet<MyEntity> MyEntities { get; set; }

Будет создана таблица MyEntities с полями, соответствующими свойствам сущности MyEntity.

Если вы не хотите заводить DbSet или хотите повлиять на дефолтные правила создания таблицы для сущности, вам нужно переопределить метод
protected override void OnModelCreating(ModelBuilder modelBuilder)
вашего контекста.

Как это сделать, вы скорее всего знаете. Более того, вы наверняка знакомы с атрибутами, позволяющими управлять правилами маппинга. Но вот какая есть штука. Атрибуты не дают полного контроля над маппингом, а значит у вас будут уточнения в OnModelCreating. То есть правила маппинга сущностей у вас будут и в виде аннотаций и в виде fluent api. Ходи потом ищи, почему поле имеет не то имя, которое вы ожидали, или не те ограничения.

Ну тогда все просто, скажете вы Я буду настраивать все через в OnModelCreating

И тут, после настройки 20й сущности из 10 полей, у вас начинает рябить в глазах. Вы пытаетесь найти настройку поля у какой либо сущности, но перед глазами все плывет от единообразной плахи длиной в 200-500 строк.

Да, можно разбить эту плаху на 20 методов и станет чуточку проще. Но полезно знать, что есть интерфейс IEntityTypeConfiguration<TEntity>, реализовав который для конкретной сущности, вы можете описать в этой реализации правила маппинга конкретной сущности. Ну а для того, чтобы контекст это подхватил, в OnModelCreating нужно написать
modelBuilder.ApplyConfigurationsFromAssembly(assemblyWithConfigurations);

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

foreach (var idEntity in modelBuilder.Model.GetEntityTypes()    .Where(x => typeof(BaseIdEntity).IsAssignableFrom(x.ClrType))    .Select(x => modelBuilder.Entity(x.ClrType))){    idEntity.HasKey(nameof(BaseIdEntity.Id));}


Построение запросов


Ну вот, порядок навели, стало чуточку приятнее.

Теперь осталось переделать запросы нашего домашнего проекта из SQL в Linq. Я уже писал запросы на работе, это проще простого
ctx.MyEntities.Where(condition).Select(map).GroupBy(expression).OrderBy(expression) легкота.

Так, какой там у нас запрос?
SELECT bla bla bla FROM table
RIGHT JOIN

ага

ctx.LeftEntities.RightJoin( f@#$

Я вот пользовался Linq и его методами расширения задолго до того, как познакомился с EF. И у меня ни разу не возникало вопроса, а где же в нем RIGHT JOIN, LEFT JOIN

Что такое LEFT JOIN с точки зрения объектов?
Это
class LeftEntity {    public List<RightEntity> RightEntities { get; set; }}


Это даже не магия. У нас просто есть некая сущность, которая ссылается на список других сущностей, но при этом может не иметь ни одной.
То есть это
ctx.LeftEntities.Include(x => x.RightEntities)

А что такое RIGHT JOIN? Это перевернутый LEFT JOIN, то есть мы просто начинаем с другой сущности.

Но бывает всякое, иногда нужен контроль над каждой связкой, как отдельной сущностью, даже если левой сущности не сопоставлено ни одной правой (правая NULL). Поэтому явно LEFT JOIN можно выполнить так
ctx.LeftEntities.SelectMany(x => x.RightEntities.DefaultIfEmpty(), (l, r) => new { Left = l, Right = r })

Таким образом мы получаем контроль над связкой, как в реляционной модели. Зачем это может понадобиться? Скажем, заказчику нужно вывести данные в виде таблицы, причем нужна сортировка и/или пагинация.

Мне тоже эта затея не нравится, но у всех свои причуды и нескончаемая любовь в Excel. Так что прикроем мат кашлем, выругавшись в кулак, и продолжим работать. Что там у нас дальше?

FULL JOIN
Так, ну я уже понял, не думаем SQLем, думаем объектами, вот так, а теперь вот так вот черт. А это как изобразить?

Без кортежей никуда.

Вообще, когда нам приходится работать с кортежами, мы теряем понимание о типе связи (1-1, 1-n, n-n) и вообще, теоретически, можем скрестить мух и слонов. Это черная магия.
Сделаем это!

За основу возьмем many-to-many.

Таким образом имеем 3 типа сущности
LeftEntity
RightEntity
LeftRight (для организации связи)

LeftRight я создам с композитным ключом. Прямой доступ к этой сущности мне не нужен, внешних ссылок на нее не потребуется, поэтому плодить лишние поля и индексы не буду.

В результате запроса я хочу получить набор кортежей, в котором будет левая сущность и правая. Причем, если девая сущность никак не связана с правой, то правый объект будет null. То же самое касается правых сущностей.

Получается, что LeftRight нам не подходит в качестве вывода, его ограничения не позволят создать связку без одной из сущностей. Если вы практикуете DDD, вы словите неконсистенцию.
Даже если не практикуете, делать так не стоит. Создадим новый тип для вывода
LeftRightFull, который все так же содержит ссылку на левую и на правую сущности.

Итак, у нас есть
Left
L1
L2

Right
R1
R2

LeftRight
L2 R2

Мы хотим на выходе
L1 n
L2 R2
n R1

Начнем с левого соединения
var query = ctx.LeftEntities    .SelectMany(x => x.RightLinks.DefaultIfEmpty(), (l, lr) => new LeftRightFull    {        LeftEntity = l,        RightEntity = lr.RightEntity    })


Вот, мы уже имеем
L1 n
L2 R2

Чего дальше то? Прилепить RightEntity через SelectMany? Ну так у нас получится
L1 n R1 n
L1 n R2 L2
L2 R2 R1 n
L2 R2 R2 L2

Отсеять лишнее (L2 R2 R1 n и L1 n R2 L2) по условию мы сможем, однако, остается непонятным, как L1 n R1 n превратить в
L1 n
n R1

Возможно, раскопки увели нас не в ту степь. Давайте просто соединим эти аналогичные запросы с левыми соединениями для левых и правых сущностей через Union.
L1 n
L2 R2
UNION
L2 R2
n R1

Тут могут возникнуть вопросы по быстродействию. На них я не отвечу, потому что все будет зависеть от конкретной СУБД и дотошности ее оптимизатора.

В качестве альтернативы можно создать View с нужным запросом. На EF Core 2 Union не работает, так что мне пришлось создать View. Однако, рекомендую про нее не забывать. Вдруг, вы решите реализовать мягкое удаление сущностей через флаг IsDeleted. Во вьюхе нужно это поддержать. Если забудете, то неизвестно когда получите багрепорт.

Кстати, а как реализовать мягкое удаление, не переписывая весь код? Об этом я расскажу в следующий раз. Возможно, о чем нибудь еще.

Всем пока.
Подробнее..

Cоздание переиспользуемых Linq фильтров (построителей предикатов для Where), которые можно применять для разных типов

15.04.2021 22:17:49 | Автор: admin

Способ создания переиспользуемых Linq фильтров (построителей предикатов для условия Where), которые можно применять для разных типов объектов. Поля объектов для фильтрации указываются с помощью MemberExpression.

Способ подходит для Entity Framework, включая Async операции.

Основная идея. Что такое переиспользуемый фильтр?

Например есть приказы:

class Order { public DateTime Start { get; set; }public DateTime? End { get; set; }}

Пусть нужно найти все приказы которые будут действовать в ближайшие 7 дней.

С помощью переиспользуемого построителя фильтра (если бы он был реализован) найти приказы можно так:

var ordersFiltred = orders.WhereOverlap(// с помощью MemberExpressions// указываем по каким полям производить поискfromField: oo => oo.Start,toField: oo => oo.End,// указываем период поискаfrom: DateTime.Now,to: DateTime.Now.AddDays(7)).ToList();

Этот же WhereOverlap можно переиспользовать и применить к другому типу. Например, для поиска командировок:

class Trip { public DateTime? From { get; set; }public DateTime? To { get; set; }}
var tripsFiltred = trips.WhereOverlap(// с помощью MemberExpressions// указываем по каким полям производить поискfromField: oo => oo.From,toField: oo => oo.To,from: DateTime.Now,to: DateTime.Now.AddDays(7)).ToList();

Приказы и командировки - это разные типы объектов, у них нет общего интерфейса, поля для поиска называются по-разному. И все таки для обоих типов (и приказов и командировок) применяется один переиспользуемый фильтр WhereOverlap.

Ниже описано как можно делать такие переиспользуемые построители предикатов.

Как сделать переиспользуемый фильтр

Выше было описано применение WhereOverlap, было бы логично показать его реализацию. Но для того, чтобы сделать WhereOverlap, нужна реализация операторов И, ИЛИ. Поэтому начнем с более простого примера.

Пусть есть выплаты и премии:

class Payout { public decimal Total { get; set; }public bool UnderControl { get; set; }}class Premium {public decimal Sum { get; set; }public bool RequiresConfirmation { get; set; }}

Сделаем переиспользуемый фильтр для поиска платежей больше определенной суммы:

class UnderControlPayFilter {readonly decimal Limit;public UnderControlPayFilter(decimal limit) {Limit = limit;}public Expression<Func<TEnt, bool>> Create<TEnt>(Expression<Func<TEnt, decimal>> sumField) {// GreaterOrEqual - нужно реализовать// GreaterOrEqual - это extension, который принимает//  - указание на поле (Expression sumField)//  - и значение с которым нужно сравнивать (Limit)return sumField.GreaterOrEqual(Limit);}}

Пример использования UnderControlPayFilter фильтра:

// фильтр поиска платежей требующих дополнительного контроля//// конкретный предел (здесь 1000) можно вынести в настройки,// а UnderControlPayFilter зарегистрировать в IoC-контейнере.// Тогда можно централизовано (через найстройки приложения)// управлять максимальным пределомvar underControlPayFilter = new UnderControlPayFilter(1000);//// Применение переиспользуемого фильтра для выплатvar payoutPredicate =underControlPayFilter.Create<Payout>(pp => pp.Total);// здесь, для упрощения, payouts - это массив,// в реальном приложении это может быть Entity Framework DbSet var payouts = new[] {new Payout{ Total = 100 },new Payout{ Total = 50, UnderControl = true },new Payout{ Total = 25.5m },new Payout{ Total = 1050.67m }}.AsQueryable().Where(payoutPredicate).ToList();//// Применение переиспользуемого фильтра для премийvar premiumPredicate =underControlPayFilter.Create<Premium>(pp => pp.Sum);// здесь, для упрощения, premiums - это массив,// в реальном приложении это может быть Entity Framework DbSet var premiums = new[] {new Premium{ Sum = 2000 },new Premium{ Sum = 50.08m },new Premium{ Sum = 25.5m, RequiresConfirmation = true },new Premium{ Sum = 1070.07m }}.AsQueryable().Where(premiumPredicate).ToList();

Все готово, осталось только реализовать GreaterOrEqual extension:

public static class MemberExpressionExtensions {    public static Expression<Func<TEnt, bool>> GreaterOrEqual<TEnt, TProp>(        this Expression<Func<TEnt, TProp>> field, TProp val)            => Expression.Lambda<Func<TEnt, bool>>(                Expression.GreaterThanOrEqual(field.Body, Expression.Constant(val, typeof(TProp))),                 field.Parameters);}

По аналогии можно реализовать extension-ы LessOrEqual, Equal, HasNoVal и другие.

Более сложные переиспользуемые фильтры с операторами И и ИЛИ

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

Дополним UnderControlPayFilter:

class UnderControlPayFilter {readonly decimal Limit;public UnderControlPayFilter(decimal limit) {Limit = limit;}public Expression<Func<TEnt, bool>> Create<TEnt>(Expression<Func<TEnt, decimal>> sumField,Expression<Func<TEnt, bool>> controlMarkField) {// PredicateBuilder нужно реализовать (см. ниже)return PredicateBuilder.Or(sumField.GreaterOrEqual(Limit),controlMarkField.Equal(true));}}

Пример использования:

// для выплатvar payoutPredicate =underControlPayFilter.Create<Payout>(sumField: pp => pp.Total,controlMarkField: pp => pp.UnderControl);// для премийvar premiumPredicate = underControlPayFilter.Create<Premium>(sumField: pp => pp.Sum,controlMarkField: pp => pp.RequiresConfirmation);

PredicateBuilder это A universal PredicateBuilder сделанный Pete Montgomery.

Заключение

Чтобы делать свои переиспользуемые фильтры, нужен только PredicateBuilder и MemberExpressionExtensions. Просто скопируйте их в свой проект. Переиспользуемые фильтры можно оформить как extension (как WhereOverlap), как статический хелпер или класс (как UnderControlPayFilter).

Я сделал парочку переиспользуемых фильтров - GitHub, NuGet (включает PredicateBuilder и MemberExpressionExtensions).

Подробнее..

Нюансы при работе с EF миграциями

19.03.2021 14:20:53 | Автор: admin

Данная статья не является инструкцией по работе с EF миграциями. Здесь не будет инфы о том, как их создавать. Здесь я собрал несколько скользких моментов и попытки их обойти. Давайте начнем!

Старт миграций при запуске приложения

Вам знаком следующий код?

context.Database.Migrate();

Если да, вероятнее всего вы накатываете миграции автоматически при старте проекта. Плохо это или хорошо, можете обсудить в комментариях. Я же в свою очередь хочу предостеречь вот от чего.

Когда вы в cmd, PS или в консоли диспетчера пакетов вызываете какую либо операцию, связанную с миграциями, эта операция включает в явном или неявном виде 2 параметра: проект и запускаемый проект. Проект - это сборка, в которую в конечном счете будут помещены миграции. А вот запускаемый проект - проект, выбранный запускаемым для данного решения (sln). При работе через консоль диспетчера пакетов вы этот параметр никогда не увидите.

Что это означает? А то, что ваш запускаемый проект при выполнении операции с миграциями будет собран (ну об этом вы знаете) и запущен. А это, в свою очередь, означает, что помимо всяких интеграционных штук, которые неожиданно для вас могут отработать, выполнение может дойти до кода, приведенного выше. К чему это может привести? К тому, что текущая операция с миграциями накатит предыдущую созданную вами миграцию. То есть, вы создаете миграцию, тут же, замечаете, что допустили ошибку в настройке сущности, пытаетесь ее удалить через Remove-Migration, но получаете от ворот поворот, потому что в момент запуска операции удаления миграции, она накатывается на базу. Вы, конечно же, выполняете роллбэк последней миграции и затем снова вызываете Remove-Migration. Но, угадайте, что произойдет?

Чтобы это побороть, могу подсказать 2 подхода:

  1. Не использовать применение миграций при запуске проекта

  2. Использовать в качестве запускаемого проекта для операций с миграциями отдельный консольный запускаемый проект.

Если есть идеи лучше, пишите в комментариях. С удовольствием обсужу с вами и приму к сведению.

Удаление миграций

В общем-то, удалять миграции я рекомендую только в двух случаях:

  1. Миграция приводит к потере данных и пока код не добрался до прода, миграцию можно и нужно вычленить.

  2. Миграция еще не слита в ветку, вы создали ее локально, но она не правильная.

Во втором случае можно удалить последнюю миграцию, использовав команду Remove-Migration. Если миграцию уже применили к базе, надо ее предварительно откатить. После выполнения Remove-Migration почистите мусор в файле проекта (csproj). Так же, если у вас всего одна миграция или несколько, но еще не закоммичены, быстрее будет откатить снапшот через Git (или вашу систему контроля версий) и удалить файлы миграций.

В данном случае универсальным решением будет откатить базу до удаляемой миграции, удалить все миграции после проблемной (вместе с ней). Мусор в csproj файле при этом чистить не надо.

Создание SQL миграций

Возможно, не все знают, но на основе разработческих миграций (тех, что создаются в коде, это майки их где-то так называли) можно создать SQL скрипты. Для этого есть команда Script-Migration. С ее помощью можно создать в т. ч. идемпотентные скрипты.

А начиная с версии EF Core 3.0 появилась команда Script-DbContext для создания миграций из контекста базы.

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

Редактирование сущностей

Нут тут, казалось бы, все просто. Мы меняем что-либо в сущности, создаем миграцию, радуемся результату. Вот только результат может быть неожиданным, когда вам нужно удалить одно поле и создать другое того же типа. Мигратор при этом создаст команду для переименования столбца в БД. Все данные из удаляемого столбца, соответственно, перенесутся в новый. В данном случае можно создать 2 миграции: для удаления столбца и для создания нового. После этого, чтобы не маячили 2 миграции вместо одной, можно совместить их код Up и Down и последнюю удалить.

Заключение

В заключении хочу сказать, будьте внимательны к данным, проверяйте новые миграции, вдумывайтесь в них, не бойтесь их редактировать. А на всякие неочевидные грабли при работе с EF вы все равно наступите.

Подробнее..
Категории: Net , Entity framework core

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru