LINQ to Entities 中仅支持无参数构造函数和初始化程序

Posted

技术标签:

【中文标题】LINQ to Entities 中仅支持无参数构造函数和初始化程序【英文标题】:Only parameterless constructors and initializers are supported in LINQ to Entities 【发布时间】:2011-04-04 00:28:56 【问题描述】:

我在这个 linq 表达式中有这个错误:

var naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments
                              (
                                  nalTmp.Dziecko.Imie,
                                  nalTmp.Dziecko.Nazwisko,
                                  nalTmp.Miesiace.Nazwa,
                                  nalTmp.Kwota,
                                  nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                                  nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                  nalTmp.DataRozliczenia,
                                  nalTmp.TerminPlatnosci
                              )).ToList();

知道如何解决这个问题吗?我尝试使用任何表达组合...:/

【问题讨论】:

你能显示支付类吗?或者至少在这里调用 ctor,特别是 8 参数 ctor 调用是否可以安全地换成 0 参数 ctor 调用并在对象上设置 8 个属性? 在为我正在“新建”的对象使用 Struct 而不是 Class 时,我遇到了同样的错误。 TL;DR 事情是 EF-LINQ 正在尝试将 select 语句发送给 EF 提供程序,即。将其转换为 SQL。要退出 EF-LINQ,请在创建任何对象之前调用 ToList()。 【参考方案1】:

很抱歉迟到了,但在找到this 后,我认为应该共享它,因为它是我能找到的最干净、最快且节省内存的实现。

适应你的例子,你会写:

public static IQueryable<Payments> ToPayments(this IQueryable<Naleznosci> source)

  Expression<Func<Naleznosci, Payments>> createPayments = naleznosci => new Payments
  
    Imie = source.Dziecko.Imie,
    Nazwisko = source.Dziecko.Nazwisko,
    Nazwa= source.Miesiace.Nazwa,
    Kwota = source.Kwota,
    NazwaRodzajuOplaty = source.RodzajeOplat.NazwaRodzajuOplaty,
    NazwaTypuOplaty = source.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
    DataRozliczenia = source.DataRozliczenia,
    TerminPlatnosci = source.TerminPlatnosci,
  ;

  return source.Select(createPayments);

这里的主要优势(正如 Damien Guard 在链接的 cmets 中指出的那样)是:

避免每次出现都使用初始化模式。 可以通过var foo = createPayments(bar);myIQueryable.ToPayments() 使用。

【讨论】:

【参考方案2】:

如果您仍想使用构造函数而不是属性进行初始化(有时这种行为是出于初始化目的而需要的),您将需要在某些时候使用 LINQ to Collections,因为 LINQ to SQL 不知道如何翻译SQL 查询的构造函数。您可以使用AsEnumerable() 来做到这一点。

所以你的代码应该是这样的:

var naleznosci = 
    db.Naleznosci
        .Where(nalTmp => nalTmp.idDziecko == idDziec)
        .Select(nalTmp => new
            
                DzieckoImie = nalTmp.Dziecko.Imie,
                DzieckoNazwisko = nalTmp.Dziecko.Nazwisko,
                MiesiaceNazwa = nalTmp.Miesiace.Nazwa
                Kwota = nalTmp.Kwota,
                NazwaRodzajuOplaty = nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                NazwaTypuOplaty = nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                DataRozliczenia = nalTmp.DataRozliczenia,
                TerminPlatnosci = nalTmp.TerminPlatnosci
            ) // Select necessary columns from DB into anonymous type.
        .AsEnumerable() // Here comes transfer to LINQ to Collections.
        .Select(nalImp => new Payments
            (
                nalTmp.DzieckoImie,
                nalTmp.DzieckoNazwisko,
                nalTmp.MiesiaceNazwa,
                nalTmp.Kwota,
                nalTmp.NazwaRodzajuOplaty,
                nalTmp.NazwaTypuOplaty,
                nalTmp.DataRozliczenia,
                nalTmp.TerminPlatnosci
            )) // Use constructor to create your models.
        .ToList();

【讨论】:

