2012-09-04 16 views
6

Derzeit baue ich eine Windows-Anwendung mit SQLite. In der Datenbank gibt es eine Tabelle sagen User, und in meinem Code gibt es eine Repository<User> und eine UserManager. Ich denke, es ist ein sehr allgemeines Design. Im Repository gibt es eine List Methode:zurück Queryable <T> oder Liste <T> in einem Repository <T>

//Repository<User> class 
public List<User> List(where, orderby, topN parameters and etc) 
{ 
    //query and return 
} 

Dieses Problem bringt, wenn ich etwas komplexer in UserManager.cs tun wollen:

//UserManager.cs 
public List<User> ListUsersWithBankAccounts() 
{ 
    var userRep = new UserRepository(); 
    var bankRep = new BankAccountRepository(); 
    var result = //do something complex, say "I want the users live in NY 
       //and have at least two bank accounts in the system 
} 

Sie sehen können, Rückkehr List<User> bringt Performance-Problem, becuase die Abfrage wird früher als erwartet ausgeführt. Jetzt muss ich es so etwas wie ein IQueryable<T> ändern:

//Repository<User> class 
public TableQuery<User> List(where, orderby, topN parameters and etc) 
{ 
    //query and return 
} 

TableQuery<T> Teil des SQLite-Treiber ist, die fast gleich zu IQueryable<T> in EF, das eine Abfrage liefert und nicht direkt ausführen lassen. Aber jetzt ist das Problem: in UserManager.cs, es weiß nicht, was ist ein TableQuery<T>, muss ich neue Referenz hinzufügen und Namespaces wie using SQLite.Query im Business-Schicht-Projekt importieren. Es bringt wirklich schlechtes Codegefühl. Warum sollte meine Business-Schicht die Details der Datenbank kennen? Warum sollte die Business-Schicht wissen, was SQLite ist? Was ist das richtige Design dann?

+0

Könnten Sie nicht „nur“ fügen Sie eine neue Ebene von Abstraktionen (Schnittstellen) zwischen der Business-Schicht und Datenzugriffsschicht? Ja, das würde eine neue Menge von Objekten erfordern, die die gleichen Daten in der Business-Schicht repräsentieren, und Mapping zwischen diesen und denen in der DA-Ebene - gewissermaßen Code-Duplikation -, aber es sollte Ihnen einen saubereren Schnitt zwischen den Layern geben Ich verstehe deine Situation richtig. – Kjartan

+1

Ich stimme @Kjartan, was Sie wirklich wollten, ist IQueryable , es ist eine Schande SQLite haben keine, es lohnt sich, eine andere Schichten zu schaffen, um den richtigen Typ zu übertragen. – ivenxu

+0

@Kjartan @ivenxu: Ich habe nicht gesagt 'TableQuery ' ist falsch, noch 'IQueryable ' ist korrekt. Auch habe ich eine neue Übertragungsschnittstelle hinzugefügt, wie 'IQuery ', es ist immer noch * Basis auf * 'TableQuery ', und wenn ich 'IQuery ' in meiner Business-Schicht verwende, ist alles gleich. Es sieht nur * nicht mit SQLite zusammen, aber tatsächlich trägt es eine Maske. –

Antwort

2

ich Sie würde empfehlen, IEnumerable<T> zu verwenden, anstatt IQueryable<T>, die zu faul Laden erlaubt. IEnumerable jedoch nicht implizieren Sie die Daten in jede Weise abfragen. Ihre DB LINQ Provider wird wahrscheinlich eine reduzierte Funktionalität haben .

0

Lazy Loading kann ein PITA sein und ich versuche lieber, eine Ladestrategie in das Repository zu injizieren, anstatt zu versuchen, mit dem Abfrageobjekt in meiner Anwendung oder Business-Schicht herumzuhantieren. Vor allem, wenn das Abfrageobjekt zwingt, die Layer fest zu koppeln.

+0

Was meinst du mit "laden Strategie"? – ken2k

2

In einer sauberen Architektur ist die Datenabfragelogik normalerweise in Repositorys gekapselt. Die Verwendung von Pipes und Filtern kann dazu beitragen, die Abfragelogik wiederzuverwenden. Diese mit Methoden in Data-Layer/Repositories zu versehen, wird besser lesbar und wartbar sowie wiederverwendbar sein.

Zum Beispiel Rohre und Filter Benutzer abfragen:

/// Pipes/Filters for user queries. 
public static class UserExtensions 
{ 
    public static IQueryable<User> Active(this IQueryable<User> query) 
    { 
     return query.Where(user => user.Active == true); 
    } 
} 

public class UserRepository : IRepository<User>, IUserRepository 
{ 
    /// Retrieve all users 
    public List<User> List() 
    { 
     // Logic to query all users in the database. 
    } 
    public List<User> ListActive() 
    { 
     // Logic to query all active users in the database. 
     return context.Users.Active().ToList(); 
    } 
} 

Komplexe Abfragen erfordert das Verständnis davon Zweck und Aufgaben zu abstrahieren die Abfragelogik ist es Repositorys ist. Zum Beispiel ‚alle Konten erhalten so auf Benutzer gehört“ kann in AccountRepository Klasse als List<Account> ListForUser(int userId) { } geschrieben werden

Edit:. Basierend auf den Kommentaren, hier ist die Szenarien eine Suchabfrage zu schreiben, die Benutzer lebt in LA ruft die mindestens zwei Konten haben.

public class UserRepository : IRepository<User>, IUserRepository 
{ 
    // other queries. 

    public List<User> List(ISearchQuery query) 
    { 
     // Logic to query all active users in the database. 
     return context.Users.Active().LivesIn(query.Country).WithAccounts(query.AccountsAtLeast).ToList(); 
    } 
} 

public static class UserExtensions 
{ 
    // other pipes and filters. 

    public static IQueryable<User> LivesIn(this IQueryable<User> query, string country) 
    { 
     return query.Where(user => user.Country.Name == country); 
    } 
    public static IQueryable<User> WithAccounts(this IQueryable<User> query, int count) 
    { 
     return query.Where(user => user.Accounts.Count() >= count); 
    } 
} 
+0

Wo werden Sie die Logik für die Anforderung setzen: "Abfrage der Benutzer in NY leben und mindestens zwei Bankkonten im System haben". –

+0

Diese Abfragen sind in Repository geschrieben und Ofcoz können Sie entsprechend umgestalten. Und ja, es gibt auch eine Einschränkung, da nicht alle Abfrageszenarien von einer einzigen SQL-Abfrage in EF geparst werden können. In solchen Fällen müssen wir alternative Ansätze entwickeln. –

1

Seit TableQuery<T> implementiert IEnumerable<T> und nicht IQueryable<T>, die beste Lösung wäre, einfach Ihre Repository-Schnittstelle ändern IEnumerable<T> statt TableQuery<T> zurückzukehren.Dies bricht nicht nur die explizite Clientabhängigkeit mit Ihrer SqlLite-Bibliothek, es ist auch ein besseres Design, eine Abstraktion (IEnumerable<T>) anstelle einer Implementierung (TableQuery<T>) in Ihren Schnittstellen zu verwenden.

Ihr Beispiel Methode soll wie folgt aussehen:

//Repository<User> class 
public IEnumerable<User> List(where, orderby, topN parameters and etc) 
{ 
    //query and return 
} 
Verwandte Themen