Join / Where mit LINQ und Lambda

458

Ich habe Probleme mit einer in LINQ und Lambda geschriebenen Abfrage. Bisher bekomme ich viele Fehler. Hier ist mein Code:

int id = 1;
var query = database.Posts.Join(database.Post_Metas,
                                post => database.Posts.Where(x => x.ID == id),
                                meta => database.Post_Metas.Where(x => x.Post_ID == id),
                                (post, meta) => new { Post = post, Meta = meta });

Ich bin neu in der Verwendung von LINQ, daher bin ich mir nicht sicher, ob diese Abfrage korrekt ist.

David
quelle
11
Was versuchst du zu erreichen?
Germán Rodríguez
4
Was soll die Abfrage in einem Satz tun?
Jäger
6
Ihre Schlüsselauswahl ist viel zu kompliziert. Wenn Sie nach ID auswählen möchten, ist nur x => x.ID in Ordnung.
Eric Lippert
1
Ich wollte einen Beitrag aus der Datenbank und die Metadaten für diesen Beitrag erhalten.
David

Antworten:

1057

Ich finde, wenn Sie mit der SQL-Syntax vertraut sind, ist die Verwendung der LINQ-Abfragesyntax viel klarer, natürlicher und erleichtert das Erkennen von Fehlern:

var id = 1;
var query =
   from post in database.Posts
   join meta in database.Post_Metas on post.ID equals meta.Post_ID
   where post.ID == id
   select new { Post = post, Meta = meta };

Wenn Sie sich jedoch wirklich für die Verwendung von Lambdas interessieren, ist Ihre Syntax ziemlich unterschiedlich. Hier ist dieselbe Abfrage unter Verwendung der LINQ-Erweiterungsmethoden:

var id = 1;
var query = database.Posts    // your starting point - table in the "from" statement
   .Join(database.Post_Metas, // the source table of the inner join
      post => post.ID,        // Select the primary key (the first part of the "on" clause in an sql "join" statement)
      meta => meta.Post_ID,   // Select the foreign key (the second part of the "on" clause)
      (post, meta) => new { Post = post, Meta = meta }) // selection
   .Where(postAndMeta => postAndMeta.Post.ID == id);    // where statement
Daniel Schaffer
quelle
10
@Emanuele Greco in Bezug auf Ihre Bearbeitung: "Die Gleichheit für ID-Felder ist in der JOIN-Bedingung festgelegt. Sie müssen die WHERE-Klausel nicht verwenden!": Die WHERE-Klausel testet nicht die Gleichheit zwischen ID-Feldern, sondern die Gleichheit zwischen der Post-ID Spalte und der außerhalb der Abfrage deklarierte Parameter id.
Daniel Schaffer
9
Fantastisches Stück lambdaund ist Zitat einfach zu bedienen und zu verstehen
Piotr Kula
1
tolles Beispiel
Spielzeug
1
Manchmal werden Erklärungen zu Lambda in Lambda geschrieben. Gut erklärt.
Prise
80

Sie könnten damit zwei Wege gehen. Mit LINQPad (von unschätzbarem Wert, wenn Sie neu in LINQ sind) und einer Dummy-Datenbank habe ich die folgenden Abfragen erstellt:

Posts.Join(
    Post_metas,
    post => post.Post_id,
    meta => meta.Post_id,
    (post, meta) => new { Post = post, Meta = meta }
)

oder

from p in Posts
join pm in Post_metas on p.Post_id equals pm.Post_id
select new { Post = p, Meta = pm }

In diesem speziellen Fall denke ich, dass die LINQ-Syntax sauberer ist (ich wechsle zwischen den beiden, je nachdem, welche am einfachsten zu lesen ist).

Ich möchte jedoch darauf hinweisen, dass Sie, wenn Sie geeignete Fremdschlüssel in Ihrer Datenbank haben (zwischen post und post_meta), wahrscheinlich keinen expliziten Join benötigen, es sei denn, Sie versuchen, eine große Anzahl von Datensätzen zu laden . Ihr Beispiel scheint darauf hinzudeuten, dass Sie versuchen, einen einzelnen Beitrag und seine Metadaten zu laden. Angenommen, es gibt viele post_meta-Datensätze für jeden Beitrag, dann können Sie Folgendes tun:

var post = Posts.Single(p => p.ID == 1);
var metas = post.Post_metas.ToList();

Wenn Sie das n + 1-Problem vermeiden möchten, können Sie LINQ explizit an SQL anweisen, alle zugehörigen Elemente auf einmal zu laden (obwohl dies ein fortgeschrittenes Thema sein kann, wenn Sie mit L2S besser vertraut sind). Im folgenden Beispiel heißt es: "Wenn Sie einen Beitrag laden, laden Sie auch alle ihm zugeordneten Datensätze über den Fremdschlüssel, der durch die Eigenschaft 'Post_metas' dargestellt wird."

var dataLoadOptions = new DataLoadOptions();
dataLoadOptions.LoadWith<Post>(p => p.Post_metas);

var dataContext = new MyDataContext();
dataContext.LoadOptions = dataLoadOptions;

var post = Posts.Single(p => p.ID == 1); // Post_metas loaded automagically

Es ist möglich, viele LoadWithAnrufe mit einem einzigen Satz DataLoadOptionsfür denselben Typ oder mit vielen verschiedenen Typen zu tätigen . Wenn Sie dies jedoch häufig tun, möchten Sie möglicherweise nur das Caching in Betracht ziehen.

Damian Powell
quelle
1
LinqPad und CRM 2016 ?
Kiquenet
49

Daniel hat eine gute Erklärung für die Syntaxbeziehungen, aber ich habe dieses Dokument für mein Team zusammengestellt, um es für sie etwas einfacher zu verstehen. Hoffe das hilft jemandemGeben Sie hier die Bildbeschreibung ein

