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

如何教EF识别我的方法(3.),就像我将其写出(1.)?

void Main()
{
    var date = DateTime.Now;

    // 1. works, returns IEnumerable<tblMitarbeiterPersonalkostenstelleHistories>
    tblMitarbeiters
    .Select(m => m.tblMitarbeiterPersonalkostenstelleHistories.Where(p => p.ZuordnungGültigAb <= date).OrderByDescending(p => p.ZuordnungGültigAb).FirstOrDefault())
    .Dump();

    // 2. works, returns one tblMitarbeiterPersonalkostenstelleHistories
    tblMitarbeiterPersonalkostenstelleHistories
    .GetValidItemForDate(p => p.ZuordnungGültigAb, date)
    .Dump();

    // 3. throws NotSupportedException
    tblMitarbeiters
    .Select(m => m.tblMitarbeiterPersonalkostenstelleHistories.GetValidItemForDate(p => p.ZuordnungGültigAb, date))
    .Dump();

    // 4. throws NotSupportedException
    tblMitarbeiters
    .Select(m => m.tblMitarbeiterPersonalkostenstelleHistories.AsQueryable().GetValidItemForDate(p => p.ZuordnungGültigAb, date))
    .Dump();
}


public static class QueryableExtensions
{
    public static T GetValidItemForDate<T>(this IQueryable<T> source, Expression<Func<T, DateTime>> selector, DateTime date)
    {
        var dateAccessor = Expression.Lambda<Func<T, DateTime>>(Expression.Constant(date), selector.Parameters);
        var lessThanOrEqual = Expression.LessThanOrEqual(selector.Body, dateAccessor.Body);
        var lambda = Expression.Lambda<Func<T, bool>>(lessThanOrEqual, selector.Parameters);
        return source.Where(lambda).OrderByDescending(selector).FirstOrDefault();
    }

    public static T GetValidItemForDate<T>(this IEnumerable<T> source, Func<T, DateTime> selector, DateTime date) =>
        source.Where(i => selector(i) <= date).OrderByDescending(selector).FirstOrDefault();
}

最佳答案

您可以在某种程度上使用LINQKit分解复杂的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; }
}


如果我正确理解了您的问题,那么在重要的地方应该与您的问题相同。

当通常使用LINQKit和LINQ时,您必须了解,在不使用存储过程的情况下重用查询代码时重用的唯一工具就是将表达式分解并缝合在一起。

您的实用程序方法将转换为以下内容:

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();
}


该方法的作用是动态创建一个表达式,其输入是返回IEnumerable<TItem>TITem。您会看到它与您提取的代码非常相似。注意事项:


源集合不是实用程序方法的参数,而是返回的表达式的参数。
您必须在您要“插入”此表达式的任何表达式上,从LinqKit调用Invoke()扩展方法。
如果结果中使用了任何Expand(),则应在结果上调用Invoke()。这将使LINQKit用正在调用的表达式替换对表达式树中Invoke()的调用。 (这不是100%必需的,但是由于某种原因扩展失败时,更容易修复错误。如果您在每个辅助方法中都不使用Expand(),则扩展过程中发生的任何错误都会在扩展,而不是实际包含有问题的代码的方法。)


然后,类似地使用它,再次使用Invoke()

var db = new EmployeeHistoryContext();

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

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


(我只针对一个空数据库测试了此代码,因为它不会使EntityFramework抛出NotSupportedException。)

在这里,您应该注意:


您要插入到您要传递到Select()的子表达式中的子表达式需要保存在本地变量中,LINQKit在扩展期间不支持方法调用。
您需要在链中的第一个AsExpandable()上调用IQueryable,这样LINQKit才能发挥其魔力。
您可能无法像问题中那样在表达式内使用扩展方法调用语法。
在展开之前必须确定所有子表达式。


这些限制源于您在做的并不是真正调用方法的事实。您是从一堆较小的表达式中构建一个巨大的表达式,但是结果表达式本身仍然必须是LINQ-to-Entities可以理解的东西。另一方面,输入必须是LINQKit可以理解的内容,并且只能处理localVariable.Invoke()形式的表达式。任何动态性都必须在此表达式树之外的代码中。基本上,它的作用与解决方案2相同,只是使用比通过程序构建表达式树更直观的语法。

最后但并非最不重要的一点:执行此操作时,请勿过分。发生任何错误时,复杂的EF查询实际上已经很难调试,因为您没有被告知问题出在哪里。如果查询是从整个代码库中零散地动态组装而成的,则调试一些错误(例如令人愉悦的“无法将类型X转换为类型Y”)将很容易成为噩梦。



(对于以后的问题:如果您从头开始编写代码示例,而不是使用实际代码库中的位,那么我通常认为这是一个好主意。它们可能是特定于域的,理解名称可能需要您使用一些上下文标识符在理想情况下应该是每个人都能理解的简单英语名称,我也许会说足够多的德语以参加其中的工作,但是“ Mitarbeiterpersonalkostenstellehistorie”很难记住,而且我还没有真正从事过什么工作您的项目足够长的时间,以熟悉其含义。)

关于c# - 在 Entity Framework 的Linq Select中使用自定义方法,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/35271132/

10-14 15:32
查看更多