Solo los constructores e inicializadores sin parámetros son compatibles con LINQ to Entities

132

Tengo este error en esta expresión de 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();

¿Alguna idea de cómo resolver este problema? Intento con cualquier combinación de expresión ...: /

netmajor
fuente
1
puedes mostrar la clase de pagos? o al menos al ctor que se llama aquí, y específicamente si esa llamada de 8 parámetros puede cambiarse de forma segura por una llamada de 0 parámetros y establecer 8 propiedades en el objeto?
James Manning el
23
Obtuve este mismo error al usar un Struct en lugar de una Clase para el objeto que estaba "renovando".
HuckIt
3
TL; DR es que EF-LINQ está tratando de enviar la declaración select al proveedor de EF, es decir. conviértalo en SQL. Para salir de EF-LINQ, llame a ToList () antes de cualquier creación de objetos.

Respuestas:

127

sin más información sobre 'Pagos', esto no ayuda mucho, pero suponiendo que desea crear un objeto de Pagos y establecer algunas de sus propiedades en función de los valores de las columnas:

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();
James Manning
fuente
10
Esto funciona muy bien, no olvides agregar un constructor vacío para la clase.
live-love
58
Solo para agregar a esta respuesta, no puedes hacer esto con Structs, solo con Clases, ¡me llevó un poco entenderlo!
naspinski
44
Sí, creo que la respuesta de Tony es mejor que esta porque en realidad resuelve el problema inmediato en cuestión, mientras que esta evita el problema cambiando la naturaleza de la clase de Pagos y posiblemente evitando que sea inmutable.
Stephen Holt
esto se ve feo af. ¿Alguna mejor manera con EF6?
Toolkit
115

Si aún desea utilizar su constructor para la inicialización y no las propiedades (a veces, este comportamiento es deseable para fines de inicialización), enumere la consulta llamando a ToList()o ToArray(), y luego use Select(…). Por lo tanto, usará LINQ to Collections y esa limitación de no poder llamar al constructor con parámetros enSelect(…) desaparecerá.

Entonces su código debería verse así:

