Die Entität kann nicht in einer LINQ to Entities-Abfrage erstellt werden

389

Es gibt einen Entitätstyp namens Produkt, der vom Entitätsframework generiert wird. Ich habe diese Anfrage geschrieben

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

Der folgende Code löst den folgenden Fehler aus:

"Die Entität oder der komplexe Typ Shop.Product kann nicht in einer LINQ to Entities-Abfrage erstellt werden."

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

Aber wenn ich select pstattdessen benutze select new Product { Name = p.Name};, funktioniert es richtig.

Wie kann ich einen benutzerdefinierten Auswahlabschnitt erstellen?

Ghooti Farangi
quelle
System.NotSupportedException: 'Die Entität oder der komplexe Typ' StudentInfoAjax.Models.Student 'kann nicht in einer LINQ to Entities-Abfrage erstellt werden.'
Md Wahid

Antworten:

390

Sie können (und sollten) nicht auf eine zugeordnete Entität projizieren. Sie können jedoch auf einen anonymen Typ oder auf ein DTO projizieren :

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

Und Ihre Methode gibt eine Liste der DTOs zurück.

public List<ProductDTO> GetProducts(int categoryID)
{
    return (from p in db.Products
            where p.CategoryID == categoryID
            select new ProductDTO { Name = p.Name }).ToList();
}
Yakimych
quelle
152
Ich verstehe nicht, warum ich das nicht tun sollte ... Das wäre sehr nützlich ...
Jonx
118
Nun, zugeordnete Entitäten in EF repräsentieren im Grunde Datenbanktabellen. Wenn Sie auf eine zugeordnete Entität projizieren, laden Sie im Grunde genommen teilweise eine Entität, die kein gültiger Status ist. EF hat keine Ahnung, wie z. B. in Zukunft mit einer Aktualisierung einer solchen Entität umgegangen werden soll (das Standardverhalten würde wahrscheinlich darin bestehen, die nicht geladenen Felder mit Nullen oder was auch immer Sie in Ihrem Objekt haben, zu überschreiben). Dies wäre eine gefährliche Operation, da Sie riskieren würden, einige Ihrer Daten in der Datenbank zu verlieren. Daher ist es nicht zulässig, Entitäten in EF teilweise zu laden (oder auf zugeordnete Entitäten zu projizieren).
Yakimych
26
@Yakimych, das ist sinnvoll, es sei denn, Sie haben eine aggregierte Entität, die Sie über eine Abfrage generieren / erstellen, und sind sich daher voll bewusst / beabsichtigen, eine brandneue Entität zu erstellen, die Sie dann bearbeiten und später hinzufügen werden. In diesem Fall müssen Sie entweder die Ausführung der Abfrage erzwingen oder in ein dto und zurück in eine Entität drücken, um - was frustrierend ist
Cargowire
16
@Cargowire - Ich stimme zu, dass dieses Szenario existiert, und es ist frustrierend, wenn Sie wissen, was Sie tun, es aber aufgrund von Einschränkungen nicht dürfen. Wäre dies jedoch zulässig gewesen, würden sich viele frustrierte Entwickler darüber beschweren, dass ihre Daten verloren gehen, wenn sie beispielsweise versuchen, teilweise geladene Entitäten zu speichern. IMO, ein Fehler, der mit viel Rauschen in die Luft sprengt (eine Ausnahme auslösen usw.), ist besser als ein Verhalten, das versteckte Fehler verursachen kann, die schwer aufzuspüren und zu erklären sind (Dinge funktionieren gut, bevor Sie fehlende Daten bemerken).
Yakimych
14
DTO - Datenübertragungsobjekte
Tkerwood
275

Sie können in einen anonymen Typ und dann in einen Modelltyp projizieren

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

Edit : Ich werde etwas genauer sein, da diese Frage viel Aufmerksamkeit bekommen hat.

Sie können nicht direkt in den Modelltyp projizieren (EF-Einschränkung), daher führt kein Weg daran vorbei. Die einzige Möglichkeit besteht darin, in einen anonymen Typ (1. Iteration) und dann in einen Modelltyp (2. Iteration) zu projizieren.

