2017-03-23 1 views
1

Nehmen wir an, ich habe eine Produktkatalog-MVC-Webanwendung, die EF 6.0 verwendet, wo alle CRUD-Operationen von Repositories verarbeitet werden. Nun, eine der Ansichten muss eine List von Categories zusammen mit einigen Aggregat Informationen über Products von diesem Category zeigen, ich meine, dass neben Category Name Spalte drei Spalten mit der Gesamtzahl der Produkte aus dieser Kategorie, zusammen mit Minimum und Maximum erscheinen sollte Preise von Produkten.Aggregatfunktionen mit EF-Repository

public class Product 
{ 
    public string Name { get; set; } 
    public decimal Price { get; set; } 
    public int  CategoryId    { get; set; } 
    public virtual Category Category { get; set; } 
} 

public class Category 
{ 
    public string Name { get; set; } 
    public virtual ICollection<Product> Products { get; set; } 
} 

Dann, wenn ich ein Repository von Backwaren haben

unitOfWork.ProductRepository.Insert(new Product(){Name="Doughnut", Price=4.0, CategoryId=1}); 
unitOfWork.ProductRepository.Insert(new Product(){Name="Apple Pie", Price=7.0, CategoryId=1}); 
unitOfWork.ProductRepository.Insert(new Product(){Name="Meat Pie", Price=9.0, CategoryId=1}); 

Ich brauche die Ansicht

# Category  Total Products  Min Price  Max Price 
1 Bakery products 3      4    9 

Ich glaube nicht, zu zeigen, dass es eine gute Idee ist aus hand von dem Repository die Abfrage, da es die ganze Idee der Verwendung von Repositories ruiniert, und AFAIK ist es allgemein als eine schlechte Praxis anerkannt.
Einige Abfragen, die direkt die DbContext verwenden, könnten geschrieben werden, aber ich wollte wissen, wie diese Art von Aufgaben in realen Anwendungen gehandhabt wird? Vielleicht ist ein neues Ansichtsmodell erforderlich, und dann sollte ein weiteres schreibgeschütztes Repository hinzugefügt werden, das diese Daten erhält? Etwas wie das?

public class CategoryStatsVM 
{ 
    public string Name { get; set; } 
    public virtual ICollection<Product> Products { get; set; } 
    public int  Count { get; set; } 
    public decimal MinPrice { get; set; } 
    public decimal MaxPrice { get; set; } 
} 
+0

Ihr Problem ist nicht von EF (welche verursacht zu bekommen ' DbContext' ** ist ** Repository und UOW und unterstützt leicht solche Szenarien), aber von zusätzlichen Abstraktionen (Einschränkungen), die Sie darauf setzen, entfernen Sie bitte das 'entity-framework' Tag. –

+0

@IvanStoev Ich folgte Microsofts Artikel https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions/getting-started-with-ef-5-using-mvc-4/implementing- Die-Repository-und-Unit-of-Work-Muster-in-einem-ASP-Net-MVC-Anwendung, die erklärt, wie Repository und UoW auf EF, zu implementieren, also ja, ich habe eine zusätzliche Abstraktion, aber ich will zu verstehen, wie Menschen mit diesen Problemen umgehen, wenn sie den Microsoft-Dokumenten folgen. – Jyrkka

Antwort

0

Ich bin nicht sicher, was Sie unter „Ich glaube nicht, dass es eine gute Idee ist aus dem Repository die Abfrage zu handhaben, da es die ganze Idee der Verwendung von Repositories ruiniert“. Die Hauptidee hinter einem Repository besteht darin, DATA ACCESS zu abstrahieren. Wenn Sie eine Aggregation, eine andere Berechnung oder etwas Ähnliches machen wollen, ist das meistens Teil Ihrer Geschäftslogik. Die VM ist die Komponente, die Sie die Zählung, die max und die min erfordert. Die VM ist im Grunde eine abstrakte Ansicht, und Ansichten werden normalerweise von Geschäftsanforderungen abgeleitet. Ich denke, dass diese Abfragen nicht Teil des Datenzugriffs sind und somit nicht Teil des Repositories sind. Was ich tun würde, ist eine Methode, um die Repository-Schnittstelle wie folgt hinzu:

unitOfWork.CategoryRepository.GetById(3).Products.Count(); 
unitOfWork.CategoryRepository.GetById(3).Products.Min(p=>p.Price); 

hat den zusätzlichen Vorteil von diesem auch:

public IQueryable<Category> GetById(int i) 
{ 
    return dbContext.Category.Single(c=>c.CategoryId==i); 
} 

Und dann in der Business-Logik Sie es wie folgt verwenden können nicht die verzögerte Ausführung von LInQ an Entitäten verlieren, so dass die Zählung und Suche nach dem Minimum in der Datenbank ausgeführt wird.

+0

