С# принцип работы Entity Framework

150
10 октября 2018, 03:20

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

Существует база данных пользователей. Каждый пользователь обладает уникальным первичным ключом. Например, нужно найти пользователя с ключом 124. Для этого пишется соответствующий LINQ запрос. Дальше, Entity Framework превращает этот LINQ-запрос в дерево выражений (extension tree). На следующим этапе Entity Framework передаёт это дерево выражений какой-либо СУБД (пусть это будет Microsoft SQL server). СУБД, в свою очередь, преобразует это деревья выражений в понятный для себя SQL запрос, выполнит его, обернёт результаты или результат в C# объекты и вернет их.

Буду бесконечно благодарен за любую критику.

Answer 1

Очень широкий вопрос. Не могу сказать, что я прямо все детали представляю, однако "лучший способ разобраться — попробовать объяснить кому-то другому". Попробуем.

Нет, в СУБД передаётся уже готовый SQL. Скачайте программу LINQpad и посмотрите какой SQL передаётся. Убедиться в этом можно очень просто: откройте SQL Server Profiler и убедитесь, что никаких деревьев выражений не передаётся, а передаётся обычный SQL.

Кроме того, нужно понимать, что LINQ-запросы состоят из двух частей: одна передаётся на сервер и выполняется там, а другая выполняется на клиенте в оперативке. Очень важно научиться различать границу этих двух частей.

Например, возьмём пример:

return await this.ApplicationDbContext
                 .OrderComments
                 .Include(x => x.Order)
                 .Where(x => x.Order.ManagerId == managerId)
                 .AsNoTracking()
                 .ToListAsync();

То, что выше строки AsNoTracking — уходит на сервер, AsNoTracking уже показывает EF'у каким образом конвертировать данные полученные от сервера в оперативку (не следить за изменениями сущностей, на этом экономится память клиента), ToList уже выполняется на клиенте, но и на клиенте мы могли начать новую часть LINQ-запроса используя .Where, .Select и прочие LINQ. Только это два разных LINQ: Linq to SQL и Linq to Entities. Они немного разные и это нужно тоже понимать.

Собственно, не всегда всё то, то вы думаете исполнять на сервере выполнится на сервере. Тут как раз важно понимать, сумеет компилятор разобрать ваше дерево выражений так, чтобы исполнить на сервере - значит исполнит, нет - выкинет ошибку.

Обычно считают все данные на сервере, чтобы на клиентской стороне оставалось лишь выполнить ToArray — но иногда приходится и на клиенте кое-что считать.

Вы можете немного управлять что и как считать, если будете помнить о разнице между IEnumerable и IQueryable. Первое считается на клиенте, второе можно посчитать на сервере. Например, .Where (и большинство других LINQ-примитивов) есть и в виде IEnumerable и для IQueryableи можно легко промахнуться (пример), думая, что вы считаете на сервере, а по факту - на клиенте. (Способ промахнуться: не тот using ткнуть, когда решарпер предлагает выбор на Where)

И можно гарантированно заставить считать на клиенте, если поставить .AsEnumerable или .ToList/.ToArray:

var regItems = db.Registrations
                 .Where(x => x.UserID == this.UserID)
                 .AsEnumerable()
                 .Select(x => new OrderItemModel(x))
                 .ToArray();

Но это нужно понимать для чего и зачем вы делаете. В конкретно этом примере перегоняются данные из одного класса в другой (про который сервер ничего не знает), много реже приходится прям действительно что-то считать серёзное.

Я так понял, именно в IQueryble происходит попытка разобрать дерево выражений? Или в IEnumerable тоже?

Когда мы говорим о деревьях выражений (Expression) то нужно понимать, что IEnumerable - это Func<T, bool> а IQueryable — это Expression<Func<T, bool>> — то есть Enumerable не умеет в expression от слова "по определению" и от слова "совсем". Там просто нечего разбирать.

Причём одна и та же лямбда x => x % 2 в тексте программы может оказаться как просто функцией, которая на вход принимает x, а на выход отдаёт bool (т.е. Func<T, bool>) — и поэтому компилятор не поймёт, что там внутри, там и оказаться деревом выражений, т.е. Expression<Func<T, bool>> (в котором компилятор уже умеет ковырятся и строить дерево) и зависит это от того, какой using у вас применится к .Where(ваша_лямбда) — есть и такой экстеншн и такой.

Вот до .AsEnumerable ("на сервере"):

А вот после ("на клиенте"):

Я так понимаю, работая с IEnumerable мы сначало будем вынуждены тянуть данные с БД и уже потом с ними работать, кода с IQueryble мы с этими данными работаем непосредствено на сервере?

Да. Причём представьте, что у вас база далеко на другом сервере и в таблице миллионы строк:

Contact[] contacts = this.ApplicationDbContext.Contacts
                         .AsEnumerable()
                         .Where(x => x.ApplicationUserId == userId)
                         .OrderBy(x => x.Id)
                         .ToArray();

Представили, как будет весело на клиенте делать фильтрацию (когда большинство скачанных данных будут просто отброшены) и потом ещё сортировку?

Пример приведён из серии "не делайте так". И вот другой вариант, хороший:

return await this.ApplicationDbContext.Contacts
                 .Include(x => x.ApplicationUser.City)
                 .Where(predicate)
                 .OrderBy(orderBy)
                 .AsNoTracking()
                 .ToListAsync();

Где сервер сам отберёт данные, сам отсортирует и отдаст их на клиент.

При этом когда вы пишете классы репозиториев с запросами есть как сторонники подхода, когда на выходе из репозитория вам возвращается Enumerable, а есть и те кто отдаёт Queryable чтобы дать возможность ещё немного после подрихтовать полученные данные, не отцепляясь от базы. Я не люблю, когда знание о деталях реализации (а EF - именно деталь реализации, это мог быть и Dapper с plain sql запросами) просачивается в другие слои приложений, поэтому чтобы запросы не были разбросаны по всему приложению сосредотачиваю их в репозиториях и отдают уже только Enumerable из них.

READ ALSO
Переход на работу с Visual Studio Code

Переход на работу с Visual Studio Code

Хочу перейти на VS Code, он удобный не такой громоздкий как VSsudio2017 да и консоль под рукой всегдаТак же он кросс платформенный

171
Как установить в .NetCore проект, (Microsoft.EntityFrameworkCore.SqlServer)

Как установить в .NetCore проект, (Microsoft.EntityFrameworkCore.SqlServer)

Как установить вNetCore проект, (Microsoft

142
File(Image) on edit form Symfony 4

File(Image) on edit form Symfony 4

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

162