Страсти по делегатам
Всем привет, особенно староверам, стирающим бельё в проруби.
В общем задача на Entity Framework. Как динамично передавать фильтры в запросы, мы уже проходили в прошлой ветке.
Теперь хотеся запрещеночки - передавать условия, связанные с другими Entities.
Но что-то не пойму как.
то есть запрос должен получится примерно такой
Select * from Person P
where exists (
select 1 from Event E
where E.name = P.Name
)
Никогда подобное еще не интересовало.
Но вроде пользуются только двумя вариантами: Expression trees и Dynamic LINQ library
https://www.bytehide.com/blog/dynamic-linq-query-csharp
Вот может быть интересная реализация
https://blog.jeremylikness.com/blog/dynamically-build-linq...
Вот может быть интересная реализация
https://blog.jeremylikness.com/blog/dynamically-build-linq...
Когда я ссылку на это где-то более года назад давал, вы все плевались. А теперь сами делитесь. "А шо случилось?"
А шо случилось? -- "Теперь хотеся запрещеночки "
Ну так сколько раз повторять - всё зависит от ситуации.
Ведь не обсуждается вопрос хорошо это или плохо, нужно применять или не нужно. Понадобилось кому то.
А может та ссылка была ещё выставлена в понедельник 13-го И неизвестно под каким соусом, может опять переперчили
нет ничего про Linq to Entities
А что под этим конкретно имеется виду?
Ничего особенно тут не заметил
https://learn.microsoft.com/en-us/dotnet/framework/data/ad...
А так не пробовали?
var result2 = query.Where(p => advancedCondition.Compile().Invoke(db, p)).ToList();
вот еще попалось но не разбирался. Может на что натолкнёт
https://www.roundthecode.com/dotnet-tutorials/using-linq-e...
https://www.codeproject.com/Articles/5358166/A-Dynamic-Whe...
коллега помог
static void Main(string[] args)
{
Func<DbEntities, Expression<Func<Person, bool>>> funky =
db =>
p => db.Set<Event>().Any(e => e.Author == p.Name);
SomeDbRequest(funky);
}
static void SomeDbRequest(Func<DbEntities, Expression<Func<Person, bool>>> advancedCondition)
{
using (DbEntities db = new DbEntities())
{
Expression<Func<Person, bool>> expression = advancedCondition(db);
var result2 = query.Where(expression);
}
}
с одной стороны, молодец, решил
С другой - опять же нифига не понял, в чем проблема была