Beachten Sie auch, dass beim teilweisen Laden von Entitäten auf diese Weise diese nicht aktualisiert werden können. Daher sollten sie unverändert bleiben.

Ich habe nie ganz verstanden, warum dies nicht möglich ist, und die Antworten in diesem Thread geben keine starken Gründe dafür (meistens über teilweise geladene Daten). Es ist richtig, dass in einem teilweise geladenen Zustand die Entität nicht aktualisiert werden kann, diese Entität jedoch getrennt wird, sodass versehentliche Versuche, sie zu speichern, nicht möglich sind.

Betrachten Sie die oben verwendete Methode: Als Ergebnis haben wir immer noch eine teilweise geladene Modellentität. Diese Entität ist getrennt.

Betrachten Sie diesen (Wunsch zu existieren) möglichen Code:

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

Dies könnte auch zu einer Liste getrennter Entitäten führen, sodass wir keine zwei Iterationen durchführen müssten. Ein Compiler wäre schlau zu sehen, dass AsNoTracking () verwendet wurde, was zu getrennten Entitäten führt, sodass wir dies tun könnten. Wenn jedoch AsNoTracking () weggelassen wurde, könnte es dieselbe Ausnahme auslösen wie jetzt, um uns zu warnen, dass wir genau genug über das gewünschte Ergebnis sein müssen.

Goran
quelle
3
Dies ist die sauberste Lösung, wenn Sie den Status der ausgewählten Entität, die Sie projizieren möchten, nicht benötigen / sich nicht darum kümmern.
Mário Meyrelles
2
Und wenn es dir egal ist, ob du IEnumerable oder IQueryable zurückgibst;). Aber du bekommst trotzdem meine Gegenstimme, weil diese Lösung jetzt für mich funktioniert.
Michael Brennt
10
Technisch gesehen erfolgt die Projektion auf den Modelltyp außerhalb der Abfrage, und ich glaube, dass auch eine zusätzliche Iteration durch die Liste erforderlich ist. Ich werde diese Lösung nicht für meinen Code verwenden, aber sie ist eine Lösung für die Frage. Uptick.
1c1cle
4
Ich ziehe dies der akzeptierten DTO-Lösung vor - viel eleganter und sauberer
Adam Hey
7
Abgesehen davon, dass es in Bezug auf Respekt keine Antwort auf die Frage ist. Dies ist eine Antwort darauf, wie eine Linq To Objects-Projektion und keine Linq To Entities-Abfrageprojektion durchgeführt wird. Daher ist die DTO-Option die einzige Option für: Linq to Entities.
Rism
78

Es gibt eine andere Möglichkeit, die ich gefunden habe. Sie müssen eine Klasse erstellen, die von Ihrer Produktklasse abgeleitet ist, und diese verwenden. Zum Beispiel:

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

Ich bin mir nicht sicher, ob dies "erlaubt" ist, aber es funktioniert.

Tomasz Iniewicz
quelle
3
Klug! Versuchte dies jetzt und es funktioniert. Ich bin mir sicher, dass es mich irgendwie verbrennen wird.
Daniel
5
Übrigens beißt Sie dies, wenn Sie versuchen, die Ergebnisse von GetProducts () beizubehalten, da EF die Zuordnung für PseudoProduct nicht finden kann, z. B. "System.InvalidOperationException: Zuordnungs- und Metadateninformationen konnten für EntityType 'blah.PseudoProduct' nicht gefunden werden".
sming
4
Beste Antwort und die einzige, die innerhalb der Parameter der Frage antwortet. Alle anderen Antworten ändern den Rückgabetyp oder führen die IQueryable vorzeitig aus und verwenden linq für Objekte
rdans
2
100% schockiert hat es funktioniert ... in EF 6.1 funktioniert dies.
TravisWhidden
2
@mejobloggs Versuchen Sie das Attribut [NotMapped] für die abgeleitete Klasse oder .Ignore <T>, wenn Sie die fließende API verwenden.
Dunc
37

Hier ist eine Möglichkeit, dies zu tun, ohne eine zusätzliche Klasse zu deklarieren:

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

