L'entità non può essere costruita in una query LINQ to Entities


389

Esiste un tipo di entità chiamato prodotto che viene generato dal framework di entità. Ho scritto questa domanda

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

Il codice seguente genera il seguente errore:

"L'entità o il tipo complesso Shop.Product non può essere costruito in una query LINQ to Entities"

var products = productRepository.GetProducts(1).Tolist();

Ma quando uso select pinvece di select new Product { Name = p.Name};esso funziona correttamente.

Come posso preformare una sezione di selezione personalizzata?


System.NotSupportedException: 'L'entità o il tipo complesso' StudentInfoAjax.Models.Student 'non può essere costruito in una query LINQ to Entities.'
Md Wahid,

Risposte:


390

Non è possibile (e non si dovrebbe essere in grado di) proiettare su un'entità mappata. Tuttavia, è possibile proiettare su un tipo anonimo o su un DTO :

public class ProductDTO
{
    public string Name { get; set; }
    // Other field you may need from the Product entity
}

E il tuo metodo restituirà un elenco di DTO.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}

152
Non capisco perché non dovrei essere in grado di farlo ... Questo sarebbe molto utile ...
Jonx,

118
Bene, le entità mappate in EF rappresentano sostanzialmente le tabelle del database. Se si proietta su un'entità mappata, ciò che sostanzialmente si fa è caricare parzialmente un'entità, che non è uno stato valido. EF non avrà alcuna idea di come, ad esempio, gestire un aggiornamento di tale entità in futuro (il comportamento predefinito sarebbe probabilmente sovrascrivere i campi non caricati con valori nulli o qualsiasi cosa tu abbia nel tuo oggetto). Questa sarebbe un'operazione pericolosa, dal momento che rischieresti di perdere alcuni dei tuoi dati nel DB, pertanto non è consentito caricare parzialmente entità (o proiettare su entità mappate) in EF.
Yakimych,

26
@Yakimych ha senso, tranne se si dispone di un'entità aggregata che si sta generando / creando tramite una query e quindi si è pienamente consapevoli / si intende creare un'entità nuova di zecca da manipolare e successivamente aggiungere. In questo caso, è necessario forzare l'esecuzione della query o il push in un dto e viceversa in un'entità da aggiungere - il che è frustrante
Cargowire

16
@Cargowire - Sono d'accordo, quello scenario esiste ed è frustrante quando sai cosa stai facendo ma non ti è permesso farlo a causa di limitazioni. Tuttavia, se ciò fosse stato consentito, ci sarebbero molti sviluppatori frustrati che si lamentano della perdita dei loro dati quando, ad esempio, cercano di salvare entità parzialmente caricate. L'IMO, un errore che esplode con un sacco di rumore (generando un'eccezione, ecc.) È meglio del comportamento che può causare bug nascosti che sono difficili da rintracciare e spiegare (le cose funzionano bene prima di iniziare a notare i dati mancanti).
Yakimych,


275

È possibile proiettare in un tipo anonimo e quindi da esso al tipo di modello

public IEnumerable<Product> GetProducts(int categoryID)
{
    return (from p in Context.Set<Product>()
            where p.CategoryID == categoryID
            select new { Name = p.Name }).ToList()
           .Select(x => new Product { Name = x.Name });
}

Modifica : sarò un po 'più specifico poiché questa domanda ha suscitato molta attenzione.

Non è possibile proiettare direttamente nel tipo di modello (restrizione EF), quindi non è possibile aggirare il problema. L'unico modo è proiettare in tipo anonimo (1a iterazione), quindi in tipo modello (2a iterazione).

Si noti inoltre che quando si caricano parzialmente le entità in questo modo, non possono essere aggiornate, quindi devono rimanere staccate, così come sono.

Non ho mai capito del tutto perché questo non è possibile e le risposte su questo thread non danno forti ragioni contro di esso (soprattutto parlando di dati parzialmente caricati). È corretto che l'entità di stato parzialmente caricata non possa essere aggiornata, ma quindi questa entità verrebbe staccata, quindi non sarebbero possibili tentativi accidentali di salvarle.

Considera il metodo che ho usato sopra: abbiamo ancora un'entità modello parzialmente caricata come risultato. Questa entità è distaccata.

Considera questo (possibile esistere) codice possibile:

return (from p in Context.Set<Product>()
        where p.CategoryID == categoryID
        select new Product { Name = p.Name }).AsNoTracking().ToList();

Ciò potrebbe anche comportare un elenco di entità distaccate, quindi non avremmo bisogno di fare due iterazioni. Un compilatore sarebbe intelligente nel vedere che AsNoTracking () è stato utilizzato, il che si tradurrà in entità distaccate, quindi potrebbe permetterci di farlo. Se, tuttavia, AsNoTracking () fosse omesso, potrebbe lanciare la stessa eccezione che sta lanciando ora, per avvertirci che dobbiamo essere abbastanza specifici sul risultato che vogliamo.


3
Questa è la soluzione più pulita quando non hai bisogno / non ti preoccupi dello stato dell'entità selezionata che vuoi proiettare.
Mário Meyrelles,

2
E quando non ti interessa se restituisci IEnumerable o IQueryable;). Ma ottieni ancora il mio voto perché questa soluzione funziona per me ora.
Michael Brennt,

