我想要实现的目标:
目前我正在开发一个过滤器系统,它将根据用户输入对实体应用不同的过滤器。如果你只关注根实体或多对一关系,这是一项容易的任务。但是一旦你想对集合进行过滤,它就变得有点难以表达,也很难查询。
问题:
我想筛选根实体(Organization),还想筛选Organization.Contracts或Contract. Licenses等集合。我可以选择将选择添加到Include和ThenInclude子句(请参见我的示例)。但我只能添加固定的LINQ-Querys,而不能构建动态函数来选择正确的行。
例外:
System.ArgumentException: "Expression of type 'System.Func`2[ExpressionTreeTest.MinimalTest+Contract,System.Boolean]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[ExpressionTreeTest.MinimalTest+Contract,System.Boolean]]' of method 'System.Linq.IQueryable`1[ExpressionTreeTest.MinimalTest+Contract] Where[Contract](System.Linq.IQueryable`1[ExpressionTreeTest.MinimalTest+Contract], System.Linq.Expressions.Expression`1[System.Func`2[ExpressionTreeTest.MinimalTest+Contract,System.Boolean]])' Arg_ParamName_Name"
基于我看到的异常,实体框架核心想要一个系统.链接.表达式.表达式1[System.Func
2[表达式树测试.最小值测试+合同,系统.布尔值]]但是我提供了一个系统.函数'2[表达式树测试.最小值测试+合同,系统.布尔值]。只要我将其更改为**表达式〈函数〈合同,布尔值〉〉合同过滤器表达式= c =〉c.结束日期〉日期时间.现在.添加月份(11);**intellisense报告一个错误,即无法接受。
其他遮阳篷:
我发现了许多关于查询实体框架核心和构建动态查询的不同问题。基于此,我能够为根实体(组织)构建我的动态查询。但对于嵌套的ThenInclude列表查询,我找不到任何动态示例。
我为您提供的最小测试用例:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using System.ComponentModel.DataAnnotations;
using System.Linq.Expressions;
namespace ExpressionTreeTest
{
public class MinimalTest
{
public void RunTest()
{
AppDbContext dbContext = new AppDbContext();
dbContext.Database.EnsureDeleted();
dbContext.Database.EnsureCreated();
// Setup some test data to check the query results
SetupTestData(dbContext);
// Pre build query with entity framework, which is working as expected
// Expectation:
// Get all Organizations with the Name == NameOfCompany and
// Include all Organization.Contracts which are Created > ateTime.Now.AddMonths(12)
// ThenInclude all Contract.Licenses which are l.Articel.Name == "TestArticelName"
IQueryable<Organization> preBuildQuery = dbContext.Organizations.Where(o => o.Name == "NameOfCompany").
Include(c => c.Contracts.Where(c => c.Created > DateTime.Now.AddMonths(12))).
ThenInclude(l => l.Licenses.Where(l => l.Articel.Name == "TestArticelName"));
// This prints 1, which is the desired result
Console.WriteLine("Query result count: " + preBuildQuery.ToList().Count());
// This is the dynamic filter funtion for the Include-Part of the query
// This function gets accepted by Visual Studio but throws an error by Entity Framework
Func<Contract, bool> ContractFilterFunction = c => c.EndDate > DateTime.Now.AddMonths(11);
// Build the above query dynamically based on user input
IQueryable<Organization> dynamicQuery = dbContext.Organizations.Where(BuildWhereQuery()).
Include(c => c.Contracts.Where(ContractFilterFunction)).
ThenInclude(l => l.Licenses.Where(l => l.Articel.Name == "TestArticelName"));
// This is the line with the error you will find in the question
// If i remove the ContractFilterFunction and replace it with an inline lambda
// the query gets executed, but i am not able to dynamically set the query parameters.
Console.WriteLine("Query result count: " + dynamicQuery.ToList().Count());
}
/// <summary>
/// This method creates based on input a query with different types. In the future there
/// should be some select based on the binaryExpression to use (Equal, Greater, etc.)
/// At the moment this is static for testing purposes
/// </summary>
/// <returns>A Func<T,bool> to parse to a Linq-Entity-Framewor query</returns>
private Expression<Func<Organization, bool>> BuildWhereQuery()
{
ParameterExpression rootEntity = Expression.Parameter(typeof(Organization));
MemberExpression fieldExpression = Expression.PropertyOrField(rootEntity, "Name");
ConstantExpression valueToCompare = Expression.Constant("NameOfCompany");
var binaryExpression = Expression.Equal(fieldExpression, valueToCompare);
return Expression.Lambda<Func<Organization, bool>>(binaryExpression, rootEntity);
}
public class AppDbContext : DbContext
{
public DbSet<Organization> Organizations { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<Contract> Contracts { get; set; }
public DbSet<License> Licenses { get; set; }
public DbSet<Articel> Articels { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite(@"Data Source=mydb.db");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
}
}
public class BasicEntity
{
[Key]
public Guid Id { get; set; }
}
public class Organization : BasicEntity
{
public string Name { get; set; }
public List<Contract> Contracts { get; set; }
public List<Contact> Contacts { get; set; }
}
public class Articel : BasicEntity
{
public string Name { get; set; }
}
public class Contact : BasicEntity
{
public string Name { get; set; }
public Organization Organization { get; set; }
}
public class Contract : BasicEntity
{
public DateTime Created { get; set; }
public DateTime EndDate { get; set; }
public List<License> Licenses { get; set; }
public Organization Organization { get; set; }
}
public class License : BasicEntity
{
public string LicenseNumber { get; set; }
public Articel Articel { get; set; }
public Contract Contract { get; set; }
}
private static void SetupTestData(AppDbContext dbContext)
{
Organization org = new Organization
{
Name = "NameOfCompany",
};
dbContext.Add(org);
dbContext.Add(new Contact
{
Name = "Contact 1",
Organization = org
});
dbContext.Add(new Contact
{
Name = "Contact 2",
Organization = org
});
Articel articel = new Articel
{
Id = Guid.NewGuid(),
Name = "TestArticelName"
};
dbContext.Add(articel);
Contract contract = new Contract
{
Id = Guid.NewGuid(),
Created = DateTime.Now,
EndDate = DateTime.Now.AddMonths(12),
Organization = org
};
dbContext.Add(contract);
License license = new License
{
Id = Guid.NewGuid(),
LicenseNumber = "12345-12345",
Articel = articel,
Contract = contract
};
dbContext.Add(license);
dbContext.SaveChanges();
}
}
}
注:
如果你有任何额外的提示给我,甚至是一个变通或其他解决方案,我会非常高兴。这不是一个要求这样做,但这是我找到的唯一方法。
1条答案
按热度按时间dphi5xsq1#
解决问题的最简单方法是安装LINQKit-
LinqKit.Microsoft.EntityFrameworkCore
在
OnConfiguring
中添加WithExpressionExpanding
使动态过滤器功能为
Expression
,EF Core不会将Func<>
转换为SQL并使用LINQKit扩展Invoke
:型
LINQKit
扩展将扩展LambdaExpression
ContractFilterFunction
,并在EF Core的LINQ翻译器处理之前注入到最终表达式树中。