【问题标题】:IQueryable Union, Unable to create a null constant value of type 'System.Collections.Generic.IEnumerable`1[[System.Int32IQueryable 联合,无法创建类型为“System.Collections.Generic.IEnumerable”的空常量值 1 [[System.Int32
【发布时间】:2016-07-12 14:50:17
【问题描述】:

我正在尝试使用联合实现对实体框架的 LINQ 查询。假设我有 2 个表:Users 和 TempUsers。我尝试:

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,文化=中性,PublicKeyToken=b77a5c561934e089]]'。 仅支持实体类型、枚举类型或原始类型 在这种情况下。

如果我删除 Roles = null,我会收到带有消息的 NotSupportedException:

类型“UserModel”出现在两个 单个 LINQ 中结构上不兼容的初始化 实体查询。一个类型可以在同一个地方的两个地方初始化 查询,但前提是在两个地方都设置了相同的属性并且 这些属性的设置顺序相同。

一个想法如何解决它?谢谢!

【问题讨论】:

  • 您是否尝试在第二个查询中删除Roles=null?我认为您不需要这样做,两个结果都是相同的类型
  • @octavioccl 我试过了。请查看更新后的问题。
  • 您是否尝试过在将它们合并到 UserModel 中之前?
  • @octavioccl 我想将不同表的结果合并到 1 个模型并不常见。
  • 您能否更新您的问题以显示 UserModel,尤其是角色类型

标签: c# .net sql-server entity-framework linq


【解决方案1】:

为避免这些异常,您可以在内存中调用 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&lt;TempUser&gt;,则此解决方案的缺点

【讨论】:

  • 是的,这确实有效。但是...我需要执行 sql 查询,而不是查询对象。这在大数据集上太慢了。
  • 是的,我猜是这样。您要投影的问题是 Role 是一种复杂类型,您不能为该属性分配默认值,除非它是第一个查询中的查询结果
  • 是的。对。匿名类型模型会有什么不同,你怎么看?
  • 我不这么认为。如果您尝试使用受支持的匿名类型,则必须使用相同的属性名称进行投影,但您的第二个查询也会发生同样的情况,因为您无法分配默认值
  • 有道理。请在您的回答中详细说明这一点。感谢您的帮助!
【解决方案2】:

第一个异常是无法避免的,但第二个异常可以通过以下技巧来避免。

由于 EF 抱怨您以不同的方式初始化同一个 type,因此您可以创建和初始化从原始类型派生的不同类型。

例如:

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)主表,然后进行条件连接,但我不确定它是否值得付出努力.更好地使用当前接受的答案中的一些混合方法。

【讨论】:

  • 我喜欢这个想法,但首先我认为他需要创建一个不同的层次结构,还应该在某个地方调用OfType 扩展方法。
  • @octavioccl 不,这个技巧只是为了投影。之前回答过类似的问题,只是没时间找。但现在我做到了,所以这真的是重复的 - 见stackoverflow.com/questions/37106147/…
  • 我明白你的意思,我不知道这一点;),但第二个例外呢?据我了解,这两个预测都应该初始化相同的属性。
  • @octavioccl 这就是诀窍:) 仅当它们是同一类型时。还有UserModel != TempUserModel。您只需要它就可以创建任意数量的派生类型,并以不同的方式调用它们。
  • @octavioccl 但该技巧仅适用于单个项目属性,不适用于集合:(
猜你喜欢
  • 1970-01-01
  • 2021-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多