10
tecnicamente, la proiezione al tipo di modello si sta verificando al di fuori della query e credo che richieda anche un'iterazione aggiuntiva attraverso l'elenco. Non userò questa soluzione per il mio codice, ma è la soluzione per la domanda. lieve aumento.
1cle

4
Preferisco questo alla soluzione accettata DTO - molto più elegante e pulita
Adam Hey,

7
Solo che, con rispetto, non è in realtà una risposta alla domanda. Questa è una risposta su come eseguire una proiezione Linq To Objects, non una proiezione di query Linq to Entities. Quindi l'opzione DTO è l'unica opzione relativa a: Linq to Entities.
rism

78

C'è un altro modo in cui ho scoperto che funziona, devi creare una classe che deriva dalla tua classe di Prodotto e usarla. Per esempio:

public class PseudoProduct : Product { }

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products
           where p.CategoryID== categoryID
           select new PseudoProduct() { Name = p.Name};
}

Non sono sicuro se questo è "consentito", ma funziona.


3
Intelligente! Ho provato questo ora e funziona. Sono sicuro che in qualche modo mi brucerà.
Daniel,

5
A proposito, questo ti morde se tenti di persistere nei risultati di GetProducts () poiché EF non riesce a trovare la mappatura per PseudoProduct, ad esempio "System.InvalidOperationException: Impossibile trovare informazioni sulla mappatura e sui metadati per EntityType 'blah.PseudoProduct'".
sming

4
La migliore risposta e l'unica che risponde entro i parametri della domanda. Tutte le altre risposte cambiano il tipo restituito o eseguono prematuramente IQueryable e usano linq per gli oggetti
rdans

2
100% scioccato ha funzionato ... in EF 6.1 funziona.
TravisWhidden

2
@mejobloggs Prova l'attributo [NotMapped] sulla classe derivata o .Ignore <T> se stai usando l'API fluente.
Dunc,

37

Ecco un modo per farlo senza dichiarare la classe adizionale:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select new { Name = p.Name };
    var products = query.ToList().Select(r => new Product
    {
        Name = r.Name;
    }).ToList();

    return products;
}

Tuttavia, questo deve essere utilizzato solo se si desidera combinare più entità in una singola entità. La funzionalità di cui sopra (semplice mappatura da prodotto a prodotto) viene eseguita in questo modo:

public List<Product> GetProducts(int categoryID)
{
    var query = from p in db.Products
            where p.CategoryID == categoryID
            select p;
    var products = query.ToList();

    return products;
}

23

Un altro modo semplice :)

public IQueryable<Product> GetProducts(int categoryID)
{
    var productList = db.Products
        .Where(p => p.CategoryID == categoryID)
        .Select(item => 
            new Product
            {
                Name = item.Name
            })
        .ToList()
        .AsQueryable(); // actually it's not useful after "ToList()" :D

    return productList;
}

buon punto ho appena imparato qualcosa IQueryable con la tua bella risposta. Sarebbe stato carino se tu avessi spiegato PERCHÉ non è utile dopo una ToList () e il motivo è che non puoi usare elenchi generici in una query LINQ-to-SQL. Quindi, se sai che spingerai sempre i risultati in un'altra query dal chiamante, allora ha sicuramente senso essere IQueryable. Ma se no ... se lo userai come un elenco generico dopo, allora usa ToList () all'interno del metodo in modo da non fare un ToList () su IQueryable ogni singola chiamata a questo metodo.
Positivo

Va tutto bene amico mio. Ho solo imitato la firma del metodo della domanda, per questo motivo la converto in una query in grado ...;)
Soren

1
Funziona, il productList diventa non modificabile dopo ToList (). Come posso renderlo modificabile?
doncadavona,

