调用ToList()时内部LINQ查询出现异常

12

昨天我在进行代码重构时遇到了一个异常,但我很难找到关于它的更多信息。以下是情况简述。

我们有一对EF实体通过关系表之间具有多对多的关系。涉及的对象看起来像这样,省略了不必要的部分。

public partial class MasterCode
{
    public int MasterCodeId { get; set; }
    ...

    public virtual ICollection<MasterCodeToSubCode> MasterCodeToSubCodes { get; set; }
}

public partial class MasterCodeToSubCodes
{
    public int MasterCodeToSubCodeId { get; set; }
    public int MasterCodeId { get; set; }
    public int SubCodeId { get; set; }
    ...
}

现在,我尝试对这些实体运行一个LINQ查询。我们使用很多LINQ投影到DTOs中。以下是DTO和查询。masterCodeId是传递的参数。

public class MasterCodeDto
{
    public int MasterCodeId { get; set; }
    ...

    public ICollection<int> SubCodeIds { get; set; }
}

(from m in MasterCodes
where m.MasterCodeId == masterCodeId
select new MasterCodeDto
{
    ...
    SubCodeIds = (from s in m.MasterCodeToSubCodes
                  select s.SubCodeId).ToList(),
    ...
}).SingleOrDefaultAsync();
内部查询抛出以下异常
Expression of type 'System.Data.Entity.Infrastructure.ObjectReferenceEqualityComparer' cannot be used for constructor parameter of type 'System.Collections.Generic.IEqualityComparer`1[System.Int32]'

我们之前在代码的其他地方做过这样的嵌套查询,并且没有遇到任何问题。不同之处在于,这次我们不是新建一个对象并将其投影到其中,而是返回了一组我们想要放入列表中的整数。

我通过将MasterCodeDto上的ICollection更改为IEnumerable并删除ToList()来找到了一种解决方法,但我从未能够找出为什么不能只选择id并将其作为列表返回。

有人对这个问题有任何见解吗?通常,当它不是内部查询的一部分时,只返回一个id字段并调用ToList()是可以正常工作的。我是否忽略了防止发生这种操作的内部查询限制?

谢谢。

编辑:为了举例说明这个模式在哪里起作用,我将展示一个可以工作的查询的例子。

 (from p in Persons
 where p.PersonId == personId
 select new PersonDto
 {
     ...
     ContactInformation = (from pc in p.PersonContacts
                           select new ContactInformationDto
                           {
                               ContactInformationId = pc.PatientContactId,
                               ...
                           }).ToList(),
     ...
  }).SingleOrDefaultAsync();

在这个例子中,我们选择一个新的Dto而不仅仅是选择单个值,它可以正常工作。问题似乎源于只选择一个单一的值。

编辑2:另一个有趣的变化是,如果我选择匿名类型而不是选择MasterCodeDto,那么在使用ToList()时也不会抛出异常。


1
你真的确定是 ToList() 导致了错误吗? - flindeberg
如果我移除ToList(),异常就会消失,但是我会得到IEnumerable<int>和ICollection<int>之间的转换错误。如果我将ICollection<int>更改为IEnumerable<int>,那么这个错误就会消失。 - Bradford Dillon
我挂断了电话,但整个查询都包装在SingleOrDefaultAsync()中以在数据库中运行查询。 - Bradford Dillon
如果您在选择之前先调用第一个 .ToList,它是否有效? - gilles emmanuel
实体框架 6.1.1 - Bradford Dillon
显示剩余7条评论
1个回答

17

我认为你在Entity Framework中遇到了一个bug。EF有一些逻辑来选择适当的具体类型来实现集合。HashSet<T>是其中之一。显然(我无法完全跟踪EF的源代码),它选择HashSet用于ICollections和List用于IEnumerable。

看起来EF尝试通过使用接受IEqualityComparer<T>构造函数来创建一个HashSet。(这发生在EF的DelegateFactory类中,方法GetNewExpressionForCollectionType。)错误在于它使用自己的ObjectReferenceEqualityComparer。但那是一个IEqualityComparer<object>,不能转换为IEqualityComparer<int>

总的来说,我认为最好的做法是不在LINQ查询中使用ToList并在DTO类型的集合中使用IEnumerable。这样,EF将有完全自由地选择适当的具体类型。


这与我在深入研究EF代码时得出的结论相似。我认为,现在转移到IEnumerable可能是避免将来出现此问题的最佳方法。感谢您确认了我所感觉到的问题。 - Bradford Dillon
谢谢。但我需要ICollection,因为我想使用它的方法,如Add、Remove等。还有其他希望吗? - Wahid Bitar
1
值得注意的是,虽然这个 bug 在 EF 6.2 中似乎仍然存在,但是 IList<T> 是 ICollection<T> 的替代方案,如果您不想回退到 IEnumerable<T> 上。 - Stefan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接