In LINQ to Entities werden nur parameterlose Konstruktoren und Initialisierer unterstützt

132

Ich habe diesen Fehler in diesem linq-Ausdruck:

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

Irgendeine Idee, wie man dieses Problem löst? Ich versuche es mit einer beliebigen Ausdruckskombination ...: /

netmajor
quelle
1
Können Sie die Zahlungsklasse anzeigen? oder zumindest der ctor, der hier aufgerufen wird, und insbesondere, ob dieser 8-Parameter-ctor-Aufruf sicher gegen einen 0-Parameter-ctor-Aufruf ausgetauscht werden kann und 8 Eigenschaften für das Objekt festgelegt werden?
James Manning
23
Ich habe den gleichen Fehler erhalten, als ich eine Struktur anstelle einer Klasse für das Objekt verwendet habe, das ich "neu gemacht" habe.
HuckIt
3
TL; DR ist, dass EF-LINQ versucht, die select-Anweisung an den EF-Anbieter zu senden, d. H. konvertiere es in SQL. Um EF-LINQ zu verlassen, rufen Sie vor jeder Objekterstellung ToList () auf.

Antworten:

127

Ohne weitere Informationen zu "Zahlungen" hilft dies nicht viel. Vorausgesetzt, Sie möchten ein Zahlungsobjekt erstellen und einige seiner Eigenschaften basierend auf Spaltenwerten festlegen:

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
quelle
10
Dies funktioniert hervorragend. Vergessen Sie nicht, einen leeren Konstruktor für die Klasse hinzuzufügen.
Live-Liebe
58
Um diese Antwort zu ergänzen, können Sie dies nicht mit Strukturen tun, sondern nur mit Klassen - ich habe ein bisschen gebraucht, um das herauszufinden!
Naspinski
4
Ja, ich denke, Tonys Antwort ist besser als diese, weil sie das unmittelbare Problem tatsächlich löst, während diese das Problem umgeht, indem sie die Art der Zahlungsklasse ändert und möglicherweise verhindert, dass sie unveränderlich ist.
Stephen Holt
das sieht hässlich aus af. Gibt es einen besseren Weg mit EF6?
Toolkit
115

Wenn Sie Ihren Konstruktor weiterhin für die Initialisierung und nicht für Eigenschaften verwenden möchten (manchmal ist dieses Verhalten für Initialisierungszwecke erwünscht), führen Sie die Abfrage auf, indem Sie ToList()oder aufrufen ToArray(), und verwenden Sie dann Select(…). Daher wird LINQ to Collections verwendet und die Einschränkung, dass Konstruktoren mit Parametern in nicht aufgerufen werden könnenSelect(…) verschwindet.

Ihr Code sollte also ungefähr so ​​aussehen:

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
quelle
21
Um zu verdeutlichen, warum dies funktioniert, besteht das Problem mit dem ursprünglich angegebenen Code darin, dass Entity Framework versucht, den Konstruktoraufruf zusammen mit dem Rest der LINQ-Abfrage an SQL weiterzuleiten, und dass SQL natürlich keine Möglichkeit zum Erstellen hat komplexe Objekte! Durch Einfügen des Aufrufs ToList () verschieben Sie die Aufzählung von einer noch nicht ausgeführten SQL-Abfrage in eine konkrete Liste von Objekten im Speicher, die Sie dann nach Belieben bearbeiten können.
Stephen Holt
19
Nicht dafür verwenden ToX(), verwenden AsEnumerable().
Rawling
1
.ToList () // Hier kommt die Übertragung zu LINQ zu Sammlungen. ist die Linie, die das Problem für mich löst.
Ram
15
Beachten Sie, dass dies alle Spalten auf der DB-Ebene auswählt, wo normalerweise nur die erforderlichen Spalten ausgewählt werden
Hugh Jeffner
4
Darüber hinaus werden Sie wahrscheinlich mehrere Aufzählungen haben. Ich mag diese Lösung nicht.
Bluebaron
47

Nachdem ich diesen Fehler gerade selbst festgestellt habe, dachte ich, ich würde hinzufügen, dass wenn der PaymentTyp a ist struct, Sie auch auf denselben Fehler stoßen würden, da structTypen keine parameterlosen Konstruktoren unterstützen.

In diesem Fall Paymentwird das Problem durch Konvertieren in eine Klasse und Verwenden der Objektinitialisierersyntax behoben.