Se si inserisce una .ToListquery, vengono eseguiti e estratti i dati dal server, qual è il punto per farlo di nuovo AsQueryable?
Moshii,

1
@Moshii solo per soddisfare la firma del tipo restituito dal metodo, (come ho detto nella risposta, non è più utile).
Soren,

4

Puoi usarlo e dovrebbe funzionare -> Devi usare toListprima di creare il nuovo elenco usando select:

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 

3
Ciò farebbe comunque un "SELEZIONA * DA [..]", non un "SELEZIONA nome DA [..]"
Timo Hermans,

1

In risposta all'altra domanda che è stata contrassegnata come duplicata ( vedi qui ) ho trovato una soluzione semplice e veloce basata sulla risposta di Soren:

data.Tasks.AddRange(
    data.Task.AsEnumerable().Select(t => new Task{
        creator_id   = t.ID,
        start_date   = t.Incident.DateOpened,
        end_date     = t.Incident.DateCLosed,
        product_code = t.Incident.ProductCode
        // so on...
    })
);
data.SaveChanges();

Nota: questa soluzione funziona solo se si dispone di una proprietà di navigazione (chiave esterna) sulla classe Task (qui chiamata "Incidente"). Se non lo possiedi, puoi semplicemente utilizzare una delle altre soluzioni pubblicate con "AsQueryable ()".


1

È possibile risolvere questo problema utilizzando Data Transfer Objects (DTO).

Questi sono un po 'come i modelli di visualizzazione in cui inserisci le proprietà di cui hai bisogno e puoi mapparle manualmente nel controller o utilizzando soluzioni di terze parti come AutoMapper.

Con DTO puoi:

  • Rendi i dati serializzabili (Json)
  • Sbarazzarsi di riferimenti circolari
  • Riduci il traffico di rete lasciando le proprietà che non ti servono (viewmodelwise)
  • Usa l'appiattimento degli oggetti

L'ho imparato a scuola quest'anno ed è uno strumento molto utile.


0

Se si utilizza Entity framework, provare a rimuovere la proprietà da DbContext che utilizza il modello complesso in quanto Entity I ha avuto lo stesso problema durante la mappatura di più modelli in un modello di visualizzazione denominato Entity

public DbSet<Entity> Entities { get; set; }

La rimozione della voce da DbContext ha corretto il mio errore.


0

se stai eseguendo Linq to Entitynon puoi usare il ClassTypecon newnella selectchiusura della queryonly anonymous types are allowed (new without type)

guarda questo frammento del mio progetto

//...
var dbQuery = context.Set<Letter>()
                .Include(letter => letter.LetterStatus)
                .Select(l => new {Title =l.Title,ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated,LetterStatus = new {ID = l.LetterStatusID.Value,NameInArabic = l.LetterStatus.NameInArabic,NameInEnglish = l.LetterStatus.NameInEnglish} })
                               ^^ without type__________________________________________________________________________________________________________^^ without type

di voi ha aggiunto la new keywordchiusura Seleziona anche complex propertiesse si ottiene questo errore

quindi removela ClassTypes from newparola chiave sulle Linq to Entityquery ,,

perché verrà trasformato in istruzione sql ed eseguito su SqlServer

quindi quando posso usare new with typesalla selectchiusura?

puoi usarlo se hai a che fare LINQ to Object (in memory collection)

//opecations in tempList , LINQ to Entities; so we can not use class types in select only anonymous types are allowed
var tempList = dbQuery.Skip(10).Take(10).ToList();// this is list of <anonymous type> so we have to convert it so list of <letter>

//opecations in list , LINQ to Object; so we can use class types in select
list = tempList.Select(l => new Letter{ Title = l.Title, ID = l.ID, LastModificationDate = l.LastModificationDate, DateCreated = l.DateCreated, LetterStatus = new LetterStatus{ ID = l.LetterStatus.ID, NameInArabic = l.LetterStatus.NameInArabic, NameInEnglish = l.LetterStatus.NameInEnglish } }).ToList();
                                ^^^^^^ with type 

dopo che ho eseguito la ToListquery è diventato in memory collection così possiamo usarlo new ClassTypesin select


Sicuro che puoi usare tipi anonimi, ma non puoi creare un'entità all'interno della query LINQ, anche per impostare un membro anonimo, perché LINQ-to-Entity genera ancora la stessa eccezione.
Suncat2000,

0

