У LINQ для сутностей підтримуються лише конструктори без параметри і ініціалізатори


132

Я маю цю помилку в цьому виразі 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();

Будь-яка ідея, як вирішити цю проблему? Я намагаюся з будь-якою комбінацією виразів ...: /


1
чи можна показати клас Платежі? або, принаймні, виклик ctor сюди, а конкретно, чи можна 8-парамний виклик ctor безпечно замінити на 0-парамний виклик ctor та встановити 8 властивостей на об'єкт?
Джеймс Меннінг

23
Я отримав цю саму помилку, коли використовував Struct замість класу для об'єкта, який я "новаю".
HuckIt

3
Річ TL; DR полягає в тому, що EF-LINQ намагається надіслати заявку select постачальнику EF, тобто. перетворити його в SQL. Щоб вийти з EF-LINQ, зателефонуйте до ToList () перед створенням будь-якого об'єкта.

Відповіді:


127

без додаткової інформації про "Платежі" це не дуже допомагає, але якщо припустити, що ви хочете створити об'єкт "Платежі" та встановити деякі його властивості на основі значень стовпця:

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

10
Це чудово працює, не забудьте додати порожній конструктор для класу.
live-love

58
Тільки щоб додати до цієї відповіді, ви не можете цього зробити зі структурами, лише Класи - мені було трохи зрозуміти це!
naspinski

4
Так, я вважаю, що відповідь Тоні є кращою, ніж ця, тому що вона фактично вирішує проблему, яка існує безпосередньо, тоді як ця обходить проблему, змінюючи характер класу Payments і, можливо, запобігаючи незмінності.
Стівен Холт

це виглядає потворно аф. Будь-який кращий спосіб із EF6?
Інструментарій

115

Якщо ви все ще хочете використовувати свій конструктор для ініціалізації, а не властивостей (іноді така поведінка потрібна для ініціалізації), перерахуйте запит, зателефонувавши ToList()або ToArray(), а потім використовуйте Select(…). Таким чином, він використовуватиме LINQ для колекцій, і обмеження неможливості викликати конструктор з параметрами у Select(…)зникає.

Отже, ваш код повинен виглядати приблизно так:

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

21
Для того, щоб уточнити, чому це працює, проблема з первісно заявленим кодом полягає в тому, що Entity Framework намагається передати виклик конструктора через SQL разом з рештою запиту LINQ, і, звичайно, немає можливості SQL іти на створення конструкції. складні об’єкти! Вставляючи виклик ToList (), ви переміщуєте перелік із ще не виконаного запиту SQL до конкретного списку об'єктів у пам'яті, якими ви зможете потім маніпулювати будь-яким способом, який вам подобається.
Стівен Холт

19
Не використовуйте ToX()для цього, використовуйте AsEnumerable().
Роулінг

1
.ToList () // Тут відбувається передача LINQ до колекцій. це рядок, який вирішує проблему для мене.
Рам

15
Будьте в курсі, що для цього буде вибрано всі стовпці на рівні db, де зазвичай вибираються лише необхідні стовпці
Х'ю Джеффнер

4
Не тільки це, але ймовірно, ви будете мати кілька перерахувань. Мені це рішення не подобається.
Блюбарон

47

Щойно зіткнувшись з цією помилкою, я подумав, що додам, що якщо Paymentтип є a struct, ви також зіткнетеся з тією ж помилкою, оскільки structтипи не підтримують конструктори без параметрів.

У цьому випадку перетворення Paymentдо класу та використання синтаксису об'єкта ініціалізатора вирішить проблему.


Це вирішує проблему у мене. Насправді цей запит із селектором структури підтримується в LINQ-2-SQL, і це проблема, коли ви переходите на EntityFramework.
Томаш Кубес

Я ненавиджу структури. Вони ніколи не закінчують робити те, що я хочу
Simon_Weaver

Створено DateTime(що є структурою) всередині мого запиту, що призводить до тієї ж помилки. витягнення його до локальної змінної виправлено для мене. Дякуємо за підказку Stru.
LuckyLikey

20

Якщо ви схожі на мене і не хочете заповнювати свої властивості для кожного запиту, який ви будуєте, є інший спосіб вирішити цю проблему.

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

Тепер ваш власний об'єкт (який приймає два об'єкти як параметр) може заповнити ваші властивості за потребою.


Це працювало для мене і стало найчистішим рішенням. Ті, хто запропонував усунути конструктор і використовувати синтаксис ініціалізатора, не повинні мати логіку в конструкторі. Це єдиний раз, коли я спираюся на конструктори, щоб заповнити властивості для об'єкта. Дякую, що поділились.
Bonez024

9

По-перше, я б уникнув рішення

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

