LINQ to Entities obsługuje tylko konstruktory i inicjatory bez parametrów

134

Mam ten błąd w tym wyrażeniu 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();

Masz pomysł, jak rozwiązać ten problem? Próbuję z dowolną kombinacją wyrażeń ...: /

netmajor
źródło
1
czy możesz pokazać klasę płatności? albo przynajmniej wywoływany tutaj ctor, a konkretnie, czy to wywołanie 8-param ctor może być bezpiecznie zamienione na wywołanie ctora 0-param i ustawiając 8 właściwości obiektu?
James Manning
23
Wystąpił ten sam błąd, gdy używam Struct zamiast Class dla obiektu, który był „nowy”.
HuckIt,
3
TL; DR Rzecz w tym, że EF-LINQ próbuje wysłać instrukcję wyboru do dostawcy EF, tj. przekonwertować go na SQL. Aby wyjść z EF-LINQ, wywołaj ToList () przed utworzeniem dowolnego obiektu.

Odpowiedzi:

127

bez dodatkowych informacji na temat `` Płatności '' to niewiele pomoże, ale zakładając, że chcesz utworzyć obiekt Płatności i ustawić niektóre jego właściwości na podstawie wartości kolumn:

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
źródło
10
To działa świetnie, nie zapomnij dodać pustego konstruktora dla klasy.
miłość na żywo
58
Aby dodać do tej odpowiedzi, nie możesz tego zrobić za pomocą Structs, tylko Classes - trochę mi zajęło zrozumienie tego!
naspinski
4
Tak, myślę, że odpowiedź Tony'ego jest lepsza niż ta, ponieważ w rzeczywistości rozwiązuje ona bieżący problem, podczas gdy ta omija problem, zmieniając charakter klasy płatności i prawdopodobnie uniemożliwiając jej niezmienność.
Stephen Holt
to wygląda brzydko af. Jest lepszy sposób z EF6?
Zestaw narzędzi
116

Jeśli nadal chcesz używać konstruktora do inicjowania, a nie właściwości (czasami to zachowanie jest pożądane do celów inicjowania), wylicz zapytanie, wywołując ToList()lub ToArray(), a następnie użyj Select(…). W ten sposób użyje LINQ to Collections, a to ograniczenie polegające na braku możliwości wywołania konstruktora z parametrami w Select(…)zniknie.

Twój kod powinien więc wyglądać mniej więcej tak:

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
źródło
22
Aby wyjaśnić, dlaczego to działa, problem z pierwotnie podanym kodem polega na tym, że Entity Framework próbuje przekazać wywołanie konstruktora do SQL wraz z resztą zapytania LINQ i oczywiście nie ma możliwości, aby SQL zajął się konstruowaniem złożone obiekty! Wstawiając wywołanie ToList (), przenosisz wyliczalne z niewykonanego jeszcze zapytania SQL do konkretnej listy obiektów w pamięci, którą możesz następnie dowolnie manipulować.
Stephen Holt
19
Nie używaj ToX()do tego, użyj AsEnumerable().
Rawling
1
.ToList () // Nadchodzi transfer do LINQ to Collections. to linia, która rozwiązuje ten problem za mnie.
Ram
16
Należy pamiętać, że spowoduje to zaznaczenie wszystkich kolumn na poziomie db, gdzie jak zwykle wybierze tylko wymagane kolumny
Hugh Jeffner
4
Nie tylko to, ale prawdopodobnie będziesz mieć wiele wyliczeń. Nie podoba mi się to rozwiązanie.
Bluebaron
47

Po tym, jak sam napotkałem ten błąd, pomyślałem, że dodam, że jeśli Paymenttyp to a struct, napotkasz również ten sam błąd, ponieważ structtypy nie obsługują konstruktorów bez parametrów.

W takim przypadku konwersja Paymentdo klasy i użycie składni inicjatora obiektu rozwiąże problem.

