Somente construtores e inicializadores sem parâmetros são suportados no LINQ to Entities

132

Eu tenho esse erro nesta expressão 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();

Alguma idéia de como resolver esse problema? Eu tento com qualquer combinação de expressão ...: /

netmajor
fonte
1
você pode mostrar a classe Payments? ou pelo menos o ctor sendo chamado aqui, e especificamente se essa chamada de 8 parâmetros pode ser trocada com segurança por uma chamada de 0 parâmetros e definindo 8 propriedades no objeto?
James Manning
23
Eu recebi esse mesmo erro ao usar um Struct em vez de uma classe para o objeto que estava "newing".
HuckIt
3
O ponto TL; DR é que o EF-LINQ está tentando enviar a instrução select para o provedor EF, ou seja. convertê-lo em SQL. Para sair do EF-LINQ, chame ToList () antes de qualquer criação de objeto.

Respostas:

127

sem mais informações sobre 'Pagamentos', isso não ajuda muito, mas suponha que você queira criar um objeto Pagamentos e definir algumas de suas propriedades com base nos valores da coluna:

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
fonte
10
Isso funciona muito bem, não se esqueça de adicionar um construtor vazio para a classe.
live-love
58
Apenas para adicionar a esta resposta, você não pode fazer isso com Structs, apenas Classes - demorei um pouco para descobrir isso!
naspinski 6/12/12
4
Sim, acho que a resposta de Tony é melhor do que esta, porque na verdade resolve o problema imediato em questão, enquanto isso contorna o problema alterando a natureza da classe Payments e possivelmente impedindo que ele seja imutável.
Stephen Holt
isso parece fe fe. Existe alguma maneira melhor com o EF6?
Toolkit
115

Se você ainda deseja usar seu construtor para inicialização e não propriedades (às vezes esse comportamento é desejado para fins de inicialização), enumere a consulta chamando ToList()or ToArray()e, em seguida, use Select(…). Assim, ele usará o LINQ to Collections e a limitação de não poder chamar o construtor com parâmetros in Select(…)desaparecerá.

Portanto, seu código deve ser algo como isto:

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
fonte
21
Apenas para esclarecer por que isso funciona, o problema com o código originalmente declarado é que o Entity Framework tenta passar a chamada do construtor para o SQL junto com o restante da consulta LINQ e, é claro, não há como o SQL continuar construindo objetos complexos! Ao inserir a chamada ToList (), você move o enumerável de uma consulta SQL ainda não executada para uma lista concreta de objetos na memória, que você pode manipular da maneira que desejar.
Stephen Holt
19
Não use ToX()para isso, use AsEnumerable().
Rawling
1
.ToList () // Aqui vem a transferência para LINQ to Collections. é a linha que resolve o problema para mim.
Ram
15
Esteja ciente de que isso selecionará todas as colunas no nível de banco de dados, onde normalmente, somente selecionará as colunas necessárias
Hugh Jeffner
4
Não apenas isso, mas você provavelmente terá várias enumerações. Eu não gosto desta solução.
Bluebaron
47

Tendo acabado de encontrar esse erro, pensei em acrescentar que, se o Paymenttipo for a struct, você também encontrará o mesmo erro, porque os structtipos não suportam construtores sem parâmetros.

Nesse caso, a conversão Payment para uma classe e o uso da sintaxe do inicializador de objetos resolverão o problema.

Gene C
fonte
Isso resolve o problema de mim. Na verdade, essa consulta com o seletor de estrutura é suportada no LINQ-2-SQL e é um problema quando você está atualizando para o EntityFramework.
Tomas Kubes
Eu odeio estruturas. Eles nunca acabam fazendo o que eu quero
Simon_Weaver 29/09/16
Criei um DateTime(que é uma estrutura) dentro da minha consulta, que resulta no mesmo erro. extraí-lo para uma variável local corrigiu para mim. Obrigado pela dica struct.
Julio
20

Se você é como eu e não deseja preencher suas propriedades para cada consulta que está criando, existe outra maneira de resolver esse problema.

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

Neste ponto, você tem um IQueryable contendo um objeto anônimo. Se você deseja preencher seu objeto personalizado com um construtor, basta fazer algo assim:

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

Agora, seu objeto personalizado (que aceita dois objetos como parâmetro) pode preencher suas propriedades conforme necessário.

Justin Helgerson
fonte
Isso funcionou para mim e veio a ser a solução mais limpa. Aqueles que sugeriram eliminar o construtor e usar a sintaxe do inicializador não devem ter lógica dentro do construtor. Essa é a única vez que me apelo aos construtores para preencher as propriedades de um objeto. Obrigado por compartilhar.
Bonez024
9

Primeiro eu evitaria a solução com

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

Isso requer um construtor vazio e ignora o encapsulamento. Portanto, você está dizendo que novo Payments () é um pagamento válido sem dados, mas o objeto deve ter pelo menos um valor e provavelmente outros campos obrigatórios, dependendo do seu domínio.

