2012-09-04 26 views
6

Attualmente sto creando un'applicazione per Windows utilizzando sqlite. Nella base dati c'è una tabella che dice User, e nel mio codice c'è un Repository<User> e un UserManager. Penso che sia un design molto comune. Nel repository c'è un metodo List:ritorno Queryable <T> o Elenco <T> in un repository <T>

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

Questo porta un problema, se voglio fare qualcosa di complesso in UserManager.cs:

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

Si può vedere, il ritorno List<User> porta problema di prestazioni, becuase il la query viene eseguita prima del previsto. Ora ho bisogno di cambiarlo in qualcosa di simile a un IQueryable<T>:

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

TableQuery<T> fa parte del conducente SQLite, che è quasi uguale a IQueryable<T> in EF, che fornisce una query e non verrà eseguito immediatamente. Ma ora il problema è: nel UserManager.cs, non sa cosa sia un TableQuery<T>, ho bisogno di aggiungere nuovo riferimento e importare spazi dei nomi come using SQLite.Query nel progetto del livello aziendale. Porta davvero brutte sensazioni al codice. Perché il mio livello aziendale dovrebbe conoscere i dettagli del database? perché il livello aziendale dovrebbe sapere cos'è SQLite? Qual è il design corretto allora?

+0

Potrebbe non "solo" aggiungere un nuovo strato di astrazioni (interfacce) tra il livello di business e lo strato di accesso ai dati? Sì, ciò richiederebbe un nuovo set di oggetti che rappresentano gli stessi dati nel livello aziendale e il mapping tra questi e quelli nel livello DA - in un certo senso, la duplicazione del codice - ma dovrebbe fornire un taglio più pulito tra i livelli, se Capisco correttamente la tua situazione. – Kjartan

+1

Sono d'accordo con @Kjartan, cosa vuoi veramente è IQueryable , è un peccato che SQLite non ne abbia uno, vale la pena creare un altro livello da trasferire per correggere il tipo. – ivenxu

+0

@Kjartan @ivenxu: non ho detto 'TableQuery ' non è corretto, né 'IQueryable ' è corretto. Anche io ho aggiunto una nuova interfaccia di trasferimento, come 'IQuery ', è ancora * base su * 'TableQuery ', e quando uso 'IQuery ' nel mio livello aziendale, tutto è lo stesso. Semplicemente * sembra * non correlato con SQLite, ma in realtà indossa una maschera. –

risposta

2

mi consiglia di utilizzare IEnumerable<T> piuttosto che IQueryable<T>, che permette il caricamento pigro troppo. IEnumerable non implica tuttavia è possibile interrogare i dati in qualsiasi modo. il vostro Il provider DB LINQ avrà probabilmente un set di funzionalità ridotto .

0

Il caricamento lento può essere un PITA e preferisco provare a iniettare una strategia di caricamento nel repository invece di cercare di giocherellare con l'oggetto query nella mia applicazione o nel livello aziendale. Soprattutto quando l'oggetto della query mi obbliga ad accoppiare strettamente gli strati.

+0

Cosa intendi per "strategia di caricamento"? – ken2k

2

In genere, in una architettura pulita, la logica di query dei dati è incapsulata nei repository. L'utilizzo di pipe e filtri può aiutare a riutilizzare la logica delle query. Il raggruppamento di questi con metodi in data-layer/repository sarà più leggibile e mantenibile, oltre che riutilizzabile.

Per esempio, Tubi e filtri per interrogare gli utenti:

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

query complesso richiede la comprensione del suo scopo e le responsabilità di astrarre la logica di query al suo repository. Per esempio, 'Get tutti gli account appartiene a questo utente" può essere scritto in AccountRepository classe come List<Account> ListForUser(int userId) { }

Edit:. Sulla base dei commenti, ecco gli scenari per scrivere una query di ricerca che recupera Utenti vive a Los Angeles, che hanno almeno 2 conti.

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

Dove metterete la logica per il requisito: "interrogare gli utenti vivono a New York e hanno almeno due conti bancari nel sistema". –

+0

Queste query sono scritte nel repository e ofcoz è possibile refactoring di conseguenza. E sì, c'è anche una limitazione dal momento che non tutti gli scenari di query possono essere analizzati da una singola query sql in EF. In questi casi dovremo elaborare approcci alternativi. –

1

Dal TableQuery<T> implementa IEnumerable<T>, e non IQueryable<T>, la soluzione migliore sarebbe quella di cambiare semplicemente l'interfaccia repository per tornare IEnumerable<T> invece di TableQuery<T>.Questo non solo rompe la dipendenza cliente esplicito con la libreria SqlLite, ma è anche un design migliore di utilizzare un un'astrazione (IEnumerable<T>) invece di un implementazione (TableQuery<T>) nelle interfacce.

Il tuo metodo esempio dovrebbe assomigliare a questo:

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