Gene C.
źródło
To rozwiązuje mój problem. W rzeczywistości to zapytanie z selektorem struktury jest obsługiwane w LINQ-2-SQL i jest to problem podczas uaktualniania do EntityFramework.
Tomas Kubes
Nienawidzę struktur. Nigdy nie robią tego, co chcę
Simon_Weaver,
Utworzono DateTime(która jest strukturą) w moim zapytaniu, co powoduje ten sam błąd. wypakowanie go do lokalnej zmiennej naprawiło to za mnie. Dzięki za wskazówkę dotyczącą struktury.
LuckyLikey
20

Jeśli jesteś podobny do mnie i nie chcesz wypełniać swoich właściwości dla każdego budowanego zapytania, istnieje inny sposób rozwiązania tego problemu.

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

W tym momencie masz IQueryable zawierający anonimowy obiekt. Jeśli chcesz wypełnić swój niestandardowy obiekt konstruktorem, możesz po prostu zrobić coś takiego:

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

Teraz twój niestandardowy obiekt (który przyjmuje dwa obiekty jako parametr) może wypełnić twoje właściwości według potrzeb.

Justin Helgerson
źródło
To zadziałało dla mnie i okazało się najczystszym rozwiązaniem. Ci, którzy zasugerowali wyeliminowanie konstruktora i użycie składni inicjatora, nie mogli mieć logiki w konstruktorze. To jedyny raz, kiedy opieram się na konstruktorach do wypełniania właściwości obiektu. Dziękuję za udostępnienie.
Bonez024
9

Najpierw unikałbym rozwiązania z

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

Wymaga to pustego konstruktora i ignoruje hermetyzację, więc mówisz, że new Payments () jest prawidłową płatnością bez żadnych danych, ale zamiast tego obiekt musi mieć co najmniej wartość i prawdopodobnie inne wymagane pola w zależności od Twojej domeny.

Lepiej mieć konstruktora dla wymaganych pól, ale przynosić tylko potrzebne dane:

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
źródło
To jest mniejsze zło.
Chalky
Ja też wolę coś takiego. Próbowałem użyć Tuple, ale Tuple nie ma konstruktora bez parametrów. Wypełniłem anonimowy obiekt, a następnie wybrałem krotkę.
Tchaps
jeden za enkapsulację i domenę
inrandomwetrust
3

Możesz spróbować zrobić to samo, ale używając metod rozszerzania. Jaki jest dostawca bazy danych?

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();
pan Tarakanoff
źródło
2

Wystarczy przed stwierdzeniem .. rzeczywista jest zapisywana jako zapytanie, to nie jest jeszcze spełnione. Po wywołaniu grasz z obiektami, a następnie możesz użyć w zapytaniu konstruktora innego niż domyślny.ToList()DbSetSelectDbSetToList()

Nie jest to najbardziej efektywny sposób pod względem czasu użytkowania, ale jest to opcja w małych zestawach.

eiran
źródło
1

tak, spróbuj tego w ten sposób ....

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

spowoduje to odświeżenie obiektu Payment przy użyciu konstruktora bez parametrów, a następnie zainicjuje właściwości wymienione w nawiasach klamrowych { }