只是为了澄清为什么会这样,最初陈述的代码的问题是实体框架试图将构造函数调用与 LINQ 查询的其余部分一起传递给 SQL,当然还有SQL 没有办法去构造复杂的对象!通过插入 ToList() 调用,您可以将枚举从尚未执行的 SQL 查询移动到内存中的具体对象列表,然后您可以以任何您喜欢的方式对其进行操作。 不要使用ToX(),使用AsEnumerable() .ToList() // 这里是转移到 LINQ to Collections。是为我解决问题的行。 请注意,这将在 db 级别选择 所有 列,而通常它只会选择所需的列 不仅如此,您可能还会有多个枚举。我不喜欢这个解决方案。【参考方案3】:

只需在Select 语句之前使用ToList() .. 类似的东西:

myDbSet.Where(x => something that minimizes the number of items).ToList().Select(...)

实际的DbSet 还没有脱离数据库。 调用ToList() 后,您正在处理对象,然后您可以在查询中使用非默认构造函数。

在使用时间方面不是最有效的方式,但它是小型套装的一种选择。

【讨论】:

DbSet.ToList 会将您的整个表转储到内存中,并且 DbSet 并未“保存为查询,尚未完成”。调用 ToList 将立即执行查询。 你是对的!我不知道我在想什么.. 编辑:)【参考方案4】:
IQueryable<SqlResult> naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments
                              
                                  Imie = nalTmp.Dziecko.Imie,
                                  Nazwisko = nalTmp.Dziecko.Nazwisko,
                                  Nazwa= nalTmp.Miesiace.Nazwa,
                                  Kwota = nalTmp.Kwota,
                                  NazwaRodzajuOplaty =                          nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                              NazwaTypuOplaty = nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                              DataRozliczenia = nalTmp.DataRozliczenia,
                              TerminPlatnosci = nalTmp.TerminPlatnosci,
                          );
Repeater1.DataSource  = naleznosci.ToList(); 
Repeater1.DataBind();


public class SqlResult

        public string Imie  get; set; 
        public string Nazwisko  get; set; 
        ...

【讨论】:

【参考方案5】:

虽然回答晚了,但它仍然可以帮助陷入困境的人。由于 LINQ to 实体不支持无参数对象构造。但是,IEnumerable的投影方法。

所以在选择之前,只需使用以下代码将您的 IQueryable 转换为 IEnumerable

var result = myContext.SomeModelClass.AsEnumerable().Select(m => m.ToString());

它会正常工作的。但是,它当然会失去原生查询的优势。

【讨论】:

【参考方案6】:

我今天遇到了同样的问题,我的解决方案与 Yoda 列出的类似,但它只适用于流利的语法。

使我的解决方案适应您的代码: 我在对象类中添加了以下静态方法

    /// <summary>
    /// use this instead of a parameritized constructor when you need support
    /// for LINQ to entities (fluent syntax only)
    /// </summary>
    /// <returns></returns>
    public static Func<Naleznosci, Payments> Initializer()
    
        return n => new Payments
        
             Imie = n.Dziecko.Imie,
             Nazwisko = n.Dziecko.Nazwisko,
             Nazwa = n.Miesiace.Nazwa,
             Kwota = n.Kwota,
             NazwaRodzajuOplaty = n.RodzajeOplat.NazwaRodzajuOplaty,
             NazwaTypuOplaty = n.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
             DataRozliczenia = n.DataRozliczenia,
             TerminPlatnosc = n.TerminPlatnosci
        ;
    

然后将基本查询更新为以下内容:

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select new Payments.Initializer());

这在逻辑上等同于 James Manning 的解决方案,其优点是将成员初始化的膨胀推到类/数据传输对象

注意:最初我使用的名称比“Initializer”更具描述性 但是在查看了我的使用方式后,我发现“Initilizer”就足够了(至少对于我的目的而言)。

最后说明: 在提出这个解决方案之后,我最初认为共享相同的代码并将其调整为适用于查询语法会很简单。我不再相信是这样的。我认为,如果您希望能够使用这种类型的速记构造,您将需要一个用于上述每个(查询、流利)流利的方法,该方法可以存在于对象类本身中。

对于查询语法,需要扩展方法(或正在使用的基类之外的某些方法)。 (因为查询语法想要操作一个 IQueryable 而不是 T)

这是我用来最终使其适用于查询语法的示例。 (Yoda 已经确定了这一点,但我认为用法可能更清楚,因为我一开始没有得到它)​​