static void SomeDbRequest(Func<DbEntities, Expression<Func<Person, bool>>> advancedCondition)
{
using (DbEntities db = new DbEntities())
{
var result2 = query.Where(p => db.Set<Event>().Any(e => e.Author == p.Name));
}
}
оно и без джойнов работает
вопрос был в том, как этот предикат передать извне
Обычно можно сделать так
это правильный ответ
но чтобы сохранить интригу, что изменится от того, если ЛИНК написать так(как у нас внегласно заведено)?
вместо ! напишем == false
var personsNotInEvent = dbContext.Persons .Where(p => dbContext.Events.Any(e => e.PersonId == p.PersonId) == false) .ToList();
Только у вас подзапрос будет выполняться на каждое вхождение основного запроса (саечка за неоптимальностьнука расскажи, почему ты так решил?)
потому что делегат в where выполняется для каждого вхождения
var personsNotInEvent = dbContext.Persons .Where(p => dbContext.Events.Any(e => e.PersonId == p.PersonId) == false) .ToList();
Вы начитались "каверзных вопросов", где требуют знать все тонкости, как построитель и оптимизатор запросов внутри преобразует те или иные условия в фильтрующих делегатах?
весь этот LINQ конвертируется в один SQL-запрос
А если я использую в
dbContext.Events.Any(e => e.PersonId == p.PersonId)
какую-нибудь внешнюю по отношению к запросу коллекцию, типа
myCollection.Contains(item => item.PersonId == p.PersonId)
то вся коллекция myCollection будет передана в запрос?
Linqpad даёт следующее
List<LiveResult> (2 items)••• | ||||||||
Case | ResultsGraph | Mean | Min | Max | Range | AllocatedBytesΞΞ | OperationsΞΞ | Phase |
---|---|---|---|---|---|---|---|---|
BenchmarkDemoWhereNot | 2.72 μs | 2.67 μs | 2.75 μs | 3% | 2'200 | 3'145'728 | Complete | |
BenchmarkDemoWhereFalse | 2.79 μs | 2.70 μs | 2.98 μs | 10% | 2'240 | 7'077'888 | Complete |
какую-нибудь внешнюю по отношению к запросу коллекцию, типа
myCollection.Contains(item => item.PersonId == p.PersonId)
то вся коллекция myCollection будет передана в запрос?
Это имхо вообще не переведётся в SQL.
А вот если у тебя будет коллекция простых integer, то да, все значения передадутся в запрос.
А ты как думал: на каждый элемент по запросу в БД?))
Там локальная SQLite, хотя обновил до нет 8.0
Немного изменил под существующие таблицы
#load "BenchmarkDotNet" void Main() { } [Benchmark] public void BenchmarkDemoWhereNot() { var context = this; var personsNotInEvent = context.Artists .Where (p => !Albums.Any(e => e.ArtistId == p.ArtistId)); } [Benchmark] public void BenchmarkDemoWhereFalse() { var context = this; var personsNotInEvent = context.Artists .Where (p => Albums.Any(e => e.ArtistId == p.ArtistId) == false); }
А ты как думал: на каждый элемент по запросу в БД?))
А я не помню, как оно работает. Вроде, если сущности из одного контекста, то он пытается это в один запрос сделать. Но в моём примере по ссылке либо джойны придлагают, либо сначала сделать один запрос, превратить его в просто коллекцию объектов, а потом второй запрос с использованием этой коллекции.
И интересно было бы посмотреть сгенерированный SQL.
но проблемо, для этого linqpad и открывался
SELECT "a"."ArtistId", "a"."Name" FROM "Artist" AS "a" WHERE NOT EXISTS ( SELECT 1 FROM "Album" AS "a0" WHERE "a0"."ArtistId" = "a"."ArtistId")
ну и как, есть разница в скорости в SQL Lite?
на такой мелочевке данных то.
Отчего - это нужно изучать специально. Может и нет 8.0 починили
Я-то думал, ты в ЕФ шаришь
А ты теоретик.
Я в последнее время с ним давно не работал. Так, мелочь. В основном приходится со старым самописным дерьмом разбираться в теме общения с БД. Зато не всякое старьё APS.NET MVC использую, как некоторые, а новомодный Blazor. ))
Вариант с == false генерерует в оракле вот такой запрос
и он гораздо медленнеее
SELECT "a"."ArtistId", "a"."Name" FROM "Artist" AS "a" WHERE 0 = CASE WHEN EXISTS ( SELECT 1 FROM "Album" AS "a0" WHERE "a0"."ArtistId" = "a"."ArtistId") THEN 1 ELSE o END
На работе ценится не зазубривание букварей, а умение решать неожиданно всплывающие проблемы типа таких, когда не понимаешь, почему и откуда. Вон, народ по 15 и более лет всё понять не может, почему им Студия не называет конкретную причину ошибки, которая может остоять от декларированной на несколько шагов.
На работе ценится не зазубривание букварей
Никто этого и не требует
Но понимать, сколько и в какой момент выполнится запрос в БД это гораздо важнее экономии 40% кода посредством использования новых фич.
а то напишешь, оно даже компилируется, на тестовой системе летает, а потом в проде умирает.
смотришь, а у ребят джойны в оперативной памяти происходят.
На работе ценится не зазубривание букварейНикто этого и не требует
Но понимать, сколько и в какой момент выполнится запрос в БД это гораздо важнее экономии 40% кода посредством использования новых фич.
а то напишешь, оно даже компилируется, на тестовой системе летает, а потом в проде умирает.
смотришь, а у ребят джойны в оперативной памяти происходят.
Если ребята с этим EF не работали, или мало работали - это нормально. Показать им, как надо, объяснить - в чём проблема?
это гораздо важнее экономии 40% кода посредством использования новых фич.
Это ещё и 40% понимания добавляет. Когда у тебя весь класс на одном экране, а не на пяти, потому что чел решил везде писать типа такого
private Obj _obj; public Obj Obj { get { if(_obj == null) { _obj = new Obj(); } return _obj; } }
вместо такого
private Obj _obj; public Obj Obj => _obj ??= new ();
А вообще, есть у кого-нибудь такие правила по оформлению кода, что типа в этом проекте запрещено использовать инициализаторы - нужно обычный конструктор и потом каждый раз созданному объекту свойства присваивать? Или свичи новые нельзя - только старые (которые statement, а не expression). Или нельзя использовать часть операторов - например ??= и прочие null-coalescing и null-propagation? Или нельзя bodies expressions (со стрелочками)? Нельзя лямбды - метод отдельный специально определяй. При этом язык и версия фреймворка позволяют. Т.е. нужно себя заставить писать где-то на уровне C# 2, но на версии C# 12 (.NET 8).
public Obj Obj => _obj ??= new ();Мало того, что при чтении этого ломается мозг, так тут еще и существует опасность забыть знак равно и получать каждый раз новый объект.
У вас наверное и от 2х2=4 он сломаться может?
Опасность всегда существует. Поскользнулся, упал, очнулся - гипс.
наверное и от 2х2=4 он сломаться может?
а то
ConstantExpression constant2 = Expression.Constant(2); BinaryExpression multiply = Expression.Multiply(constant2, constant2); Expression<Func<int>> lambda = Expression.Lambda<Func<int>>(multiply); Func<int> multiplyFunc = lambda.Compile(); int result = multiplyFunc();
Если в вашем поселении не могут выучить один новый оператор в год, это ваши проблемы. Особенно, когда этот оператор позволяет сократить некоторые конструкции в разы.
Как уже говорилось, многие паттерны-шматтерны возникли не из-за гениальности разных дядей Бобов и всяких банд четырёх, а из-за недостаточных возможностей языков и фреймворков той поры. Но в некоторых поселениях продолжают долбиться в замшелые книжонки и восхищаться ходящими по сцене старичками-инфоцыганами, льющими тонну воды на пару умных фраз.