【发布时间】:2015-10-14 11:33:05
【问题描述】:
在我的 C# 代码中,我有 2 个 WHERE 查询,我可以在 IQueryable 上调用这两个查询,并将整个事情编译成 SQL,并且这两个查询都有很多共同的逻辑。
我相信这不是这个类似问题的重复: Using Function in Select Clause of Entity Framework Query 因为在我的场景中,有问题的函数可以转换为 SQL - EF 只是没有意识到它可以这样做。
查询大概是:
public static IQueryable<Template> WhereIsOwnedByUser(this IQueryable<Template> set, User user)
{
return set.Where(temp =>
temp.Requests
.Where(req => req.WasSent)
.OrderByDescending(req => req.DueDate)
.Take(2)
.SelectMany(req => req.RequestRecipients.Select(reqRecip => reqRecip.Recipient.Id))
.Contains(user.Id));
}
与
public static IQueryable<Template> WhereIsOwnedByUser(this IQueryable<DataReturn> set, User user)
{
return set.Where(ret=>
ret.Entity.Id == user.Entity.Id
&&
ret.Request.Template.Requests
.Where(req => req.WasSent)
.OrderByDescending(req => req.DueDate)
.Take(2)
.SelectMany(req => req.RequestRecipients.Select(reqRecip => reqRecip.Recipient.Id))
.Contains(user.Id));
}
因此,“拥有模板”的基本 BusinessLogic 规则,然后是“如果公司匹配且拥有模板,则拥有 DataReturn”的推论
如您所见,仅考虑 C#,这些可以很容易地重构为:
private static bool UserOwnsTemplate(User user, Template temp)
{
return temp.Requests
.Where(req => req.WasSent)
.OrderByDescending(req => req.DueDate)
.Take(2)
.SelectMany(req => req.RequestRecipients.Select(reqRecip => reqRecip.Recipient.Id))
.Contains(user.Id);
}
public static IQueryable<Template> WhereIsOwnedByUser(this IQueryable<Template> set, User user)
{
return set.Where(temp => UserOwnsTemplate(user, temp));
}
public static IQueryable<DataReturn> WhereIsOwnedByUser(this IQueryable<DataReturn> set, User user)
{
return set.Where(
ret =>
ret.Entity.Id == user.Entity.Id
&&
UserOwnsTemplate(user, ret.Request.Template)
);
}
从而减少重复(耶!)
但是然后EF会抱怨它不知道如何处理UserOwnsTemplate,尽管它可以很好地处理SQL中的逻辑。
AFAICT 没有很好的方法来解决这个问题。我认为我的选择是:
- 将
UserOwnsTemplate转换为UDF,即数据库中定义的SQL 函数。- 但是我无法从 C# lamda 创建 UDF,我必须定义 SQL,这样会比较麻烦。
- 将
UserOwnsTemplate定义的Expression<Func<Template,bool>>分配为变量,然后使用Expression.AndAlso为DataReturn 版本手动构建相关的Expression<Func<DataReturn ,bool>>将两个“子句”粘合在一起。- 元编程。呃。我以前在另一个项目中做过这件事,做起来很卑鄙,维护起来简直是一场噩梦。
- 接受副本。
- 除非 SO 另有建议,否则可能会发生什么情况。 ;)
任何人都可以看到任何其他可用的选项吗?
我可以做些什么来强制 EF 将函数解析为 SQL 吗? (我想到了“inling”这个词,但我不是 100% 知道我认为我的意思是什么?)
谁能看到将 ret.Request.Template 转换为 IQueryable 的方法,以便我可以在其上调用其他 WhereIsOwnedBy 扩展方法?
还有其他建议吗?
【问题讨论】:
-
关于您的第二个要点,我认为 AndAlso 位不需要任何表达式树争论。您可以随时使用
set.Where(expression).Where(anotherExpression)。困难在于您的表达式依赖于用户,并且您希望将其应用到与 IQueryable 的根不同的路径(temp 与 ret.Request.Template)。如果不构建自己的表达式,我看不出如何解决这些问题。可能有另一种解决方法,但我希望它涉及到实质性地改变你正在做的查询。
标签: c# sql-server entity-framework function