Gene C.
quelle
Dies löst das Problem von mir. Tatsächlich wird diese Abfrage mit Strukturauswahl in LINQ-2-SQL unterstützt und ist ein Problem, wenn Sie ein Upgrade auf EntityFramework durchführen.
Tomas Kubes
Ich hasse Strukturen. Sie machen nie das, was ich will
Simon_Weaver
Erstellt eine DateTime(die eine Struktur ist) in meiner Abfrage, was zu demselben Fehler führt. Das Extrahieren in eine lokale Variable hat es für mich behoben. Danke für den Strukturhinweis.
LuckyLikey
20

Wenn Sie wie ich sind und nicht für jede Abfrage, die Sie erstellen, Ihre Eigenschaften auffüllen müssen, gibt es eine andere Möglichkeit, dieses Problem zu lösen.

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

Zu diesem Zeitpunkt haben Sie eine IQueryable, die ein anonymes Objekt enthält. Wenn Sie Ihr benutzerdefiniertes Objekt mit einem Konstruktor füllen möchten, können Sie einfach Folgendes tun:

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

Jetzt kann Ihr benutzerdefiniertes Objekt (das zwei Objekte als Parameter verwendet) Ihre Eigenschaften nach Bedarf füllen.

Justin Helgerson
quelle
Dies funktionierte für mich und wurde zur saubersten Lösung. Diejenigen, die vorgeschlagen haben, den Konstruktor zu entfernen und die Initialisierersyntax zu verwenden, dürfen keine Logik im Konstruktor gehabt haben. Dies ist das einzige Mal, dass ich mich auf Konstruktoren stütze, um Eigenschaften für ein Objekt zu füllen. Ich danke Ihnen für das Teilen.
Bonez024
9

Zuerst würde ich die Lösung mit vermeiden

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

Dies erfordert einen leeren Konstruktor und ignoriert die Kapselung, sodass Sie sagen, dass new Payments () eine gültige Zahlung ohne Daten ist. Stattdessen muss das Objekt abhängig von Ihrer Domain mindestens einen Wert und wahrscheinlich andere erforderliche Felder haben.

Es ist besser, einen Konstruktor für erforderliche Felder zu haben, aber nur die erforderlichen Daten mitzubringen:

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
quelle
Dies ist das kleinere Übel.
Chalky
Ich bevorzuge auch so etwas. Ich habe versucht, Tuple zu verwenden, aber Tuple hat keinen Konstruktor ohne Parameter. Ich habe ein anonymes Objekt ausgefüllt und dann Tupel ausgewählt.
Tchaps
eine für Umarmung und Domäne
Anton Rhein
2

Sie können versuchen, dasselbe zu tun, aber die Erweiterungsmethoden verwenden. Was ist der Anbieter der Datenbanknutzung?

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();
Herr Tarakanoff
quelle
2

Nur ToList()die DbSetvor der SelectAnweisung .. die tatsächliche DbSetwird als Abfrage gespeichert, es ist noch nicht erfüllt. Nach dem Aufruf spielen ToList()Sie mit Objekten und können dann einen nicht standardmäßigen Konstruktor in der Abfrage verwenden.

Nicht die effizienteste Art der Nutzungsdauer, aber eine Option für kleine Sets.

Eiran
quelle
1

yeh, versuch es so ....

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

Dadurch wird Ihr Zahlungsobjekt mithilfe eines parameterlosen Konstruktors neu erstellt und anschließend die Eigenschaften initialisiert, die in geschweiften Klammern aufgeführt sind { }

Muad'Dib
quelle
3
Zu ()Ihrer Information, das in den Payemnts wird nicht benötigt, daher kann es sein, "Neue Zahlungen auswählen {// Init-Werte}
PostMan
Jetzt habe ich einen Fehler: Der Typ 'Zahlungen' kann nicht mit einem Sammlungsinitialisierer initialisiert werden, da er 'System.Collections.IEnumerable' nicht implementiert
netmajor
Richtig - Wenn Sie einen Anon-Typ erstellen (anstelle einer Instanz der Payments-Klasse), ist der Code von Muad in Ordnung, da die festzulegenden Eigenschaften implizit die Namen der Eigenschaften sind, aus denen gelesen wird. Da es sich jedoch um eine "echte" Klasse handelt, müssen Sie angeben, welche Eigenschaften auf die verschiedenen Werte festgelegt werden sollen.
James Manning
1

