Я использую .NET Core 3.1, Microsoft.EntityFrameworkCore 3.1.9 и Npgsql 4.1.9. Я хотел бы повторно использовать функцию CompareBuilds
для сравнения 2 объектов Build
в операторах Where(...)
. Я хотел бы, чтобы функция была переведена в SQL.
Это функция, которая у меня есть в настоящее время:
public List<Ticket> GetTicketsForBuild(Build build)
{
Expression<Func<Build, Build, int>> CompareBuilds = (first, second) =>
// complex logic, simplified for the sake of example
first.Version == second.Version ? 0 : (first.Version < second.Version ? -1 : 1);
return _myDbContext.Ticket
.Where(x => CompareBuilds(x.BuildIntroducedIn, build) <= 0)
.Where(x => x.IsPublic ?
CompareBuilds(x.BuildResolvedIn, build) < 0 :
CompareBuilds(x.BuildResolvedIn, build) > 0)
.ToList();
}
Однако компилятор выдает мне следующую ошибку в первом предложении Where
:
CS0149: Method name expected
Как я могу повторно использовать одну и ту же функцию для сравнения двух Build
объектов внутри Where
предложений? Более того, эта функция должна возвращать int
, как показано в фрагменте кода.
А если объявить его просто Func
, то он будет разрешаться и на стороне клиента.
Хорошо, я понимаю, что компиляция Expression
препятствует его переводу в SQL. Однако я хотел бы, чтобы запрос выполнялся на уровне базы данных, а не на клиенте. Есть ли способ сделать то, что я хочу, без компиляции выражения, может быть, каким-то образом обернув его, используя скомпилированные запросы, ...?
Я делаю это в своей кодовой базе следующим образом dotnetfiddle.net/JIPXcw, но он не позволяет вам дополнительно изменять условие Where, как вы делаете с тернарным if или < 0
или > 0
.
Возможно, то, что вы хотите сделать, достижимо с помощью создания собственных деревьев выражений (learn.microsoft.com/en-us/dotnet/csharp/… ), но я не могу с этим помочь.
Точно, я видел много таких примеров. Я использую этот подход в своей кодовой базе, а также для предикатных предложений. Однако я еще не нашел пример того, как использовать результат Expression<Func<...>>
в предложении Where
(например, < 0
). Я посмотрю на создание собственного дерева выражений.
Вам нужно LINQKit
здесь. Если интересно могу показать образец.
@SvyatoslavDanyliv Да, пожалуйста, я был бы очень признателен за пример.
Сначала активируйте LINQKit. Его нужно просто настроить DbContextOptions
:
builder
.UseSqlServer(connectionString) // or any other provider
.WithExpressionExpanding(); // enabling LINQKit extension
Затем вы можете повторно использовать это выражение:
public List<Ticket> GetTicketsForBuild(Build build)
{
Expression<Func<Build, Build, int>> CompareBuilds = (first, second) =>
// complex logic, simplified for the sake of example
first.Version == second.Version ? 0 : (first.Version < second.Version ? -1 : 1);
return _myDbContext.Ticket
.Where(x => CompareBuilds.Invoke(x.BuildIntroducedIn, build) <= 0)
.Where(x => x.IsPublic ?
CompareBuilds.Invoke(x.BuildResolvedIn, build) < 0 :
CompareBuilds.Invoke(x.BuildResolvedIn, build) > 0)
.ToList();
}
Но обычно такие функции удобны в других запросах, и мы можем сделать это более общим способом:
public static class QueryHelper
{
[Expandable(nameof(CompareBuildsImpl))]
public static int CompareBuilds(Build first, Build second)
{
throw new InvalidOperationexception();
}
private static Expression<Func<Build, Build, int>> CompareBuildsimpl()
{
return (first, second) =>
// complex logic, simplified for the sake of example
first.Version == second.Version ? 0 : (first.Version < second.Version ? -1 : 1);
}
}
И повторно использовать в запросах:
public List<Ticket> GetTicketsForBuild(Build build)
{
return _myDbContext.Ticket
.Where(x => QueryHelper.CompareBuilds(x.BuildIntroducedIn, build) <= 0)
.Where(x => x.IsPublic ?
QueryHelper.CompareBuilds(x.BuildResolvedIn, build) < 0 :
QueryHelper.CompareBuilds(x.BuildResolvedIn, build) > 0)
.ToList();
}
Есть и другие библиотеки, которые делают то же самое, и я собрал некоторые из них здесь
Спасибо. Это именно то, что мне нужно. Всего одно небольшое замечание для всех будущих читателей. Если вы не хотите включать LinqKit глобально, вы можете включить его для каждого запроса с помощью _myDbContext.Ticket.AsExpandable()
.
Вы получаете эту ошибку, потому что Выражения не могут быть вызваны таким образом - если бы вы объявили
CompareBuilds
просто какFunc
, то вы могли бы назвать это какCompareBuilds(x.BuildIntroducedIn, build)
в своей лямбде, но сначала Выражение должно быть.Compile()
'd, и это, конечно, сделало бы сравнение разрешенным на стороне клиента.