LINQ查询中的函数:无法翻译

qmelpv7a  于 2023-07-31  发布在  其他
关注(0)|答案(2)|浏览(107)

我有一个数据库结构,以处理人员(学生,工作人员,...),可以分配到不同的机构,为不同的日期。现在有一个查询,以获得所有工作人员的信息,如果单身人士目前是“活跃”或不是(意味着他们是该机构的一部分,在特定的一天,并没有离开产假)。因为“isActive”这个东西相当复杂,而且我不仅在这个查询中需要它,在其他地方也需要它,所以我创建了这个方法:

public static Func<StaffInstitute, bool> StaffIsActive(DateTime referenceDate)
{
    return pe => (pe.EntryDate == null || pe.EntryDate <= referenceDate.Date) &&
        (pe.ExitDate == null || pe.ExitDate >= referenceDate.Date) &&
        (
            !pe.Staff.MaternityLeave ||
            (
                pe.Staff.MaternityLeave &&
                pe.Staff.MaternityLeaveFrom.HasValue &&
                pe.Staff.MaternityLeaveUntil.HasValue &&
                (pe.Staff.MaternityLeaveFrom.Value.Date > referenceDate.Date || pe.Staff.MaternityLeaveUntil.Value.Date < referenceDate.Date)
            ) ||
            (
                pe.Staff.MaternityLeave &&
                (pe.Staff.MaternityLeaveFrom.HasValue || pe.Staff.MaternityLeaveUntil.HasValue) &&
                (!pe.Staff.MaternityLeaveFrom.HasValue || pe.Staff.MaternityLeaveFrom.Value.Date >= referenceDate.Date) &&
                (!pe.Staff.MaternityLeaveUntil.HasValue || pe.Staff.MaternityLeaveUntil.Value.Date < referenceDate.Date)
            )
        );
}

字符串
这样做的目的是为了方便地重用它,以便在将来的任何查询中,我都能够找到IsActive值,而不必复制这17行。因此,我的查询将获得所有员工的列表,如下所示:

Func<StaffInstitute, bool> staffIsActiveFunction = StaffIsActive(referenceDate);
IQueryable<StaffListDTO> staff =
    from staffInstitute
    in _context.StaffInstitute.Include(s => s.Staff)
    select new StaffListDTO()
    {
        StaffId = staffInstitute.StaffId,
        Name = staffInstitute.Staff.Name,
        IsActive = staffIsActiveFunction.Invoke(staffInstitute),
    };


这实际上很好用,这是一个令人愉快的惊喜。这个列表有一个分页,因为它得到得到相当长,所以在最后这将被添加到查询:

var res = staff.Skip(0).Take(30).ToList();


不幸的是,员工列表中有一个针对IsActive值的筛选器。有时候,我只想查看当前处于活动状态的员工,所以我在分页之前添加了以下内容:

if (istAktiv.HasValue)
{
    staff = staff.Where(p => p.IsActive);
}


这就是它停止工作的地方,我得到以下错误:

The LINQ expression '...' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'


我也无计可施了。我不能在这一点上做.toList,因为这样查询将从数据库中获取所有条目,而不是仅30个。我能做些什么来使它工作?如果我把StaffIsActive的内容直接写入我的查询,它突然就起作用了,我只是不明白。为什么?为什么?
有什么办法吗?

wfypjpf4

wfypjpf41#

您的查询需要表达式扩展。它可以由不同的库完成,几乎完整的列表可以找到here。我选择LINQKit:
使用ExpandableAttribute定义存根函数:

[Expandable(nameof(StaffIsActiveImpl))]
public static bool StaffIsActive(StaffInstitute pe, DateTime referenceDate)
{
    throw new NotImplementedException();
}

字符串
定义返回Expression的私有实现。结果表达式的参数应模拟原始函数:

private static Expression<Func<StaffInstitute, DateTime, bool>> StaffIsActiveImpl()
{
    return (pe, referenceDate) => (pe.EntryDate == null || pe.EntryDate <= referenceDate.Date) &&
        (pe.ExitDate == null || pe.ExitDate >= referenceDate.Date) &&
        (
            !pe.Staff.MaternityLeave ||
            (
                pe.Staff.MaternityLeave &&
                pe.Staff.MaternityLeaveFrom.HasValue &&
                pe.Staff.MaternityLeaveUntil.HasValue &&
                (pe.Staff.MaternityLeaveFrom.Value.Date > referenceDate.Date || pe.Staff.MaternityLeaveUntil.Value.Date < referenceDate.Date)
            ) ||
            (
                pe.Staff.MaternityLeave &&
                (pe.Staff.MaternityLeaveFrom.HasValue || pe.Staff.MaternityLeaveUntil.HasValue) &&
                (!pe.Staff.MaternityLeaveFrom.HasValue || pe.Staff.MaternityLeaveFrom.Value.Date >= referenceDate.Date) &&
                (!pe.Staff.MaternityLeaveUntil.HasValue || pe.Staff.MaternityLeaveUntil.Value.Date < referenceDate.Date)
            )
        );
}


如果您使用EF Core,请在DbContextOptions构建期间激活LINQKit:

builder
    .UseSqlServer(connectionString)
    .WithExpressionExpanding(); // enabling LINQKit extension


对于EF6,使用AsExpandable()

IQueryable<StaffListDTO> staff =
    from staffInstitute in _context.StaffInstitute.AsExpandable() // not needed for EF Core
    select new StaffListDTO()
    {
        StaffId = staffInstitute.StaffId,
        Name = staffInstitute.Staff.Name,
        IsActive = StaffIsActive(staffInstitute, referenceDate),
    };

zaq34kh6

zaq34kh62#

我不确定你的EF版本,但从我最近在EF6的经验来看,即使是staffIsActiveFunction.Invoke(staffInstitute)也不应该工作。我想他们在新版本中修复了这个问题。
无论如何,在EF6中,我使用Expression<Func<T, bool>>来处理这种情况。

public static readonly Expression<Func<TEntity, bool>> IsSomethingExpression =
            e => !e.DeleteFlag && ... ; // boolean conditions

字符串
然后,我可以像这样在不同的地方应用相同的条件:

IQueryable<TEntity> basicQuery = DbContext.TEntity
                .Where(IsSomethingExpression);


IQueryable<T>有一个.Where(Expression<Func<T, bool>>)过载。
我想你可以:
1.重构StaffIsActive以返回Expression<Func<StaffInstitute, bool>>
1.将其用于IsActive.Where()
当我们编写LINQ时,EF需要将语句转换为SQL查询。如果语句太复杂或不受EF支持,它将抛出。因此,在EF查询中调用自定义函数时应始终小心。一个通用的解决方案是使用如上所述的Expression s。

相关问题