Страсти по делегатам
Всем привет, особенно староверам, стирающим бельё в проруби.
В общем задача на Entity Framework. Как динамично передавать фильтры в запросы, мы уже проходили в прошлой ветке.
Теперь хотеся запрещеночки - передавать условия, связанные с другими Entities.
Но что-то не пойму как.
то есть запрос должен получится примерно такой
Select * from Person P
where exists (
select 1 from Event E
where E.name = P.Name
)
Давно не делал и могу напутать, но вроде там ничего сложного не было - просто перечисляешь используемые через запятую и пользуешь.
не хочу, с таким же успехом я могу чистый SQL писать
Никогда подобное еще не интересовало.
Но вроде пользуются только двумя вариантами: 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
По второй - если оно и заработает, то оно все без синтаксической проверки - то есть очень большая опасность словить ошибку в рантайме.
нет ничего про Linq to Entities
А что под этим конкретно имеется виду?
Ничего особенно тут не заметил
https://learn.microsoft.com/en-us/dotnet/framework/data/ad...
пол этим имеется в виду, что не любой Linq переводится в SQL.
А так не пробовали?
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...
А ты ее в ToSql() конвертни и погляди что там будет.
коллега помог
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));
}
}
оно и без джойнов работает
вопрос был в том, как этот предикат передать извне
Только теперь дошло, что хотелось и таблицу засунуть в выражение
а теперь модифицируем задачу:
Нужно получить людей, которые не участвовали ни в одном ивенте. Как должен выглядеть предикат?
видимо срочно надо... может чем то поможет.
Обычно можно сделать так
var personsNotInEvent = dbContext.Persons .Where(p => !dbContext.Events.Any(e => e.PersonId == p.PersonId)) .ToList();
Это то, что я выше предлагал. Только у вас подзапрос будет выполняться на каждое вхождение основного запроса (саечка за неоптимальность ).
Только у вас подзапрос будет выполняться на каждое вхождение основного запроса (саечка за неоптимальность )
нука расскажи, почему ты так решил?
Это то, что я выше предлагал.
ничего дельного ты тут не предложил
Обычно можно сделать так
это правильный ответ
но чтобы сохранить интригу, что изменится от того, если ЛИНК написать так(как у нас внегласно заведено)?
вместо ! напишем == 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();
Вы начитались "каверзных вопросов", где требуют знать все тонкости, как построитель и оптимизатор запросов внутри преобразует те или иные условия в фильтрующих делегатах?
потому что делегат в where выполняется для каждого вхождения
бред какой
весь этот LINQ конвертируется в один SQL-запрос
Вы начитались "каверзных вопросов", где требуют знать все тонкости, как построитель и оптимизатор запросов внутри преобразует те или иные условия в фильтрующих делегатах?
нет, я заметил, что один запрос как-то очень медленно обрабатывается
весь этот 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 |
Это то, что я выше предлагал.
А можно оптимальный код повторить?
Желательно чтобы можно было засунуть в Linqpad или netfiddle или на крайняк готовый проект для студии.
какую-нибудь внешнюю по отношению к запросу коллекцию, типа
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 Lite?
И интересно было бы посмотреть сгенерированный SQL.
Я не исключаю, что моя проблема существует только при работе с ораклом.
И интересно было бы посмотреть сгенерированный 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
Не, почему, когда-то писал и code first, но теперь снова букварь надо читать, чтобы вспомнить, что там и как. Так-то подключиться к БД через контекст и что-то там позапрашивать могу - много ума не надо.
это я заметил.
у меня в соседнем проекте тоже коллеги местами не знают, в какой момент происходит запрос к БД.
На работе ценится не зазубривание букварей, а умение решать неожиданно всплывающие проблемы типа таких, когда не понимаешь, почему и откуда. Вон, народ по 15 и более лет всё понять не может, почему им Студия не называет конкретную причину ошибки, которая может остоять от декларированной на несколько шагов.
Странно, жалко Оракле снёс, интересно что дуреет.
вангую, что Oracle.ManagedDataAccess.EntityFramework.dll
На работе ценится не зазубривание букварей
Никто этого и не требует
Но понимать, сколько и в какой момент выполнится запрос в БД это гораздо важнее экономии 40% кода посредством использования новых фич.
а то напишешь, оно даже компилируется, на тестовой системе летает, а потом в проде умирает.
смотришь, а у ребят джойны в оперативной памяти происходят.
что Oracle.ManagedDataAccess.EntityFramework.dll
всё может быть, что Oracle.*
Oracle.ManagedDataAccess.Core
На работе ценится не зазубривание букварейНикто этого и не требует
Но понимать, сколько и в какой момент выполнится запрос в БД это гораздо важнее экономии 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).
у нас тут на днях прислали coding standards
В принципе все норм, но какой-то старпер запретил использовать + для конкатенации строк. только StringBuilder. Пришлось объяснить, что оно не всегда нужно.
твоя простыня это же эквивалент ?
public Obj Obj = new Obj() {get;}
Не совсем. У вас нет модной ленивой инициализации.
public Obj Obj => _obj ??= new ();
Мало того, что при чтении этого ломается мозг, так тут еще и существует опасность забыть знак равно и получать каждый раз новый объект.
есть у кого-нибудь такие правила по оформлению кода, что типа в этом проекте запрещено
Ну так для этого и существуют правила, что то можно, что то нельзя. Кроме этого никто не запрещает правила менять.
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();
В моём-то примере наоборот упрощение.
Количество строк не всегда означает проще.
И не следует смотреть исключительно со своей колокольни. У каждого поселения она может быть своя.
Если в вашем поселении не могут выучить один новый оператор в год, это ваши проблемы. Особенно, когда этот оператор позволяет сократить некоторые конструкции в разы.
Как уже говорилось, многие паттерны-шматтерны возникли не из-за гениальности разных дядей Бобов и всяких банд четырёх, а из-за недостаточных возможностей языков и фреймворков той поры. Но в некоторых поселениях продолжают долбиться в замшелые книжонки и восхищаться ходящими по сцене старичками-инфоцыганами, льющими тонну воды на пару умных фраз.
Если в вашем поселении не могут выучить один новый оператор в год,
Мне уже надоело повторять - не смотрите на проблему исключительно с своей личной точки зрения.
Проблема то разве в этом?