var naleznosci = db.Naleznosci
                          .Where(nalTmp => nalTmp.idDziecko == idDziec)
                          .ToList() // Here comes transfer to LINQ to Collections.
                          .Select(nalImp => 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();
Tony
fuente
21
Solo para aclarar por qué esto funciona, el problema con el código originalmente establecido es que Entity Framework intenta pasar la llamada del constructor al SQL junto con el resto de la consulta LINQ, y, por supuesto, no hay forma de que SQL construya objetos complejos! Al insertar la llamada ToList (), mueve lo enumerable de una consulta SQL aún no ejecutada a una lista concreta de objetos en la memoria, que luego puede manipular de la forma que desee.
Stephen Holt
19
No uses ToX()para esto, usa AsEnumerable().
Rawling
1
.ToList () // Aquí viene la transferencia a LINQ to Collections. es la línea que resuelve el problema para mí.
Ram
15
Tenga en cuenta que esto seleccionará todas las columnas en el nivel de base de datos, donde normalmente solo seleccionará las columnas requeridas
Hugh Jeffner
44
No solo eso, sino que probablemente tendrás múltiples enumeraciones. No me gusta esta solución
Bluebaron
47

Habiendo encontrado este error yo mismo, pensé que agregaría que si el Paymenttipo es a struct, también encontrarías el mismo error porquestruct tipos no admiten constructores sin parámetros.

En ese caso, la conversión Paymenta una clase y el uso de la sintaxis del inicializador del objeto resolverán el problema.

Gene C
fuente
Esto resuelve el problema de mí. En realidad, esta consulta con el selector de estructura es compatible con LINQ-2-SQL y es un problema cuando se actualiza a EntityFramework.
Tomas Kubes
Odio las estructuras. Nunca terminan haciendo lo que quiero
Simon_Weaver
Creé un DateTime(que es una estructura) dentro de mi consulta, que produce el mismo error. extraerlo a una variable local lo arregló para mí. Gracias por la sugerencia de estructura.
LuckyLikey
20

Si eres como yo y no quieres tener que llenar tus propiedades para cada consulta que estás creando, hay otra forma de resolver este problema.

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

En este punto, tiene un IQueryable que contiene un objeto anónimo. Si desea llenar su objeto personalizado con un constructor, simplemente puede hacer algo como esto:

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

Ahora su objeto personalizado (que toma dos objetos como parámetro) puede llenar sus propiedades según sea necesario.

Justin Helgerson
fuente
Esto funcionó para mí y llegó a ser la solución más limpia. Aquellos que han sugerido eliminar el constructor y usar la sintaxis del inicializador no deben haber tenido lógica dentro del constructor. Esa es la única vez que me apoyo en los constructores para completar las propiedades de un objeto. Gracias por compartir.
Bonez024
9

Primero evitaría la solución con

from ....
select new Payments
{
  Imie = nalTmp.Dziecko.Imie,
  ....
}

Esto requiere un constructor vacío e ignora la encapsulación, por lo que está diciendo que Payments () es un pago válido sin ningún dato, sino que el objeto debe tener al menos un valor y probablemente otros campos obligatorios dependiendo de su dominio.

Es mejor tener un constructor para los campos obligatorios pero solo traer los datos necesarios:

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();
Eugen
fuente
Este es el mal menor.
Chalky
También prefiero algo como esto. Intenté usar Tuple pero Tuple no tiene un parámetro menos constructor. Rellené un objeto anónimo y luego seleccioné Tupla.
Tchaps
uno para abrazar la encapsulación y el dominio
inrandomwetrust
2

Puede intentar hacer lo mismo, pero utilizando los métodos de extensión. ¿Cuál es el proveedor del uso de la base de datos?

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();
Sr. Tarakanoff
fuente
2

Sólo ToList()el DbSetantes de la Selectdeclaración .. el actual DbSetse guarda como una consulta, no se ha cumplido todavía. Después de llamar ToList(), estás jugando con objetos, y luego puedes usar un constructor no predeterminado en la consulta.

No es la forma más eficiente en cuanto al tiempo de uso, pero es una opción en conjuntos pequeños.

eiran
fuente
1

sí, inténtalo así ...

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();

esto renovará su objeto de pago utilizando un constructor sin parámetros y luego inicializará las propiedades que se enumeran dentro de las llaves { }

Muad'Dib
fuente
3
Para su información, ()en Payemnts no es necesario, por lo que puede ser `seleccione nuevos pagos {// valores de inicio}
PostMan
ahora tengo un error: no se puede inicializar el tipo 'Pagos' con un inicializador de cobro porque no implementa 'System.Collections.IEnumerable'
netmajor
correcto: si estuviera creando un tipo anon (en lugar de una instancia de la clase Payments), el código de Muad estaría bien ya que las propiedades que se establecerían serían implícitamente los nombres de las propiedades de las que se está leyendo. Sin embargo, dado que es una clase 'real', necesitaría especificar qué propiedades establecer en los distintos valores.
James Manning
1

Además de los métodos mencionados anteriormente, también puede analizarlo como una colección Enumerable, de esta manera:

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

Esto también tiene el beneficio adicional de hacer la vida más fácil al construir un objeto anónimo, como este:

 (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();

Sin embargo, al recordar que analizar una colección como Enumerable la lleva a la memoria, ¡puede requerir muchos recursos! Se debe tener precaución aquí.

XtraSimplicity
fuente
1

Además, si desea utilizar un constructor con varios objetos para inicializar, es posible que obtenga un error si Linq no devuelve ningún valor.

Por lo tanto, es posible que desee hacer algo como esto:

(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();
Mahesh
fuente
1

Perdón por llegar tarde a la fiesta, pero después de encontrar esto , pensé que esto debería compartirse, ya que es la implementación más limpia, rápida y que también ahorra memoria que pude encontrar.

Adaptado a tu ejemplo, escribirías:

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);
}

Las grandes ventajas aquí (como Damien Guard señaló en los comentarios en el enlace) son:

  • Le ahorra el uso del patrón de inicialización en cada aparición.
  • Uso vía var foo = createPayments(bar);así como el uso a través de myIQueryable.ToPayments () posible.
Yoda
fuente
1

Tuve el mismo problema hoy y mi solución fue similar a la que Yoda enumeró, sin embargo, solo funciona con sintaxis fluida.

Adaptando mi solución a su código: agregué el siguiente método estático a la clase de objeto

    /// <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
        };
    }

y luego actualizó la consulta base a lo siguiente:

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

Esto es lógicamente equivalente a la solución de James Manning con la ventaja de impulsar la gran cantidad de inicialización de miembros al objeto de transferencia de datos / clase

Nota: Originalmente estaba usando nombres más descriptivos que "Initializer", pero después de revisar cómo lo estaba usando, descubrí que "Initilizer" era suficiente (al menos para mis propósitos).

Nota final:
después de llegar a esta solución, originalmente pensé que sería simple compartir el mismo código y adaptarlo para que funcione también para la sintaxis de Query. Ya no creo que ese sea el caso. Creo que si desea poder utilizar este tipo de construcción abreviada, necesitaría un método para cada fluido (consulta, fluido) como se describe anteriormente, que puede existir en la clase de objeto en sí.

Para la sintaxis de consulta, se requeriría un método de extensión (o algún método fuera de la clase base que se utiliza). (dado que la sintaxis de consulta quiere operar un IQueryable en lugar de T)

Aquí hay una muestra de lo que solía hacer que finalmente funcione para la sintaxis de consulta. (Yoda ya clavó esto, pero creo que el uso podría ser más claro porque no lo entendí al principio)

/// <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
    };
}

y el uso

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select nalTmp).Initializer().ToList();
Wode
fuente
Agregué una sección sobre la sintaxis de consulta para completar cuando me di cuenta de que mi respuesta inicial no se extendía bien. La respuesta de @yoda probablemente sea mejor con respecto a la sintaxis de consulta.
wode
0

Aunque es tarde para responder, aún podría ayudar a alguien en apuros. Dado que LINQ to entidades no admite las construcciones de objetos sin parámetros. Sin embargo, los métodos de proyección para IEnumerable .

Entonces, antes de la selección, simplemente convierta su IQueryable a IEnumerable utilizando este código:

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

Funcionará bien Sin embargo, por supuesto, perderá los beneficios de las consultas nativas.

arslanahmad656
fuente
0
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; }
        ...
}
Jair Marques
fuente