none
Entity Framework Doppelte Einträge bei Suche in Subtabelle RRS feed

  • Frage

  • Hallo zusammen,

    ich stehe vor folgenden Problem:

    Ich habe eine Tabelle Compendium und eine Tabelle Tag. Diese sind beide miteinander verknüpft und haben eine * zu * Beziehung.

    Ich rufe die Daten mit: db.Tags.Where(p => parameters.Contains(p.Name)) ab.

    Wenn ich jetzt eine Abfrage starte die 2 Tags beinhaltet und ein Eintrag in Compendium auch diese 2 Tags besitzt, bekomme ich 2 Einträge zurück. Dies möchte ich gerne Filtern und auf einen beschränken, ich habe es mit GroupBy, Distict und über die Where Bedingung versucht.

    Komme jedoch nie zu einem brauchbaren Ergebniss (Es wird immer ein leeres Objekt übergeben).

    Gibt es eine Möglichkeit dies noch über die SQL Abfrage mit EF laufen zu lassen oder muss ich dies im nach hinein noch einmal filtern?

    Vielen Dank für eure Antworten und einen schönen AT euch.

    Grüße

    akuller 

    Donnerstag, 30. Januar 2014 00:59

Antworten

  • Hallo akuller,

    wenn ich dich richtig verstehe, besteht zwischen den Tabellen Tag und Compendium eine n-zu-n-Beziehung. Eine Möglichkeit dein Problem zu lösen, wäre folgende LINQ-Query:

    string[] parameters = {"Tag1", "Tag2", "Tag3"};
    var result = db.Compendiums.Where(c => c.Tags.Count(t => parameters.Contains(t.Name)) > 0);

    In result sind jetzt alle Compendiums gespeichert, die mit einem folgender Tags versehen sind: Tag1, Tag2 oder Tag3. Auch wenn ein Compendium mit mehr als einem der Tags versehen ist, erscheint es nur einmal in der Liste.

    Grundlage dafür ist, dass du eine Navigation-Property Tags in der Compendium-Entity hast.

    Die SQL-Abfrage, die LINQ hier erzeugt sieht ungefähr so aus (CompendiumTag ist die Tabelle, in der die Beziehungen zwischen den beiden Tabellen gespeichert werden):

    SELECT 
    [Compendium].[Id] AS [Id], 
    [Compendium].[Name] AS [Name]
    FROM [dbo].[Compendium]
    WHERE  EXISTS (
    	SELECT 
    		1 AS [C1]
    		FROM  [dbo].[CompendiumTag]
    		INNER JOIN [dbo].[Tag] ON [Tag].[Id] = [CompendiumTag].[Tags_Id]
    		WHERE 
    			([Compendium].[Id] = [CompendiumTag].[Compendiums_Id]) AND 
    			([Tag].[Name] IN (N'Tag1',N'Tag2',N'Tag3')
    	)
    )

    EDIT: 

    Elmar Boyes Vorschlag ist auf jeden Fall sinnvoll. Das Ganze würde in deinem Fall dann so aussehen:

    string[] parameters = {"Tag1", "Tag2", "Tag3"};
    var result = db.Compendiums.Where(c => c.Tags.Any(t => parameters.Contains(t.Name)));
    

    Viele Grüße,
    Thomas Fröhle
    App-Entwickler-Hotline für MSDN Online Deutschland

    Disclaimer:
    Bitte haben Sie Verständnis dafür, dass wir hier auf Rückfragen gar nicht oder nur sehr zeitverzögert antworten können.
    Bitte nutzen Sie für Rückfragen oder neue Fragen den telefonischen Weg über die App-Entwickler-Hotline: http://www.msdn-online.de/Hotline
    MSDN Hotline: Schnelle & kompetente Hilfe für Entwickler: kostenfrei!

    Es gelten für die App-Entwickler-Hotline und dieses Posting diese Nutzungsbedingungen, Hinweise zu MarkenzeichenInformationen zur Datensicherheit sowie die gesonderten Nutzungsbedingungen für die App-Entwickler-Hotline.


    • Als Antwort markiert A.Kuller Donnerstag, 30. Januar 2014 17:36
    • Bearbeitet Thomas Fröhle Donnerstag, 30. Januar 2014 22:45
    Donnerstag, 30. Januar 2014 16:15

