昨天我正在研究一个代码重构,遇到一个异常,我真的找不到太多的信息。情况就是这样。
我们有一对通过关系表具有多对多关系的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查询。我们在DTO中使用了很多LINQ投影。 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:在另一个有趣的转折中,如果不是选择进入MasterCodeD,而是选择匿名类型,也不会在ToList()就位时抛出异常。
答案 0 :(得分:10)
我认为你偶然发现了Entity Framework中的一个错误。 EF有一些逻辑可以选择适当的具体类型来实现集合。 HashSet<T>
是它的最爱之一。显然(我不能完全遵循EF的源代码),它为ICollections选择HashSet,为IEnumerable选择List。
看起来EF尝试使用接受IEqualityComparer<T>
的{{3}}来创建HashSet。 (这种情况发生在EF DelegateFactory
类,方法GetNewExpressionForCollectionType
中。)错误在于它使用了自己的ObjectReferenceEqualityComparer
。但那是IEqualityComparer<object>
,无法转换为IEqualityComparer<int>
。
一般来说,我认为最好不要在LINQ查询中使用ToList
,并在DTO类型的集合中使用IEnumerable
。因此,EF将有完全的自由选择合适的混凝土类型。