/// <summary>
/// use this instead of a parameritized constructor when you need support
/// for LINQ to entities (query syntax only)
/// </summary>
/// <returns></returns>
public static IQueryable<Payments> Initializer(this IQueryable<Naleznosci> source)

    return source.Select(
        n => new Payments
        
            Imie = n.Dziecko.Imie,
            Nazwisko = n.Dziecko.Nazwisko,
            Nazwa = n.Miesiace.Nazwa,
            Kwota = n.Kwota,
            NazwaRodzajuOplaty = n.RodzajeOplat.NazwaRodzajuOplaty,
            NazwaTypuOplaty = n.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
            DataRozliczenia = n.DataRozliczenia,
            TerminPlatnosc = n.TerminPlatnosci
    ;

及用法

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select nalTmp).Initializer().ToList();

【讨论】:

当我意识到我的初始答案没有很好地扩展时,添加了一个关于查询语法的完整部分。 @yoda 的答案在查询语法方面可能更好。【参考方案7】:

首先我会避免使用

的解决方案
from ....
select new Payments

  Imie = nalTmp.Dziecko.Imie,
  ....

这需要一个空的构造函数并忽略封装,因此您说 new Payments() 是没有任何数据的有效付款,但该对象必须至少具有一个值,并且可能具有其他必需字段,具体取决于您的域。

最好有一个必填字段的构造函数,但只带上需要的数据:

from ....
select new

  Imie = nalTmp.Dziecko.Imie,
  Nazwisko = nalTmp.Dziecko.Nazwisko
  ....

.ToList() // Here comes transfer to LINQ to Collections.
.Select(nalImp => new Payments
 (
  nalTmp.Imie,//assume this is a required field
  ...........
  )
  
     Nazwisko = nalTmp.Nazwisko //optional field
  )
.ToList();

【讨论】:

这是小恶。 我也喜欢这样的东西。我尝试使用元组,但元组没有无参数构造函数。我填充了一个匿名对象,然后选择元组。 一个拥抱封装和域【参考方案8】:

另外,如果你想使用带有多个对象的构造函数来初始化,如果 Linq 没有返回值,你可能会得到错误。

所以你可能想做这样的事情:

(from x in table_1
   join y in table_2
   on x.id equals y.id
   select new 
   val1 = x,
   val2 = y
)
.DefaultIfEmpty()
.ToList()
.Select(a => new Val_Constructor(a.val1 != null ? a.val1 : new Val_1_Constructor(),
                            a.val2 != null ? a.val2 : new Val_2_Constructor()))
.ToList();

【讨论】:

【参考方案9】:

除了前面提到的方法,你还可以把它解析成一个可枚举的集合,像这样:

(from x in table
....
).AsEnumerable()
.Select(x => ...)

这还有一个额外的好处,那就是在构建匿名对象时更轻松,如下所示:

 (from x in tableName
select x.obj)
.Where(x => x.id != null)
.AsEnumerable()
.Select(x => new 
   objectOne = new ObjectName(x.property1, x.property2),
   parentObj = x
)
.ToList();

但是请记住,将集合解析为 Enumerable 会将其拉入内存,因此可能会占用大量资源!此处应谨慎使用。

【讨论】:

【参考方案10】:

如果您像我一样不想为您正在构建的每个查询填充属性,那么还有另一种方法可以解决此问题。

var query = from orderDetail in context.OrderDetails
            join order in context.Orders on order.OrderId equals orderDetail.orderId
            select new  order, orderDetail ;

此时您有一个包含匿名对象的 IQueryable。如果你想用构造函数填充你的自定义对象,你可以简单地做这样的事情:

return query.ToList().Select(r => new OrderDetails(r.order, r.orderDetail));

现在您的自定义对象(将两个对象作为参数)可以根据需要填充您的属性。

【讨论】:

这对我有用,成为最干净的解决方案。那些建议消除构造函数并使用初始化器语法的人一定没有构造函数中的逻辑。那是我唯一一次依靠构造函数来填充对象的属性。谢谢分享。【参考方案11】:

我自己刚刚遇到这个错误,我想我会补充一点,如果Payment 类型是struct,你也会遇到同样的错误,因为struct 类型不支持无参数构造函数。