Zusätzlich zu den oben genannten Methoden können Sie es auch als Enumerable-Sammlung analysieren, z. B.:

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

Dies hat auch den zusätzlichen Vorteil, dass das Leben beim Erstellen eines anonymen Objekts wie folgt erleichtert wird:

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

Denken Sie jedoch daran, dass das Parsen einer Sammlung als Enumerable sie in den Speicher zieht, sodass sie ressourcenintensiv sein kann! Hier ist Vorsicht geboten.

XtraSimplicity
quelle
1

Wenn Sie zum Initialisieren einen Konstruktor mit mehreren Objekten verwenden möchten, wird möglicherweise eine Fehlermeldung angezeigt, wenn Linq keine Werte zurückgibt.

Vielleicht möchten Sie so etwas tun:

(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
quelle
1

Sorry für die Verspätung der Partei, aber ich nach dem Auffinden dieser , dachte ich, dass dies geteilt werden sollte, da es die sauberste, schnellste und auch speichersparendste Implementierung ist, die ich finden konnte.

An Ihr Beispiel angepasst würden Sie schreiben:

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

Die großen Vorteile hier (wie Damien Guard in den Kommentaren unter dem Link hervorhob) sind:

  • Verhindert, dass Sie bei jedem Auftreten ein Initialisierungsmuster verwenden.
  • Nutzung über var foo = createPayments(bar);sowie Nutzung über myIQueryable.ToPayments () möglich.
Yoda
quelle
1

Ich hatte heute das gleiche Problem und meine Lösung ähnelte der von Yoda aufgelisteten, funktioniert jedoch nur mit fließender Syntax.

Anpassen meiner Lösung an Ihren Code: Ich habe der Objektklasse die folgende statische Methode hinzugefügt

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

und aktualisierte dann die Basisabfrage auf Folgendes:

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

Dies entspricht logischerweise der Lösung von James Manning mit dem Vorteil, dass das Aufblähen der Elementinitialisierung auf das Klassen- / Datenübertragungsobjekt übertragen wird

Hinweis: Ursprünglich verwendete ich aussagekräftigere Namen als "Initializer", aber nachdem ich überprüft hatte, wie ich ihn verwendete, stellte ich fest, dass "Initilizer" ausreichend war (zumindest für meine Zwecke).

Schlussbemerkung:
Nachdem ich diese Lösung entwickelt hatte, dachte ich ursprünglich, es wäre einfach, denselben Code zu teilen und diesen anzupassen, um auch für die Abfragesyntax zu funktionieren. Ich glaube nicht mehr, dass dies der Fall ist. Ich denke, wenn Sie in der Lage sein möchten, diese Art der Kurzschriftkonstruktion zu verwenden, benötigen Sie eine Methode für jede (Abfrage, fließend) fließend wie oben beschrieben, die in der Objektklasse selbst vorhanden sein kann.

Für die Abfragesyntax wäre eine Erweiterungsmethode (oder eine Methode außerhalb der verwendeten Basisklasse) erforderlich. (da die Abfragesyntax eher ein IQueryable als ein T betreiben möchte)

Hier ist ein Beispiel dessen, was ich verwendet habe, um dies endlich für die Abfragesyntax zum Laufen zu bringen. (Yoda hat das bereits verstanden, aber ich denke, die Verwendung könnte klarer sein, weil ich es zuerst nicht verstanden habe.)

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

und die Verwendung

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select nalTmp).Initializer().ToList();
wode
quelle
Der Vollständigkeit halber wurde ein Abschnitt zur Abfragesyntax hinzugefügt, als ich feststellte, dass meine erste Antwort nicht gut war. @ yodas Antwort ist wahrscheinlich besser in Bezug auf die Abfragesyntax.
Wode
0

Obwohl es spät ist zu antworten, könnte es dennoch jemandem in Not helfen. Da LINQ to-Entitäten die parameterlosen Objektkonstruktionen nicht unterstützen. Die Projektionsmethoden für IEnumerable sind jedoch .

Konvertieren Sie also vor der Auswahl einfach Ihr IQueryable mit diesem Code in IEnumerable :

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

Es wird gut funktionieren. Es wird jedoch natürlich die Vorteile nativer Abfragen verlieren.

arslanahmad656
quelle
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
quelle