Talspaugh27
quelle
Das funktioniert nicht, wenn Sie sich einfach mit einer Liste von Werten befassen, wie wir sie hier sind. Es gibt keine id-Eigenschaft für das Objekt.
Talspaugh27
Ich fand das wirklich nützlich, aber ich habe einen Fehler erhalten, bei dem ich die Verbindungsspalte hinzufügen musste. Wenn Sie sich auch die Antwort von @Mark Byers ansehen, enthält die Verknüpfungsspalte das Post_IDFeld im zweiten Alias meta => meta.Post_ID. In dem Beispiel in dieser Abbildung wird der g.idTeil der ursprünglichen select-Anweisung JOIN gStatus g on g.idnicht im endgültigen Lambda-Ausdruck repliziert.
SausageFingers
3
Ich habe nicht versucht, dies als Referenz auf die tatsächliche Linq zu veröffentlichen, die für die Beantwortung durch das OP erforderlich ist. Es war eher eine Referenz zum Verschieben von SQL in ein Linq-Format, sodass meine Eingaben etwas anders waren als die ursprüngliche Frage. Wenn ich eine Klasse für die gStatus-Werte erstellt hätte, hätte ich eine id-Eigenschaft darauf gesetzt und dann hätte sie sich mit g => g.id verbunden. Ich habe eine Liste von Werten verwendet, um zu versuchen, den Code so einfach wie möglich zu halten.
Talspaugh27
@ Talspaugh27 Warum wird die SQL-Abfrage in g.id auf g.id verknüpft? Ist das ein Fehler oder eine Absicht?
Drammy
@Drammy In einer SQL-Tabelle muss jede Spalte einen Namen haben. Da dies also eine 1-Spalten-Tabelle war, die ausschließlich zum Speichern dieser IDs dient, habe ich nur eine Spalte mit dem Namen id verwendet. Die Liste <int> hat dieses Problem nicht. Wenn ich es als solches eingerichtet und public class IdHolder{ int id } dann dieses Objekt im gStatus verwendet List<IdHolder> gStatus = new List<IdHolder>(); gStatus.add(new IdHolder(){id = 7}); gStatus.add(new IdHolder(){id = 8}); hätte, hätte es die Linq so geändert, dass t =>t.value.TaskStatusId, g=>g.id diese Änderung sinnvoll ist.
Talspaugh27
37

Ihre Schlüsselauswahl ist falsch. Sie sollten ein Objekt vom Typ der betreffenden Tabelle nehmen und den Schlüssel zurückgeben, der im Join verwendet werden soll. Ich denke du meinst das:

var query = database.Posts.Join(database.Post_Metas,
                                post => post.ID,
                                meta => meta.Post_ID,
                                (post, meta) => new { Post = post, Meta = meta });

Sie können die where-Klausel anschließend anwenden, nicht als Teil der Schlüsselauswahl.

Mark Byers
quelle
9

Posting, weil ich beim Starten von LINQ + EntityFramework einen Tag lang auf diese Beispiele gestarrt habe.

Wenn Sie EntityFramework verwenden und eine Navigationseigenschaft Metafür Ihr Modellobjekt Posteingerichtet haben, ist dies sehr einfach. Worauf warten Sie, wenn Sie eine Entität verwenden und diese Navigationseigenschaft nicht haben?

database
  .Posts
  .Where(post => post.ID == id)
  .Select(post => new { post, post.Meta });

Wenn Sie zuerst Code ausführen, richten Sie die Eigenschaft folgendermaßen ein:

class Post {
  [Key]
  public int ID {get; set}
  public int MetaID { get; set; }
  public virtual Meta Meta {get; set;}
}
Andy V.
quelle
5

Ich habe so etwas gemacht;

var certificationClass = _db.INDIVIDUALLICENSEs
    .Join(_db.INDLICENSECLAsses,
        IL => IL.LICENSE_CLASS,
        ILC => ILC.NAME,
        (IL, ILC) => new { INDIVIDUALLICENSE = IL, INDLICENSECLAsse = ILC })
    .Where(o => 
        o.INDIVIDUALLICENSE.GLOBALENTITYID == "ABC" &&
        o.INDIVIDUALLICENSE.LICENSE_TYPE == "ABC")
    .Select(t => new
        {
            value = t.PSP_INDLICENSECLAsse.ID,
            name = t.PSP_INDIVIDUALLICENSE.LICENSE_CLASS,                
        })
    .OrderBy(x => x.name);
Mahib
quelle
4

Es könnte so etwas sein

var myvar = from a in context.MyEntity
            join b in context.MyEntity2 on a.key equals b.key
            select new { prop1 = a.prop1, prop2= b.prop1};
Pepitomb
quelle
1

1 entspricht 1 zwei verschiedenen Tabellenverknüpfungen

var query = from post in database.Posts
            join meta in database.Post_Metas on 1 equals 1
            where post.ID == id
            select new { Post = post, Meta = meta };
mtngunay
quelle
1

Diese linq-Abfrage sollte für Sie funktionieren. Es werden alle Beiträge mit Post-Meta abgerufen.

var query = database.Posts.Join(database.Post_Metas,
                                post => post.postId, // Primary Key
                                meta => meat.postId, // Foreign Key
                                (post, meta) => new { Post = post, Meta = meta });

Äquivalente SQL-Abfrage

Select * FROM Posts P
INNER JOIN Post_Metas pm ON pm.postId=p.postId
Ahamed Ishak
quelle
Sie haben die Klammern geschlossen, in denen nach dem dritten Parameter ... "Keine Überladung für Join
erfordert
3
Dies ist identisch mit der akzeptierten Antwort und 7 Jahre später -1
reggaeguitar