Dies ist jedoch nur zu verwenden, wenn Sie mehrere Entitäten in einer einzigen Entität kombinieren möchten. Die oben genannten Funktionen (einfache Zuordnung von Produkt zu Produkt) werden folgendermaßen ausgeführt:

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;
}
Bojan Hrnkas
quelle
23

Ein anderer einfacher Weg :)

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;
}
Soren
quelle
Guter Punkt, ich habe gerade mit Ihrer netten Antwort etwas IQueryable gelernt. Es wäre jedoch schön gewesen, wenn Sie erklärt hätten, WARUM es nach einer ToList () nicht nützlich ist, und der Grund dafür ist, dass Sie in einer LINQ-to-SQL-Abfrage keine generischen Listen verwenden können. Wenn Sie also wissen, dass Sie die Ergebnisse immer in eine andere Abfrage des Anrufers verschieben, ist es auf jeden Fall sinnvoll, IQueryable zu sein. Aber wenn nicht ... wenn Sie es später als generische Liste verwenden möchten, verwenden Sie die ToList () in der Methode, damit Sie nicht bei jedem Aufruf dieser Methode eine ToList () für IQueryable ausführen.
PositiveGuy
Du bist in Ordnung, mein Freund. Ich ahme nur die Signatur der Fragemethode nach, deshalb konvertiere ich sie in eine abfragefähige ...;)
Soren
1
Dies funktioniert, die Produktliste kann nach der ToList () nicht mehr bearbeitet werden. Wie kann ich es bearbeitbar machen?
Doncadavona
Wenn Sie eine .ToListAbfrage eingeben, werden diese ausgeführt und Daten vom Server abgerufen. Was ist dann der Sinn, um sie erneut zu erstellen AsQueryable?
Moshii
1
@Moshii nur um die Signatur des Rückgabetyps der Methode zu erfüllen (wie ich in der Antwort sagte, ist es nicht mehr nützlich).
Soren
4

Sie können dies verwenden und es sollte funktionieren -> Sie müssen es verwenden, toListbevor Sie die neue Liste mit select erstellen :

db.Products
    .where(x=>x.CategoryID == categoryID).ToList()
    .select(x=>new Product { Name = p.Name}).ToList(); 
Mohamed Adam
quelle
3
Dies würde jedoch immer noch ein 'SELECT * FROM [..]' machen, kein 'SELECT name FROM [..]'
Timo Hermans
1

Als Antwort auf die andere Frage, die als Duplikat markiert wurde ( siehe hier ), fand ich eine schnelle und einfache Lösung basierend auf der Antwort von 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();

Hinweis: Diese Lösung funktioniert nur, wenn Sie über eine Navigationseigenschaft (Fremdschlüssel) in der Task-Klasse verfügen (hier als "Vorfall" bezeichnet). Wenn Sie das nicht haben, können Sie einfach eine der anderen veröffentlichten Lösungen mit "AsQueryable ()" verwenden.

JollyBrackets
quelle
1

Sie können dies mithilfe von DTOs (Data Transfer Objects) lösen.

Dies sind ein bisschen wie Ansichtsmodelle, in denen Sie die gewünschten Eigenschaften eingeben und diese manuell in Ihrem Controller oder mithilfe von Lösungen von Drittanbietern wie AutoMapper zuordnen können.

Mit DTOs können Sie:

  • Daten serialisierbar machen (Json)
  • Entfernen Sie Zirkelverweise
  • Reduzieren Sie den Netzwerkverkehr, indem Sie nicht benötigte Eigenschaften belassen (Ansichtsmodell)
  • Verwenden Sie die Objektabflachung

Ich habe dies dieses Jahr in der Schule gelernt und es ist ein sehr nützliches Werkzeug.

Jelman
quelle
0

Wenn Sie das Entity-Framework verwenden, entfernen Sie die Eigenschaft aus DbContext, das Ihr komplexes Modell als Entity verwendet. Ich hatte das gleiche Problem, als ich mehrere Modelle einem Ansichtsmodell mit dem Namen Entity zuordnete

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

Das Entfernen des Eintrags aus DbContext hat meinen Fehler behoben.

vikas suhag
quelle
0

Wenn Sie ausführen Linq to Entity, können Sie das ClassTypewith nicht newzum selectSchließen der Abfrage verwendenonly anonymous types are allowed (new without type)