In molti casi, la trasformazione non è necessaria. Pensa per il motivo per cui desideri fortemente l'Elenco e valuta se desideri solo i dati, ad esempio in un servizio Web o per visualizzarli. Non importa il tipo. Hai solo bisogno di sapere come leggerlo e verificare che sia identico alle proprietà definite nel tipo anonimo che hai definito. Questo è lo scenario ottimale, causa qualcosa che non ti serve tutti i campi di un'entità, e questo è il motivo per cui esiste un tipo anonimo.

Un modo semplice è farlo:

IEnumerable<object> list = dataContext.Table.Select(e => new { MyRequiredField = e.MyRequiredField}).AsEnumerable();

0

Non ti permetterà di mappare di nuovo sul Prodotto poiché quella è la tua tabella su cui stai interrogando. È necessaria una funzione anonima, quindi è possibile aggiungerla a un ViewModel e aggiungere ogni ViewModel a un List<MyViewModel>e restituirli. È una leggera digressione, ma includo avvertenze sulla gestione di date nulle perché sono un dolore alla schiena da affrontare, nel caso in cui tu ne abbia. È così che l'ho gestito.

Spero che tu abbia un ProductViewModel:

public class ProductViewModel
{
    [Key]
    public string ID { get; set; }
    public string Name { get; set; }
}

Ho un framework di iniezione / repository di dipendenze in cui chiamo una funzione per acquisire i miei dati. Usando il tuo post come esempio, nella tua chiamata alla funzione Controller, sarebbe simile al seguente:

int categoryID = 1;
var prods = repository.GetProducts(categoryID);

Nella classe repository:

public IEnumerable<ProductViewModel> GetProducts(int categoryID)
{
   List<ProductViewModel> lstPVM = new List<ProductViewModel>();

   var anonymousObjResult = from p in db.Products
                            where p.CategoryID == categoryID 
                            select new
                            {
                                CatID = p.CategoryID,
                                Name = p.Name
                            };

        // NOTE: If you have any dates that are nullable and null, you'll need to
        // take care of that:  ClosedDate = (DateTime?)p.ClosedDate ?? DateTime.Now

        // If you want a particular date, you have to define a DateTime variable,
        // assign your value to it, then replace DateTime.Now with that variable. You
        // cannot call a DateTime.Parse there, unfortunately. 
        // Using 
        //    new Date("1","1","1800"); 
        // works, though. (I add a particular date so I can edit it out later.)

        // I do this foreach below so I can return a List<ProductViewModel>. 
        // You could do: return anonymousObjResult.ToList(); here
        // but it's not as clean and is an anonymous type instead of defined
        // by a ViewModel where you can control the individual field types

        foreach (var a in anonymousObjResult)
        {                
            ProductViewModel pvm = new ProductViewModel();
            pvm.ID = a.CatID;  
            pvm.Name = a.Name;
            lstPVM.Add(rvm);
        }

        // Obviously you will just have ONE item there, but I built it 
        // like this so you could bring back the whole table, if you wanted
        // to remove your Where clause, above.

        return lstPVM;
    }

Di nuovo nel controller, fai:

 List<ProductViewModel> lstProd = new List<ProductViewModel>();

 if (prods != null) 
 {
    // For setting the dates back to nulls, I'm looking for this value:
    // DateTime stdDate = DateTime.Parse("01/01/1800");

    foreach (var a in prods)
    {
        ProductViewModel o_prod = new ReportViewModel();
        o_prod.ID = a.ID;
        o_prod.Name = a.Name;
       // o_prod.ClosedDate = a.ClosedDate == stdDate ? null : a.ClosedDate;
        lstProd.Add(o_prod);
    }
}
return View(lstProd);  // use this in your View as:   @model IEnumerable<ProductViewModel>

-1

aggiungi solo AsEnumerable ():

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

8
MAI farlo! Questo recupererà tutti i dati dal DB e quindi farà la selezione.
Gh61,

1
Questo è il motivo per cui in alcune società è vietato l'uso di Linq.
Hakan,

-2

puoi aggiungere AsEnumerable alla tua raccolta come il seguente:

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}

Perché questa è una cattiva risposta anche se funziona ... .AsEnumerable termina linq alle entità. La clausola Where e tutto il resto viene gestita al di fuori di linq per le entità. cioè ogni prodotto viene recuperato e poi filtrato da linq agli oggetti. A parte questo, è praticamente la stessa della risposta .Lista sopra. stackoverflow.com/questions/5311034/...
KenF

1
Il problema con questo è che è solo un selezionare * da ... eseguito, non selezionare un nuovo prodotto {Nome = p.Nome}, poiché otterrai anche un riferimento ciclico. E vuoi solo il nome.
Sterling Diaz
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.