É melhor ter um construtor para os campos obrigatórios, mas apenas trazer os dados necessários:

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
fonte
Este é o mal menor.
Chalky
Eu também prefiro algo assim. Eu estava tentando usar Tuple, mas Tuple não tem um parâmetro menos construtor. Eu preenchi um objeto anônimo e selecione Tupla.
Tchaps
um para abraçar encapsulamento e de domínio
inrandomwetrust
2

Você pode tentar fazer o mesmo, mas usando os métodos de extensão. Qual é o provedor do banco de dados usado?

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
fonte
2

Apenas ToList()o DbSetantes da Selectdeclaração .. o real DbSeté guardada como uma consulta, não é ainda preenchidas. Após chamar ToList()você está jogando com objetos e, em seguida, você pode usar um construtor não padrão na consulta.

Não é a maneira mais eficiente em termos de tempo de uso, mas é uma opção em aparelhos pequenos.

eiran
fonte
1

sim, tente assim ....

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

isso atualizará seu objeto Payment usando um construtor sem parâmetros e inicializará as propriedades listadas dentro dos chavetas { }

Muad'Dib
fonte
3
FYI a ()nos Payemnts não é necessário, por isso pode ser `selecionar novos Pagamentos {// valores de inicialização}
carteiro
agora eu tenho o erro: Não é possível inicializar o tipo 'Payments' com um inicializador de coleção porque ele não implementa 'System.Collections.IEnumerable'
netmajor
à direita - se você estivesse criando um tipo anon (em vez de uma instância da classe Payments), o código de Muad ficaria bem, pois as propriedades a serem definidas seriam implicitamente os nomes das propriedades que estão sendo lidas. Como é uma classe 'real', você precisará especificar quais propriedades definir para os vários valores.
James Manning
1

Além dos métodos mencionados acima, você também pode analisá-lo como uma coleção Enumerable, assim:

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

Isso também tem o benefício adicional de facilitar a vida ao criar um 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();

Lembrando, no entanto, que a análise de uma coleção como Enumerable a armazena na memória, para que possa consumir muitos recursos! Cuidado deve ser usado aqui.

XtraSimplicity
fonte
1

Além disso, se você desejar usar um construtor com vários objetos para inicializar, poderá receber um erro se nenhum valor for retornado pelo Linq.

Então, você pode querer fazer algo assim:

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

Desculpe por chegar atrasado à festa, mas depois de encontrar isso , achei que deveria ser compartilhado, pois é a implementação mais limpa, rápida e também de economia de memória que pude encontrar.

Adaptado ao seu exemplo, você escreveria:

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

As grandes vantagens aqui (como Damien Guard apontou nos comentários no link) são:

  • Protege você de usar o padrão de inicialização em cada ocorrência.
  • É var foo = createPayments(bar);possível usar o uso via myIQueryable.ToPayments ().
Yoda
fonte
1

Hoje eu tinha o mesmo problema e minha solução era semelhante à listada pela Yoda, no entanto, ela funciona apenas com sintaxe fluente.

Adaptando minha solução ao seu código: Adicionei o seguinte método estático à classe 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
        };
    }

e atualizou a consulta base para o seguinte:

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

Isso é logicamente equivalente à solução de James Manning, com a vantagem de empurrar o inchaço da inicialização do membro para o objeto de transferência de classe / dados

Nota: Originalmente, eu estava usando nomes mais descritivos que "Initializer", mas depois de revisar como o estava usando, descobri que "Initilizer" era suficiente (pelo menos para meus propósitos).

Nota final:
Depois de criar esta solução, eu originalmente pensava que seria simples compartilhar o mesmo código e adaptá-lo ao trabalho para a sintaxe de consulta também. Não acredito mais que seja esse o caso. Eu acho que se você quiser usar esse tipo de construção abreviada, precisará de um método para cada fluente (consulta, fluente), conforme descrito acima, que possa existir na própria classe de objeto.

Para a sintaxe da consulta, seria necessário um método de extensão (ou algum método fora da classe base). (como a sintaxe da consulta deseja operar um IQueryable em vez de T)

Aqui está um exemplo do que eu usei para finalmente fazer com que isso funcionasse para a sintaxe da consulta. (Yoda já acertou em cheio, mas acho que o uso pode ser mais claro porque não o compreendi a princípio)

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

e o uso

var naleznosci = (from nalTmp in db.Naleznosci
    where nalTmp.idDziecko == idDziec
    select nalTmp).Initializer().ToList();
wode
fonte
adicionei uma seção sobre a sintaxe da consulta para completar, quando percebi que minha resposta inicial não se estendia bem. A resposta do @ yoda é provavelmente melhor em relação à sintaxe da consulta.
wode 31/03
0

Embora seja tarde para responder, ainda pode ajudar alguém em perigo. Desde o LINQ para entidades, não há suporte para construções de objetos sem parâmetros. No entanto, os métodos de projeção para IEnumerable .

Portanto, antes da seleção, basta converter seu IQueryable para IEnumerable usando este código:

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

Vai funcionar bem. No entanto, é claro, perderá os benefícios das consultas nativas.

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