C# 在具有实体框架的Linq Select中使用自定义方法

C# 在具有实体框架的Linq Select中使用自定义方法,c#,entity-framework,linq,extension-methods,C#,Entity Framework,Linq,Extension Methods,我试图在与EF一起使用的Linq Select中使用自定义函数。 我想将tblMitarbeiter的每个项目投影到一个tblMitarbeiterPersonalkostenstelleHistories上,该历史在给定日期有效。 这应该通过扩展方法来完成,这样我就不会重复我自己;) 我只能在直接在DbSet上使用它,而不能在Select中使用它 我如何教EF识别我的方法(3),就好像我要把它写出来一样(1) void Main() { var date=DateTime.Now; //1.工

我试图在与EF一起使用的Linq Select中使用自定义函数。 我想将tblMitarbeiter的每个项目投影到一个tblMitarbeiterPersonalkostenstelleHistories上,该历史在给定日期有效。 这应该通过扩展方法来完成,这样我就不会重复我自己;) 我只能在直接在DbSet上使用它,而不能在Select中使用它

我如何教EF识别我的方法(3),就好像我要把它写出来一样(1)

void Main()
{
var date=DateTime.Now;
//1.工作、回报是不可数的
tblMitarbeiters
.Select(m=>m.tblMitarbeiterPersonalkostenstelleHistories.Where(p=>p.zuordunggültigAb p.zuordunggültigAb).FirstOrDefault())
.Dump();
//2.工作,返回一个tblMitarbeiterPersonalkostenstelleHistories
TBLMitarbeiterPersonalKostenstelle历史
.GetValidItemForDate(p=>p.zuordunggültigAb,日期)
.Dump();
//3.抛出NotSupportedException
tblMitarbeiters
.Select(m=>m.tblMitarbeiterPersonalkostenstelleHistories.GetValidItemForDate(p=>p.zuordunggültigAb,date))
.Dump();
//4.抛出NotSupportedException
tblMitarbeiters
.Select(m=>m.tblMitarbeiterPersonalkostenstelleHistories.AsQueryable().GetValidItemForDate(p=>p.zuordunggültigAb,date))
.Dump();
}
公共静态类QueryableExtensions
{
公共静态T GetValidItemForDate(此IQueryable源、表达式选择器、日期时间)
{
var dateAccessor=Expression.Lambda(Expression.Constant(日期),selector.Parameters);
var lessThanOrEqual=Expression.lessThanOrEqual(selector.Body,dateAccessor.Body);
var lambda=Expression.lambda(lessThanOrEqual,selector.Parameters);
返回source.Where(lambda).OrderByDescending(选择器).FirstOrDefault();
}
公共静态T GetValidItemForDate(此IEnumerable源、函数选择器、日期时间日期)=>

source.Where(i=>selector(i)在某种程度上,您可以使用拆分复杂的LINQ表达式。如果您不介意的话,我将使用一个不太日耳曼的示例模型:

public class Employee
{
    public long Id { get; set; }
    public virtual ICollection<EmployeeHistoryRecord> HistoryRecords { get; set; } 
}

public class EmployeeHistoryRecord
{
    public long Id { get; set; }
    public DateTime ValidFrom { get; set; }
    public long EmployeeId { get; set; }
    public Employee Employee { get; set; }
}
(我只针对空数据库测试了这段代码,因为它不会使EntityFramework抛出
NotSupportedException

在此,您应该注意:

  • 插入到传递到
    Select()
    的子表达式中的子表达式需要保存在局部变量中,LINQKit不支持扩展期间的方法调用
  • 您需要在链中的第一个
    IQueryable
    上调用
    AsExpandable()
    ,这样LINQKit就可以发挥它的魔力了
  • 您可能无法像问题中那样在表达式中使用扩展方法调用语法
  • 在展开之前,必须确定所有子表达式
这些限制源于这样一个事实,即您所做的并不是真正调用方法。您正在从一堆较小的表达式中构建一个巨大的表达式,但结果表达式本身必须是LINQ to Entities能够理解的内容。另一方面,输入必须是LINQKit能够理解的内容,并且y处理形式为
localVariable.Invoke()
的表达式。任何动态都必须位于此表达式树之外的代码中。基本上,它的作用与解决方案2相同,只是使用了比以编程方式构建表达式树更直观的语法

最后,但并非最不重要的一点是:在执行此操作时,不要过火。当出现任何错误时,复杂的EF查询已经很难调试,因为您不知道代码中的问题在哪里。如果查询是从整个代码库中的位和块动态组装的,则调试一些错误(如“无法将类型
X
转换为类型
Y
”)很容易成为噩梦



(对于未来的问题:我认为如果你从头开始制作代码样本,而不是使用实际代码库中的位,通常是个好主意。它们可能过于特定于域,理解名称可能需要一些你想当然的上下文。标识符最好是每个人都能理解的简单英文名称。我可以你会说足够多的德语来面试一份it工作,但是“Mitarbeiterpersonalkostenstellehistorie”很难在我的脑海中出现,也很难在我还没有为你的项目工作足够长的时间来熟悉它的含义时进行解释。)

健壮的方式呼叫
ToList()
在您的
之前,请选择带有自定义方法的
。它可以工作,但我不推荐。我希望在服务器上,而不是在内存中使用它。
ToList()
可能需要一个Include(性能差)或大量延迟加载(性能更差)。好吧,有一些可以在IQueryable(数据库端)中调用,但如果您想要更多,则应使用存储过程或将其加载到内存中。我的方法无法分解为EF(1.)识别的简单linq函数,因此不需要SqlFunctions。
public class Employee
{
    public long Id { get; set; }
    public virtual ICollection<EmployeeHistoryRecord> HistoryRecords { get; set; } 
}

public class EmployeeHistoryRecord
{
    public long Id { get; set; }
    public DateTime ValidFrom { get; set; }
    public long EmployeeId { get; set; }
    public Employee Employee { get; set; }
}
private static Expression<Func<IEnumerable<TItem>, TItem>> GetValidItemForDate<TItem>(
            Expression<Func<TItem, DateTime>> dateSelector, 
            DateTime date)
{
    return Linq.Expr((IEnumerable<TItem> items) =>
        items.Where(it => dateSelector.Invoke(it) <= date)
            .OrderByDescending(it => dateSelector.Invoke(it))
            .FirstOrDefault())
        .Expand();
}
var db = new EmployeeHistoryContext();

var getValidItemForDate = GetValidItemForDate((EmployeeHistoryRecord cab) => cab.ValidFrom, DateTime.Now);

var historyRecords = db.Employees.AsExpandable().Select(emp => getValidItemForDate.Invoke(emp.HistoryRecords));