Для цього потрібен порожній конструктор і ігнорується інкапсуляція, тому ви говорите, що нові платежі () - це дійсний платіж без будь-яких даних, але натомість об’єкт повинен мати принаймні значення та, ймовірно, інші необхідні поля залежно від вашого домену.

Краще мати конструктор для обов'язкових полів, а лише приносити необхідні дані:

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

Це менше зло.
Крейда

Я також віддаю перевагу щось подібне. Я намагався використати Tuple, але у Tuple немає конструктора менш параметрів. Я заповнював анонімний об’єкт, а потім вибирав Tuple.
Чапс

один за використання капсуляції та домену
inrandomwetrust

2

Можна спробувати зробити те ж саме, але використовуючи методи розширення. Який провайдер використання бази даних?

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

2

Просто перед затвердженням .. фактичний зберігається як запит, він ще не виконав. Після дзвінка ви граєте з об’єктами, а потім можете використовувати конструктор, який не використовується за замовчуванням.ToList()DbSetSelectDbSetToList()

Не найефективніший спосіб використання часу, але це варіант на невеликих наборах.


1

так, спробуйте так….

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

це створить ваш об’єкт платежу за допомогою конструктора без параметрів, а потім ініціалізує властивості, перелічені всередині фігурних дужок { }


3
FYI ()в Payemnts не потрібен, тому це може бути `вибрати нові платежі {// init величини}
PostMan

тепер у мене помилка: не вдається ініціалізувати тип "Платежі" за допомогою ініціалізатора колекції, оскільки він не реалізує "System.Collections.IEnumerable"
netmajor

праворуч - якби ви створювали тип анон (замість екземпляра класу Payments), код Муада був би добре, оскільки для встановлених властивостей неявно будуть імена властивостей, з яких читаються. Оскільки це "реальний" клас, однак, вам потрібно буде вказати, які властивості слід встановити для різних значень.
Джеймс Меннінг

1

На додаток до вищезгаданих методів, ви можете також розібрати його як численну колекцію, наприклад:

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

Пам’ятаючи, однак, що розбір колекції як «Безліч» тягне її в пам’ять, тож це може бути ресурсомістким! Тут слід застосовувати обережність.


1

Крім того, якщо ви хочете використовувати конструктор з декількома об'єктами для ініціалізації, ви можете отримати помилку, якщо 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();

1

Вибачте за те, що запізнилися на вечірку, але, знайшовши це , я подумав, що це слід розділити, оскільки це найчистіша, найшвидша і також збереження пам'яті реалізація, яку я міг знайти.

Адаптований до вашого прикладу, ви напишете:

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

Великими перевагами тут (як наголосив Демієн Гард у коментарях за посиланням) є:

  • Захищає вас від використання шаблону ініціалізації в кожному випадку.
  • Використання через var foo = createPayments(bar);, а також використання через myIQueryable.ToPayments () можливо.

1

У мене була така ж проблема сьогодні, і моє рішення було схоже на те, що перераховано у 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());

Це логічно еквівалентно рішенню Джеймса Меннінга з перевагою підштовхування ініціалізації члена до об'єкта класу / передачі даних

Примітка. Спочатку я використовував більш описові назви, що "Ініціалізатор", але переглянувши, як я ним користуюся, я виявив, що "Ініціалізатор" був достатнім (принаймні для моїх цілей).

Підсумкове зауваження:
Після розробки цього рішення я спочатку думав, що буде просто поділитися тим самим кодом та адаптувати його до роботи для синтаксису Query. Я вже не вірю, що так має бути. Я думаю, що якщо ви хочете використовувати цю конструкцію скорочень, вам знадобиться метод для кожного (запиту, вільного) розмову, як описано вище, який може існувати в самому класі об'єктів.

Для синтаксису запитів знадобиться метод розширення (або якийсь метод поза базовим класом, який використовується). (оскільки синтаксис запитів хоче керувати IQueyable, а не T)

Ось зразок того, що я використав, щоб нарешті змусити це працювати для синтаксису запитів. (Йода це вже прибив, але я думаю, що використання може бути зрозумілішим, оскільки я спочатку цього не зрозумів)

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

додав розділ щодо синтаксису запиту щодо повноти, коли зрозумів, що моя початкова відповідь не надто добре поширюється. @ anoda yoda, мабуть, краще стосовно синтаксису запитів.
wode

0

Хоча відповісти вже пізно, це все-таки може допомогти комусь у біді. Оскільки LINQ для сутностей не підтримує об'єкти без параметрів. Однак методів проекції для IEnumerable .

Тому перед вибором просто конвертуйте свій IQueryable в IEnumerable за допомогою цього коду:

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

Це буде добре працювати. Однак це, звичайно, позбавить переваг нативних запитів.


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; }
        ...
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.