Vielen Dank für Ihre Einsicht, aber ich glaube, Sie haben mich falsch verstanden, ich möchte ein Gitter mit einer Liste von 'Kategorien' zeigen und ich möchte nicht Dutzende von Abfragen ausführen, um es zu bauen. Und um zu verstehen, was ich meine, indem ich sage, dass die Rückgabe von IQueryable eine schlechte Idee ist, können Sie f. Dieser Artikel http://codetunnel.io/should-you-return-iqueryablet-from-your-repositories/ und viele andere, einfach googeln. Aber ich sage nicht, dass dies der richtige Weg ist, um Dinge zu tun. Ich versuche nur zu verstehen, wie dies erreicht werden sollte, wenn ich den besten Praktiken folge ... – Jyrkka

+0

Danke für den Blogbeitrag. Dem stimme ich nicht unbedingt zu, aber es ist immer gut, andere Meinungen zu lesen. –

+0

Stimme völlig mit dir überein, deshalb frage ich hier. Wenn Sie an etwas denken könnten, das mit meinen Beschränkungen arbeiten kann, wäre ich dankbar, wenn Sie Ihre Gedanken teilen. – Jyrkka

0

Wie schon von @Ivan Stoev erwähnt. DbContext IS Repository. Es behandelt alle grundlegenden CRUD-Operationen und eine weitere Repository-Schicht darüber scheint überflüssig zu sein. Worüber Sie sprechen, klingt eher nach einer Geschäftsschicht, die Repository für "rohe" Daten aufruft, zusätzliche Logik anwendet und Daten für die Präsentationsebene bereitstellt.

Also in Ihrem Fall, dass Sie einen Service/Business-Fabrik/Klasse haben würde - was auch immer Sie es nennen - mit einer Methode wie folgt:

public IEnumerable<CategoryStatsVM> GetCategoryStats() 
{ 
    IList<Category> categories = dbContext.Categories.Include(m => m.Products).ToList(); 

    foreach (Category category in categories) 
    { 
     yield return new CategoryStatsVM 
     { 
      Name = category.Name, 
      Count = category.Products.Count(), 
      Products = this.GetProducts(category.Products), 
      MinPrice = category.Products.Aggregate(GetMinPrice), 
      MaxPrice = category.Products.Aggregate(GetMaxPrice) 
     } 
    } 
} 

private Product GetMinPrice(Product min, Product current) 
{ 
    return min == null || curr.Price < min.Price ? curr : min; 
} 

private Product GetMaxPrice(Product max, Product current) 
{ 
    return max == null || curr.Price > max.Price ? curr : max; 
} 

private IEnumerable<ProductVM> GetProducts(IEnumerable<Product> products) 
{ 
    List<ProductVM> products; // create instance of the product view model list 
    return products; 
} 

Nur eine Anmerkung über die letzte Funktion - es ist dort, weil Sie sollten Erstellen Sie auch eine Ansichtsmodellklasse für das Produkt. Sie sollten nicht mit Datenmodellen in der Präsentationsebene arbeiten.

Ich erstellte getrennte Funktionen für die Aggregation, nur um es lesbarer zu machen.Sie können natürlich alles in einem Verfahren haben:

MinPrice = category.Products.Aggregate((min, curr) => min == null || curr.Price < min.Price ? curr : min), 
+0

Wenn Sie 'IEnumerable' so verwenden, werden Sie viel Leistung verlieren. Sie lesen alle Kategorienobjekte in den Speicher, um die Zählung durchzuführen, anstatt das 'IQueryable' in eine SQL-Abfrage zu konvertieren, die von der Datenbank ausgeführt wird und nur eine Zahl zurückgibt. –

+0

Wenn Sie nur den Elementtyp IEnumerable verwenden, bedeutet das nicht, dass Sie alle Objekte in den Speicher laden. IQueryable erbt auch von IEnumerable. Variablendefinitionen wirken sich nicht auf die Ausführung des Codes aus. Mit der Abfrage bestimmen Sie, ob Sie mit Objekten im Arbeitsspeicher arbeiten oder ob Sie Datenbankaufrufe durchführen, wenn die Objekte benötigt werden. Es ist die erste Zeile in GetCategoryStats, die alles im Speicher lädt, die .ToList() -Methode. Ob es auf diese Weise besser geht oder nicht, hängt von anderer Logik, Datenbankstruktur, Anzahl der Items usw. ab. IEnumerable hat damit nichts zu tun. – Tacud

+0

Ich denke, ich war nicht klar, aber ich meinte genau, was du gesagt hast, wenn du IEnumerable __like this__ verwendest. Ich hätte klarer sein und hinzufügen sollen, dass ich auch die 'ToList()' meinte. Auf diese Weise können Sie der verwendenden Komponente keine Kontrolle darüber geben, wann sie ausgeführt werden soll. Wenn Sie das IQueryable und die Abfrage selbst belassen, können Sie die Abfrageausführung und sogar die Zusammensetzung steuern, was wichtige Funktionen sind. –

0

Sie die Aggregate Funktion wie diese verwenden können, die UserLogs letzte Protokollierung der Transaktionsdatei

UsersLogs.Where(i => i.Id== _id).Max(x => x.transId) 
Verwandte Themen