Schauen Sie sich diesen Ausschnitt meines Projekts an

//...
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

von Ihnen haben die new keywordin Select-Schließung hinzugefügt, auch wenn complex propertiesSie diesen Fehler erhalten

also removedas ClassTypes from newSchlüsselwort bei Linq to EntityAbfragen ,,

weil es in eine SQL-Anweisung umgewandelt und auf SqlServer ausgeführt wird

so , wenn kann ich new with typesauf selectSchließung?

Sie können es verwenden, wenn Sie es zu tun haben 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 

Nachdem ich ToListauf Abfrage ausgeführt wurde, wurde es in memory collection so, dass wir new ClassTypesin select verwenden können

Basheer AL-MOMANI
quelle
Natürlich können Sie anonyme Typen verwenden, aber Sie können keine Entität innerhalb der LINQ-Abfrage erstellen, auch nicht, um ein anonymes Mitglied festzulegen, da LINQ-to-Entities immer noch dieselbe Ausnahme auslösen.
Suncat2000
0

In vielen Fällen wird die Transformation nicht benötigt. Überlegen Sie, aus welchem ​​Grund Sie die stark eingegebene Liste verwenden möchten, und bewerten Sie, ob Sie die Daten beispielsweise nur in einem Webdienst oder zum Anzeigen der Daten verwenden möchten. Es spielt keine Rolle, um welchen Typ es sich handelt. Sie müssen nur wissen, wie man es liest und überprüfen, ob es mit den Eigenschaften identisch ist, die in dem von Ihnen definierten anonymen Typ definiert sind. Dies ist das optimale Szenario, da Sie nicht alle Felder einer Entität benötigen und deshalb ein anonymer Typ vorhanden ist.

Ein einfacher Weg ist dies:

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

Sie können das Produkt nicht wieder zuordnen, da dies Ihre Tabelle ist, die Sie abfragen. Sie benötigen eine anonyme Funktion, können sie dann einem ViewModel hinzufügen und jedes ViewModel einem hinzufügen List<MyViewModel>und diese zurückgeben. Es ist ein kleiner Exkurs, aber ich schließe Vorbehalte zum Umgang mit nullbaren Daten ein, da dies ein Problem für Sie ist, nur für den Fall, dass Sie welche haben. So habe ich damit umgegangen.

Hoffentlich haben Sie eine ProductViewModel:

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

Ich habe ein Abhängigkeitsinjektions- / Repository-Framework, in dem ich eine Funktion zum Abrufen meiner Daten aufrufe. Am Beispiel Ihres Beitrags in Ihrem Controller-Funktionsaufruf würde dies folgendermaßen aussehen:

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

In der Repository-Klasse:

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

Zurück in der Steuerung tun Sie Folgendes:

 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>
vapcguy
quelle
-1

füge nur AsEnumerable () hinzu:

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
HamidReza
quelle
8
Mache das niemals! Dadurch werden alle Daten aus der Datenbank abgerufen und anschließend ausgewählt.
Gh61
1
Aus diesem Grund ist die Verwendung von Linq in einigen Unternehmen verboten.
Hakan
-2

Sie können AsEnumerable wie folgt zu Ihrer Sammlung hinzufügen:

public IQueryable<Product> GetProducts(int categoryID)
{
    return from p in db.Products.AsEnumerable()
           where p.CategoryID== categoryID
           select new Product { Name = p.Name};
}
HamidReza
quelle
Warum dies eine schlechte Antwort ist, obwohl es funktioniert ... .AsEnumerable beendet linq to entity. Die Where-Klausel und alles andere wird außerhalb von linq to Entities behandelt. dh jedes Produkt wird abgerufen und dann von linq nach Objekten gefiltert. Abgesehen davon ist es ziemlich genau das gleiche wie die obige .ToList-Antwort. stackoverflow.com/questions/5311034/…
KenF
1
Das Problem dabei ist, dass nur eine Auswahl * von ... durchgeführt wird, kein neues Produkt {Name = p.Name} ausgewählt wird, da Sie auch eine zyklische Referenz erhalten. Und du willst nur den Namen.
Sterling Diaz