在这种情况下,将Payment 转换为类并使用对象初始化器语法将解决此问题。

【讨论】:

这解决了我的问题。实际上,在 LINQ-2-SQL 中支持使用结构选择器进行此查询,当您升级到 EntityFramework 时,这是一个问题。 我讨厌结构体。他们永远不会做我想做的事 在我的查询中创建了一个DateTime(这是一个结构),这会导致相同的错误。将它提取到本地变量为我修复了它。感谢您的结构提示。【参考方案12】:

您可以尝试做同样的事情,但使用扩展的方法。数据库使用的提供者是什么?

var naleznosci = db.Naleznosci
                          .Where<TSource>(nalTmp => nalTmp.idDziecko == idDziec)
                          .Select<TSource, TResult>(
                             delegate(TSource nalTmp)  return new Payments
                             (
                                 nalTmp.Dziecko.Imie,
                                 nalTmp.Dziecko.Nazwisko,
                                 nalTmp.Miesiace.Nazwa,
                                 nalTmp.Kwota,
                                 nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                                 nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                 nalTmp.DataRozliczenia,
                                 nalTmp.TerminPlatnosci
                             ); )
                          .ToList();

【讨论】:

【参考方案13】:

如果没有关于“付款”的更多信息,这并没有多大帮助,但假设您要创建一个付款对象并根据列值设置它的一些属性:

var naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments
                              
                                  Imie = nalTmp.Dziecko.Imie,
                                  Nazwisko = nalTmp.Dziecko.Nazwisko,
                                  Nazwa= nalTmp.Miesiace.Nazwa,
                                  Kwota = nalTmp.Kwota,
                                  NazwaRodzajuOplaty = nalTmp.RodzajeOplat.NazwaRodzajuOplaty,
                                  NazwaTypuOplaty = nalTmp.RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                  DataRozliczenia = nalTmp.DataRozliczenia,
                                  TerminPlatnosci = nalTmp.TerminPlatnosci,
                              ).ToList();

【讨论】:

这很好用,别忘了为类添加一个空的构造函数。 只是为了补充这个答案,你不能用结构来做到这一点,只有类 - 花了我一点时间才弄清楚! 是的,我认为 Tony 的答案比这个更好,因为它实际上解决了手头的直接问题,而这个通过改变 Payments 类的性质并可能阻止它不可变来规避问题. 这看起来很难看。 EF6 有什么更好的方法吗?【参考方案14】:

嗯,这样试试吧……

var naleznosci = (from nalTmp in db.Naleznosci
                              where nalTmp.idDziecko == idDziec
                              select new Payments()
                              
                                  Dziecko.Imie,
                                  Dziecko.Nazwisko,
                                  Miesiace.Nazwa,
                                  Kwota,
                                  RodzajeOplat.NazwaRodzajuOplaty,
                                  RodzajeOplat.TypyOplat.NazwaTypuOplaty,
                                  DataRozliczenia,
                                  TerminPlatnosci
                              ).ToList();

这将使用无参数构造函数新建您的 Payment 对象,然后初始化大括号 中列出的属性

【讨论】:

仅供参考,Payemnts 中的 () 是不需要的,所以它可以是 `select new Payments // 初始值 现在我遇到错误:无法使用集合初始化程序初始化类型“Payments”,因为它没有实现“System.Collections.IEnumerable” 正确 - 如果您正在创建一个匿名类型(而不是 Payments 类的实例),那么 Muad 的代码就可以了,因为要设置的属性将隐含地是正在读取的属性的名称。但是,由于它是一个“真正的”类,您需要指定将哪些属性设置为各种值。

以上是关于LINQ to Entities 中仅支持无参数构造函数和初始化程序的主要内容,如果未能解决你的问题,请参考以下文章

“LINQ to Entities 不支持 LINQ 表达式节点类型 'Invoke'” - 难倒!

错误,LINQ to Entities 不支持的方法

LINQ to Entities 不支持 LINQ 表达式节点类型“Invoke”

LINQ to Entities 异常中不支持指定的类型成员“日期”

LINQ to Entities 不支持指定的类型成员“日期”。 DbFunctions.TruncateTime()

如何修复 ApplicationUserManager 中的“LINQ to Entities 中不支持指定的类型成员‘UserId’”