Alle Antworten

  • Hi,

    poste doch bitte mal dein Linq Statement. Und Grob den Aufbau der beiden Tabellen mit Beispieldaten. GroupBy sollte eigendlich funktioniern.

    MFG

    Björn

    Donnerstag, 30. Januar 2014 10:13
  • Hallo akuller,

    wenn ich dich richtig verstehe, besteht zwischen den Tabellen Tag und Compendium eine n-zu-n-Beziehung. Eine Möglichkeit dein Problem zu lösen, wäre folgende LINQ-Query:

    string[] parameters = {"Tag1", "Tag2", "Tag3"};
    var result = db.Compendiums.Where(c => c.Tags.Count(t => parameters.Contains(t.Name)) > 0);

    In result sind jetzt alle Compendiums gespeichert, die mit einem folgender Tags versehen sind: Tag1, Tag2 oder Tag3. Auch wenn ein Compendium mit mehr als einem der Tags versehen ist, erscheint es nur einmal in der Liste.

    Grundlage dafür ist, dass du eine Navigation-Property Tags in der Compendium-Entity hast.

    Die SQL-Abfrage, die LINQ hier erzeugt sieht ungefähr so aus (CompendiumTag ist die Tabelle, in der die Beziehungen zwischen den beiden Tabellen gespeichert werden):

    SELECT 
    [Compendium].[Id] AS [Id], 
    [Compendium].[Name] AS [Name]
    FROM [dbo].[Compendium]
    WHERE  EXISTS (
    	SELECT 
    		1 AS [C1]
    		FROM  [dbo].[CompendiumTag]
    		INNER JOIN [dbo].[Tag] ON [Tag].[Id] = [CompendiumTag].[Tags_Id]
    		WHERE 
    			([Compendium].[Id] = [CompendiumTag].[Compendiums_Id]) AND 
    			([Tag].[Name] IN (N'Tag1',N'Tag2',N'Tag3')
    	)
    )

    EDIT: 

    Elmar Boyes Vorschlag ist auf jeden Fall sinnvoll. Das Ganze würde in deinem Fall dann so aussehen:

    string[] parameters = {"Tag1", "Tag2", "Tag3"};
    var result = db.Compendiums.Where(c => c.Tags.Any(t => parameters.Contains(t.Name)));
    

    Viele Grüße,
    Thomas Fröhle
    App-Entwickler-Hotline für MSDN Online Deutschland

    Disclaimer:
    Bitte haben Sie Verständnis dafür, dass wir hier auf Rückfragen gar nicht oder nur sehr zeitverzögert antworten können.
    Bitte nutzen Sie für Rückfragen oder neue Fragen den telefonischen Weg über die App-Entwickler-Hotline: http://www.msdn-online.de/Hotline
    MSDN Hotline: Schnelle & kompetente Hilfe für Entwickler: kostenfrei!

    Es gelten für die App-Entwickler-Hotline und dieses Posting diese Nutzungsbedingungen, Hinweise zu MarkenzeichenInformationen zur Datensicherheit sowie die gesonderten Nutzungsbedingungen für die App-Entwickler-Hotline.


    • Als Antwort markiert A.Kuller Donnerstag, 30. Januar 2014 17:36
    • Bearbeitet Thomas Fröhle Donnerstag, 30. Januar 2014 22:45
    Donnerstag, 30. Januar 2014 16:15
  • Hallo,

    Vielen Dank für eure Antworten, jetzt funktioniert es.

    Für alle die ähnliche Probleme haben:

    Meine Datenbank schaut folgendermaßen aus:

    Mit:

    ViewData["comp"] = db.Compendia.Where(c => c.Tags.Count(t => parameters.Contains(t.Name)) > 0);

    lasse ich mir jetzt alle Einträge, die den entsprechenden Tag enthalten, zurückgeben.

    Anschließend noch mit einer foreach Schleife über die Einträge laufen.

    Vielen Dank nochmal und Grüße

    akuller

    Donnerstag, 30. Januar 2014 17:35
  • Hallo A.,

    anstatt Count sollte man Any verwenden - das entspricht EXISTS in SQL:
    In LINQ, Don’t Use Count() When You Mean Any()

    Da ich jetzt keine Zeit zum Nachstellen des Datenmodells habe, ein Beispiel anhand der Northwind Orders:

            internal static void OrdersWithProductList()
            {
                var productList = new List<string>() {"Chai", "Chef Anton's Gumbo Mix"}; //  ' ProductId IN (1, 5)
    
                using (var context = new NorthwindEntities())
                {
                    var ordersProductAny = context.Orders
                        .Where(o => o.Order_Details
                                .Where(od => productList.Contains(od.Products.ProductName)).Any())
                        .Select(o => o.OrderID);
                    foreach (var orderID in ordersProductAny)
                        Console.WriteLine("{0}", orderID);
                    
                    var ordersProductCount = context.Orders
                        .Where(o => o.Order_Details
                            .Where(od => productList.Contains(od.Products.ProductName)).Count() > 0)
                        .Select(o => o.OrderID);
                    foreach (var orderID in ordersProductCount)
                        Console.WriteLine("{0}", orderID);
                }
            }
    
    Hier mit einer Navigation von Order Details auf Products um die Orders zu erhalten.Hier der Kürze halber begrenzt auf OrderID, um das SQL abzukürzem, sonst werden alle Spalten geliefert. Als SQL bekommt man als Ergebnis:
    -- 1. Abfrage: Any => EXISTS
    SELECT [Extent1].[OrderID] AS [OrderID]
    FROM [dbo].[Orders] AS [Extent1]
    WHERE  EXISTS (SELECT 
    	1 AS [C1]
    	FROM  [dbo].[Order Details] AS [Extent2]
    	INNER JOIN [dbo].[Products] AS [Extent3] ON [Extent2].[ProductID] = [Extent3].[ProductID]
    	WHERE ([Extent1].[OrderID] = [Extent2].[OrderID]) AND ([Extent3].[ProductName] IN (N'Chai',N'Chef Anton''s Gumbo Mix')) )
    
    -- 2. Abfrage via Count 
    SELECT [Project1].[OrderID] AS [OrderID]
    FROM ( SELECT [Extent1].[OrderID] AS [OrderID], 
    	(SELECT COUNT(1) AS [A1]
    		FROM  [dbo].[Order Details] AS [Extent2]
    		INNER JOIN [dbo].[Products] AS [Extent3] ON [Extent2].[ProductID] = [Extent3].[ProductID]
    		WHERE ([Extent1].[OrderID] = [Extent2].[OrderID]) AND ([Extent3].[ProductName] IN (N'Chai',N'Chef Anton''s Gumbo Mix'))) AS [C1]
    	FROM [dbo].[Orders] AS [Extent1]
    )  AS [Project1]
    WHERE [Project1].[C1] > 0

    Sind nun die Tabellen größer, macht ein EXISTS vs. COUNT schon einen signifikanten Unterschied in der Abfragedauer.

    Gruß Elmar

    • Als Antwort vorgeschlagen Thomas Fröhle Donnerstag, 30. Januar 2014 22:43
    Donnerstag, 30. Januar 2014 19:36
  • Hallo,

    Vielen Dank für die Antwort, habe die Modifikation soeben vorgenommen.

    Was ist den eigentlich effizienter? Die Methodenschreibweise oder die Linq Abfrage?

    Samstag, 1. Februar 2014 00:13
  • Hallo A.,

    von der Effizienz tun sich Methoden- oder Abfragesyntax nichts. Der Compiler setzt eine Linq Abfrage bereits beim Kompilieren in Methoden-Aufrufe um.

    Leichter in Abfragesyntax schreiben lassen sich i. a. Join oder komplexere GroupBy-Abfragen, da die dazugehörigen Methoden relativ komplexe Parameterlisten haben, da hilft der "Linq-Compiler" dann schon. Nur gibt es nicht für alle Methoden Schlüsselwörter so dass man die Methodensyntax ebenso beherrschen muss.

    Im Falle von EF (oder Linq To Sql) wird daraus ein Ausdrucksbaum (via IQueryable). Den setzt zur Laufzeit das EF in den SQL-Dialekt des Providers um. Für häufiger genutzte Abfragen lohnt es diese zu übersetzen. Aktuellere Versionen können das automatisch, siehe u. a. Improve Performance with Entity Framework 5

    Gruß Elmar

    Samstag, 1. Februar 2014 06:35