【问题标题】:Using expression returning methods in EF [duplicate]在 EF 中使用表达式返回方法
【发布时间】:2019-06-01 15:11:31
【问题描述】:

我有一个表达式返回方法如:

    public Expression<Func<User, bool>> GetUserPredicate(int userID)
    {
        return u => u.ID == userID;
    }

(在原始代码中,这是一段更长、更复杂的逻辑,我想重用它,因此我将其提取到这样的方法中。)

我可以毫无问题地在用户查询中使用它:

    var user = dbContext.Users
        .Where(GetUserPredicate(1))
        .Single();

但我也想在查询 Posts 等其他实体时使用它:

    var post = dbContext.Posts
        .Where(p => p.ID == 1)
        .Where(p.User => GetUserPredicate(1))
        .Single();

但这不起作用。怎么实现?

【问题讨论】:

  • 编译器是怎么说的?
  • @CodeNotFound p 在上下文中不存在
  • 你应该使用.Where(p =&gt; GetUserPredicate(1)而不是.Where(p.User =&gt; GetUserPredicate(1))
  • 标记为“重复”的问题的答案允许您从Expression&lt;Func&lt;Post, User&gt;&gt;Expression&lt;Func&lt;User, bool&gt;&gt; 编写Expression&lt;Func&lt;Post, bool&gt;&gt;。另请参阅 LINQKitNeinLinq 和类似内容,它们解释并尝试更一般地解决表达式组合问题。
  • LinqKit 提供自定义扩展方法InvokeAsExpandable() 一起使用。所以用法是var userPredicate = GetUserPredicate(1); var post = dbContext.Posts.AsExpandable.Where(p =&gt; userPredicate.Invoke(p.User)。 Nether C# 和 BCL 提供开箱即用的此类功能。

标签: c# entity-framework


【解决方案1】:

您可以创建一个通用方法。问题是您需要访问不相关类的属性。但这对于泛型是不可能的,除非泛型类型约束使这些属性可用。思路:在接口中定义需要的属性:

public interface IUserProvider
{
    int UserID { get; set; }
}

public class User : IUserProvider ...
public class Post : IUserProvider ...

然后将方法定义为

public Expression<Func<TEntity, bool>> GetUserPredicate<TEntity>(int userID)
    where TEntity : IUserProvider
{
    return e => e.UserID == userID;
}

【讨论】:

  • 但是 Post 没有 UserID 属性,它有一个 User 导航属性,它有一个 ID 属性。其他实体也是如此。
  • 要么GetUserPredicate 总是返回相同的谓词,并且必须对可比较的属性进行操作,或者然后使用两种不同的方法GetUserPredicateFromUserGetUserPredicateFromPost
  • 为什么不添加UserID?您可以使用 EF 获得导航属性 + 外键。
【解决方案2】:

编辑:
我的解决方案使用表达式的编译函数。由于 EF 只查看表达式树并将其转换为 Sql,因此它无法处理已编译的函数,因为它们不是表达式树(请参阅下面 Filip Corades 的评论)。
如果您将我的代码用于其他可以与编译函数一起使用的东西,那么您应该没有任何问题。然而,对于这个问题,它不是正确的答案。

原答案:

您可以添加使用第一个方法的另一种方法。

public Expression<Func<Post, bool>> GetPostUserPredicate(int userID)
{
    Expression<Func<User, bool>> expr = GetUserPredicate(userID);
    Func<User, bool> func = expr.Compile();
    return post => func(post.User);
}

它的作用是编译用户表达式的函数并使用它使表达式适合帖子。你可以这样称呼它(根据你的代码调整):

var post = dbContext.Posts
        .Where(p => p.ID == 1)
        .Where(GetPostUserPredicate(1))
        .Single();

你也可以内联它而不是使用额外的函数,但对我来说这有点太乱了。无论如何我都会添加它:

var post = dbContext.Posts
    .Where(p => p.ID == 1)
    .Where(p => GetUserPredicate(1).Compile()(p.User))
    .Single();

如果我在编译表达式方面没有遗漏什么,这应该可以正常工作。
希望这会有所帮助,并让我知道它是否有效。

【讨论】:

  • 编译后的函数不会翻译成sql。
  • @FilipCordas 真的吗?我担心可能是这种情况。我不会删除它,直到我得到 OP 的回答以查看它是否有效(我显然没有他们的确切设置)。
  • 它说“LINQ to Entities 不支持 LINQ 表达式节点类型 'Invoke'。” :(没有其他方法可以让它工作吗?
  • 没有 Ef 做了什么,它需要 c# 表达式树并将其转换为 Sql,因为编译的函数不是表达式树,因此无法获取原始函数。
  • @FilipCordas 我猜应该以某种方式转换(转换)表达式而不调用编译?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-14
  • 2021-07-04
  • 2012-10-05
相关资源
最近更新 更多