【问题标题】:IReadOnlyCollection part of a LINQ queryLINQ 查询的 IReadOnlyCollection 部分
【发布时间】:2013-11-07 17:27:31
【问题描述】:

我的模型(使用 Entity Framework 生成,请记住我已经为示例进行了简化)如下所示:

public class Person
{
    public string Name { get; set; }
    protected virtual ICollection<PersonHouseMapping> PersonHouseMappings { get; set; }

    public virtual IReadOnlyCollection<House> GetHouses(Func<House, bool> where = null)
    {
        var houses = PersonHouseMappings.Select(x => x.House);

        if (where != null)
            houses = houses.Where(where);

        return houses.ToList().AsReadOnly();
    }

    public void AddHouse(House house, DateTime movingDate)
    {
        PersonHouseMappings.Add(new PersonHouseMapping
        {
            Person = this,
            House = house,
            MovingDate = movingDate
        });
    }
}

public class House
{
    public int Number { get; set; }
    protected virtual ICollection<PersonHouseMapping> PersonHouseMappings { get; set; }
}

internal class PersonHouseMapping
{
    public virtual Person Person { get; set; }
    public virtual House House { get; set; }
    public DateTime MovingDate { get; set; }
}

Person 和 Houses 之间存在 N:M 关系,由 PersonH​​ouseMapping 实体表示。通常 EF 会用一个导航属性来表示它,但是因为除了关系的每个实体部分的 PK 之外,PersonH​​ouseMapping 还有一个额外的字段,它不能这样做。

为了从我的库的用户中抽象出 PersonH​​ouseMapping,因为它不像具有导航属性那样干净,我已经使 ICollection 受到保护,并通过可以在 Person 类中看到的方法公开查询/添加功能。我不希望用户修改返回的集合,因为他们可能认为在“GetHouses”的结果中添加一个新的房子会被保存到数据库中,但事实并非如此。为了强制他们调用“AddHouse”,我将返回类型设为 IReadOnlyCollection。

问题是,当我想为 Person 构造一个包含与其 Houses 相关的条件的查询时,LINQ 无法将表达式转换为有效的 Linq to Entities 之一。假设我们要查询所有拥有 1 号房屋的人:

dbContext.Persons.Where(p => p.GetHouses(h => h.Number == 1).Any()).ToList();

这不起作用,并抛出异常:

LINQ to Entities does not recognize the method 'System.Collections.Generic.IReadOnlyCollection`1[Namespace.House] GetHouses(System.Func`2[Namespace.House,System.Boolean])' method, and this method cannot be translated into a store expression.

这是有道理的。但是我怎样才能在保留执行这些查询的能力的同时完成我想要的呢?有没有像 IEnumerable 这样不能实例化为 List 的东西?

编辑:我认为通过返回 IEnumerable 而不是 IReadOnlyCollection 查询会起作用(尽管它不能解决我的问题),但事实并非如此。为什么无法创建表达式?

编辑 2: 返回 IEnumerable 也不起作用的原因是问题不在于类型,问题在于在 LINQ 表达式的中间有一个方法调用。它需要直接翻译成商店查询,所以没有方法调用:(

非常感谢

【问题讨论】:

  • 看起来我们需要一些自定义表达式树。
  • 我认为您不能在 EF 查询中调用 CLR 对象上的任意方法,因为查询会被转换为在数据库引擎中执行的 SQL。

标签: c# linq entity-framework


【解决方案1】:

唯一的办法就是公开映射集合:

public virtual ICollection<PersonHouseMapping> PersonHouseMappings { get; set; }

并重写查询:

dbContext.Persons.Where(p => p.PersonHouseMappings.Any(m => m.House.Number == 1)).ToList();

EF 无法解析您的方法并从中构建表达式树的一部分。
实际上,我在您的查询中看不到任何优点。

【讨论】:

  • 这不符合 To abstract PersonH​​ouseMapping from the users of my library, 因为它不像 OP 的导航属性要求那样干净
  • @ken2k:在使用 EF(和任何其他 OR/M)时,您应该了解,可能存在任何限制。如果有人将 EDM 作为公共 API 的一部分公开,那么他会向该 API 的用户公开相同的限制。如果这些限制是不可接受的,那么不要这样做,在 EDM 之上制作一个领域模型层并将其公开。
  • 你的建议显然会解决问题,这是 EF 默认给你的。但我认为用户不必创建任何“映射”实体并处理它会更好;对于读取查询,可能没有那么大的区别,但对于添加或删除,只调用方法要好得多。
  • 顺便说一下,示例查询只是说明问题的一个例子;实际上,有一个带有 GetMany 调用的 PersonRepository,“where 子句”将在其中。
  • @Dennis 当然有限制。我的观点是 OP 已经知道他可以公开集合以使查询有效(这是默认的 EF 行为)。我将其问题理解为“我该如何公开收藏?”。答案是“你不能”或“在 EF 之上构建自己的模型”
猜你喜欢
  • 1970-01-01
  • 2014-05-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-12-12
  • 1970-01-01
相关资源
最近更新 更多