我正在尝试使用联合实现对实体框架的 LINQ 查询。假设我有 2 个表:用户和临时用户。我尝试:
var users = context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
})
.Union(context.TempUsers
.Select(u => new UserModel
{
Name = u.Name,
Roles = null
}))
.ToList();
型号类别:
public class UserModel
{
public string Name { get; set; }
public IEnumerable<int> Roles { get; set; }
}
我收到消息NotSupportedException
:
无法创建类型的空常量值 'System.Collections.Generic.IEnumerable'1[[System.Int32, mscorlib, 版本=4.0.0.0,区域性=中性,公钥令牌=b77a5c561934e089]]'。 仅支持实体类型、枚举类型或基元类型 在这种情况下。
如果我删除Roles = null
,我会收到NotSupportedException
消息:
类型"用户模型"出现在两个 单个 LINQ to 中结构不兼容的初始化 实体查询。一个类型可以在同一位置的两个位置初始化 查询,但前提是在两个位置和 这些属性按相同的顺序设置。
一个想法如何解决它?谢谢!
若要避免这些异常,可以在内存中调用AsEnumerable
扩展方法进行联合:
var users = context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
})
.AsEnumerable() //Add this
.Union(context.TempUsers
.Select(u => new UserModel
{
Name = u.Name,
}))
.ToList();
问题是您正在尝试使用具有复杂类型作为属性的 DTO 进行投影。这不允许你分配默认值,因为当你尝试投影查询时,EF 仅支持实体类型、枚举类型或基元类型。也不值得使用匿名类型,因为两个投影都必须基于具有相同属性名称的匿名类型,因此,最后是相同的问题。
更新
现在我看到了使用继承的解决方案,我想到了这个想法。
public TempUser
{
public string Name { get; set; }
}
public UserModel:TempUser
{
public IEnumerable<int> Roles { get; set; }
}
然后,要尝试在服务器端执行联合,您还需要调用OfType
扩展方法
var query=context.TempUsers
.Select(u => new TempUser
{
Name = u.Name,
})
.Union(context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
}).OfType<TempUser>())
.ToList();
如果结果将是List<TempUser>
,则此解决方案的缺点
第一个例外是无法避免的,但第二个例外可以通过以下技巧。
由于 EF 抱怨以不同的方式初始化同一类型,因此可以创建和初始化从原始类型派生的不同类型。
例如:
public class TempUserModel : UserModel { }
然后
var users = context.Users
.Select(u => new UserModel
{
Name = u.Name,
Roles = u.Roles.Select(r => r.Id)
})
.Union(context.TempUsers
.Select(u => new TempUserModel
{
Name = u.Name
}))
.ToList();
更新:上述技巧适用于单值属性,但不适用于集合类型属性。更重要的是,事实证明根本不支持带有嵌套子查询的Concat
/Union
。因此,在SQL中执行此操作的唯一方法是首先执行Concat
(顺便说一句,相当于SQL UNION ALL
)主表,然后进行条件连接,但我不确定这是否值得付出努力。最好使用当前接受的答案中的一些混合方法。