В LINQ to Entities поддерживаются только конструкторы и инициализаторы без параметров.

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

Есть идеи, как решить эту проблему? Я пытаюсь использовать любую комбинацию выражений ...: /

netmajor
источник
1
вы можете показать класс Payments? или, по крайней мере, здесь вызывается ctor, и, в частности, можно ли безопасно заменить этот вызов ctor с 8 параметрами на вызов ctor с 0 параметрами и установить 8 свойств объекта?
Джеймс Мэннинг,
23
Я получил ту же ошибку при использовании структуры вместо класса для объекта, который я «новичок».
HuckIt
3
TL; DR дело в том, что EF-LINQ пытается отправить оператор select поставщику EF, т.е. преобразовать его в SQL. Чтобы выйти из EF-LINQ, вызовите ToList () перед созданием любого объекта.

Ответы:

127

без дополнительной информации о «Платежах» это мало помогает, но при условии, что вы хотите создать объект Payments и установить некоторые из его свойств на основе значений столбца:

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 06
4
Да, я думаю, что ответ Тони лучше, чем этот, потому что он фактически решает непосредственную проблему, тогда как этот позволяет обойти проблему, изменяя природу класса Payments и, возможно, предотвращая его неизменность.
Стивен Холт
это выглядит некрасиво. Есть ли лучший способ с EF6?
Toolkit
115

Если вы по-прежнему хотите использовать свой конструктор для инициализации, а не свойства (иногда такое поведение желательно для целей инициализации), перечислите запрос, вызвав ToList()или ToArray(), а затем используйте Select(…). Таким образом, он будет использовать LINQ to Collections, и это ограничение на невозможность вызова конструктора с параметрами 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().
Rawling
1
.ToList () // А вот и переход в LINQ to Collections. это строка, которая решает проблему для меня.
Ram
15
Имейте в виду, что при этом будут выбраны все столбцы на уровне
базы данных, тогда
4
Не только это, но у вас, вероятно, будет несколько перечислений. Мне не нравится это решение.
Bluebaron
47

Только что столкнувшись с этой ошибкой, я подумал, что добавлю, что если Paymentтип - a struct, вы также столкнетесь с той же ошибкой, потому что structтипы не поддерживают конструкторы без параметров.

В этом случае преобразование Paymentв класс и использование синтаксиса инициализатора объекта решит проблему.

Джин С
источник
Это решает мою проблему. На самом деле этот запрос с селектором структуры поддерживается в LINQ-2-SQL, и это проблема при обновлении до EntityFramework.
Tomas Kubes
Ненавижу структуры. Они никогда не заканчивают делать то, что я хочу
Simon_Weaver
Создал DateTime(который является структурой) внутри моего запроса, что приводит к той же ошибке. извлечение его в локальную переменную исправило это для меня. Спасибо за подсказку по структуре.
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,
  ....
}

Для этого требуется пустой конструктор и игнорируется инкапсуляция, поэтому вы говорите, что 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();
Eugen
источник
Это меньшее зло.
Меловой
Я тоже предпочитаю что-то подобное. Я пытался использовать Tuple, но у Tuple нет конструктора без параметров. Я заполнил анонимный объект, а затем выбрал Tuple.
Tchaps
один за охват инкапсуляции и домена
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()

Не самый эффективный способ использования с точки зрения времени, но это вариант для небольших наборов.

eiran
источник
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();

это обновит ваш объект Payment с помощью конструктора без параметров, а затем инициализирует свойства, перечисленные в фигурных скобках { }

Муад'Диб
источник
3
К вашему сведению, ()в Payemnts нет необходимости, так что это может быть `select new Payments {// init values}
PostMan
теперь у меня ошибка: не могу инициализировать тип «Payments» с помощью инициализатора коллекции, потому что он не реализует «System.Collections.IEnumerable»
netmajor
правильно - если вы создавали анонимный тип (вместо экземпляра класса Payments), код Muad был бы в порядке, поскольку устанавливаемые свойства неявно были бы именами свойств, из которых выполняется чтение. Однако, поскольку это «настоящий» класс, вам нужно будет указать, какие свойства устанавливать для различных значений.
Джеймс Мэннинг,
1

В дополнение к вышеупомянутым методам вы также можете проанализировать его как коллекцию Enumerable, например:

(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 втягивает ее в память, поэтому она может потребовать значительных ресурсов! Здесь следует проявлять осторожность.

XtraSimplicity
источник
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 () возможно.
Yoda
источник
1

Сегодня у меня была такая же проблема, и мое решение было похоже на то, что указал Йода, однако оно работает только с плавным синтаксисом.

Адаптируя свое решение к вашему коду: я добавил следующий статический метод в класс объекта

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

Это логически эквивалентно решению Джеймса Мэннинга с тем преимуществом, что раздувание инициализации членов для класса / объекта передачи данных

Примечание. Первоначально я использовал более описательные имена, чем «Инициализатор», но после рассмотрения того, как я его использовал, я обнаружил, что «Инициализатор» было достаточно (по крайней мере, для моих целей).

Заключительное примечание:
придумав это решение, я изначально думал, что было бы просто поделиться одним и тем же кодом и адаптировать его для работы с синтаксисом запросов. Я больше не верю, что это так. Я думаю, что если вы хотите иметь возможность использовать этот тип сокращенной конструкции, вам понадобится метод для каждого (запрос, беглый), как описано выше, который может существовать в самом классе объекта.

Для синтаксиса запроса потребуется метод расширения (или какой-либо метод за пределами используемого базового класса). (поскольку синтаксис запроса хочет работать с IQueryable, а не с 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();
Wode
источник
добавил раздел, посвященный синтаксису запроса, для полноты, когда я понял, что мой первоначальный ответ не подходит. Ответ @yoda, вероятно, лучше в отношении синтаксиса запроса.
wode
0

Хотя и поздно отвечать, он все же может помочь кому-то в беде. Поскольку LINQ to entity не поддерживает конструкции объектов без параметров. Однако методы проецирования IEnumerable .

Поэтому перед выбором просто преобразуйте свой IQueryable в IEnumerable с помощью этого кода:

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

Это будет работать нормально. Однако это, конечно, потеряет преимущества собственных запросов.

arslanahmad656
источник
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; }
        ...
}
Жаир Маркес
источник