Muad'Dib
źródło
3
Do Twojej wiadomości informacja ()w Payemnts nie jest potrzebna, więc można ją wybrać „select new Payments {// init values}”
PostMan
teraz mam błąd: nie można zainicjować typu „Płatności” za pomocą inicjatora kolekcji, ponieważ nie implementuje on „System.Collections.IEnumerable”
netmajor
right - jeśli tworzysz typ anon (zamiast instancji klasy Payments), kod Muada byłby w porządku, ponieważ właściwości do ustawienia byłyby niejawnie nazwami właściwości, z których odczytywane są. Ponieważ jest to jednak klasa „prawdziwa”, należałoby określić, które właściwości ustawić na różne wartości.
James Manning
1

Oprócz wyżej wymienionych metod możesz również przeanalizować go jako kolekcję Enumerable, na przykład:

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

Ma to również tę dodatkową zaletę, że ułatwia życie podczas tworzenia anonimowego obiektu, na przykład:

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

Pamiętając jednak, że analizowanie kolekcji jako Enumerable wciąga ją do pamięci, więc może wymagać dużych zasobów! Należy zachować ostrożność.

XtraSimplicity
źródło
1

Ponadto, jeśli chcesz użyć konstruktora z wieloma obiektami do zainicjowania, możesz otrzymać błąd, jeśli Linq nie zwróci żadnych wartości.

Więc możesz zrobić coś takiego:

(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
źródło
1

Przepraszam, że spóźniłem się na imprezę, ale po znalezieniu tego pomyślałem, że powinno się to udostępnić, ponieważ jest to najczystsza, najszybsza i oszczędzająca pamięć implementacja, jaką znalazłem.

Dostosowując się do twojego przykładu, napisałbyś:

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

Wielkie zalety tutaj (jak wskazał Damien Guard w komentarzach pod linkiem) to:

  • Uniemożliwia używanie wzorca inicjalizacji przy każdym wystąpieniu.
  • var foo = createPayments(bar);Możliwe użycie przez, jak również użycie przez myIQueryable.ToPayments ().
Yoda
źródło
1

Miałem dzisiaj ten sam problem i moje rozwiązanie było podobne do tego, które wymienił Yoda, jednak działa tylko z płynną składnią.

Dostosowanie mojego rozwiązania do twojego kodu: dodałem następującą metodę statyczną do klasy obiektu

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

a następnie zaktualizowałem zapytanie podstawowe do następującego:

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

Jest to logicznie równoważne z rozwiązaniem Jamesa Manninga, ale ma tę zaletę, że przenosi nadmiar inicjalizacji elementu członkowskiego do obiektu transferu klasy / danych

Uwaga: Początkowo używałem bardziej opisowych nazw niż „Initializer”, ale po przejrzeniu, jak go używam, stwierdziłem, że „Initilizer” był wystarczający (przynajmniej do moich celów).

Uwaga końcowa:
Po wymyśleniu tego rozwiązania początkowo myślałem, że udostępnienie tego samego kodu i dostosowanie go do pracy również ze składnią zapytań będzie proste. Nie wierzę już, że tak jest. Myślę, że jeśli chcesz móc używać tego typu skróconej konstrukcji, potrzebujesz metody dla każdego (zapytania, płynnego) biegłego, jak opisano powyżej, która może istnieć w samej klasie obiektów.

W przypadku składni zapytania wymagana byłaby metoda rozszerzenia (lub inna metoda spoza używanej klasy bazowej). (ponieważ składnia zapytania chce obsługiwać IQueryable zamiast T)

Oto próbka tego, czego użyłem, aby w końcu uruchomić to dla składni zapytań. (Yoda już to załatwił, ale myślę, że użycie mogłoby być jaśniejsze, ponieważ nie dostałem tego na początku)

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

i użytkowania

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select nalTmp).Initializer().ToList();
wode
źródło
dodałem sekcję dotyczącą składni zapytania pod kątem kompletności, gdy zdałem sobie sprawę, że moja początkowa odpowiedź nie była dobrze rozszerzona. Odpowiedź @ yody jest prawdopodobnie lepsza pod względem składni zapytania.
działał
0

Chociaż jest późno na odpowiedź, może pomóc komuś w potrzebie. Ponieważ LINQ to entity nie obsługuje konstrukcji obiektów bez parametrów. Jednak metody projekcji dla IEnumerable .

Więc przed wyborem, po prostu przekonwertuj swój IQueryable na IEnumerable , używając tego kodu:

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

Będzie działać dobrze. Jednak utraci to oczywiście zalety zapytań natywnych.

arslanahmad656
źródło
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
źródło