Risposte:
Il livello repository offre un ulteriore livello di astrazione sull'accesso ai dati. Invece di scrivere
var context = new DatabaseContext();
return CreateObjectQuery<Type>().Where(t => t.ID == param).First();
per ottenere un singolo elemento dal database, utilizzare l'interfaccia del repository
public interface IRepository<T>
{
IQueryable<T> List();
bool Create(T item);
bool Delete(int id);
T Get(int id);
bool SaveChanges();
}
e chiama Get(id)
. Il livello repository espone CRUD di base operazioni .
Il livello di servizio espone la logica aziendale, che utilizza il repository. Il servizio di esempio potrebbe essere simile a:
public interface IUserService
{
User GetByUserName(string userName);
string GetUserNameByEmail(string email);
bool EditBasicUserData(User user);
User GetUserByID(int id);
bool DeleteUser(int id);
IQueryable<User> ListUsers();
bool ChangePassword(string userName, string newPassword);
bool SendPasswordReminder(string userName);
bool RegisterNewUser(RegisterNewUserModel model);
}
Mentre il List()
metodo di repository restituisce tutti gli utenti, ListUsers()
di IUserService potrebbe restituirne solo uno, l'utente ha accesso a.
In ASP.NET MVC + EF + SQL SERVER, ho questo flusso di comunicazione:
Viste <- Controller -> Livello di servizio -> Livello repository -> EF -> SQL Server
Livello di servizio -> Livello repository -> EF Questa parte funziona su modelli.
Viste <- Controller -> Livello di servizio Questa parte funziona su modelli vista.
MODIFICARE:
Esempio di flusso per / Orders / ByClient / 5 (vogliamo vedere l'ordine per un cliente specifico):
public class OrderController
{
private IOrderService _orderService;
public OrderController(IOrderService orderService)
{
_orderService = orderService; // injected by IOC container
}
public ActionResult ByClient(int id)
{
var model = _orderService.GetByClient(id);
return View(model);
}
}
Questa è l'interfaccia per il servizio ordini:
public interface IOrderService
{
OrdersByClientViewModel GetByClient(int id);
}
Questa interfaccia restituisce il modello di visualizzazione:
public class OrdersByClientViewModel
{
CientViewModel Client { get; set; } //instead of ClientView, in simple project EF Client class could be used
IEnumerable<OrderViewModel> Orders { get; set; }
}
Questa è l'implementazione dell'interfaccia. Utilizza le classi del modello e il repository per creare il modello di visualizzazione:
public class OrderService : IOrderService
{
IRepository<Client> _clientRepository;
public OrderService(IRepository<Client> clientRepository)
{
_clientRepository = clientRepository; //injected
}
public OrdersByClientViewModel GetByClient(int id)
{
return _clientRepository.Get(id).Select(c =>
new OrdersByClientViewModel
{
Cient = new ClientViewModel { ...init with values from c...}
Orders = c.Orders.Select(o => new OrderViewModel { ...init with values from o...}
}
);
}
}
IRepository<>
alla GenericRepository<>
propria libreria IOC. Questa risposta è molto antica. Penso che la soluzione migliore sia combinare tutti i repository in una classe chiamata UnitOfWork
. Dovrebbe contenere repository di ogni tipo e un metodo chiamato SaveChanges
. Tutti i repository dovrebbero condividere un contesto EF.
Come ha affermato Carnotaurus, il repository è responsabile della mappatura dei dati dal formato di archiviazione agli oggetti business. Dovrebbe gestire sia come leggere e scrivere i dati (eliminare, aggiornare anche) da e verso la memoria.
Lo scopo del livello di servizio è invece quello di incapsulare la logica aziendale in un unico posto per promuovere il riutilizzo del codice e la separazione delle preoccupazioni. Ciò che questo in genere significa per me in pratica quando si costruiscono siti MVC Asp.net è che ho questa struttura
[Controller] chiama [Servizio / i] che chiama [repository (i)]
Un principio che ho trovato utile è mantenere la logica al minimo nei controller e nei repository.
Nei controller è perché mi aiuta a SECCO. È molto comune che devo usare lo stesso filtro o la stessa logica da qualche altra parte e se l'ho inserito nel controller non posso riutilizzarlo.
Nei repository è perché voglio essere in grado di sostituire il mio archivio (o ORM) quando arriva qualcosa di meglio. E se ho una logica nel repository, devo cambiare questa logica quando cambio il repository. Se il mio repository restituisce solo IQueryable e il servizio esegue invece il filtraggio, dovrò solo sostituire i mapping.
Ad esempio, recentemente ho sostituito diversi dei miei repository Linq-To-Sql con EF4 e quelli in cui ero rimasto fedele a questo principio potevano essere sostituiti in pochi minuti. Dove avevo qualche logica, invece, era questione di ore.
onBeforeBuildBrowseQuery
e possono usare il generatore di query per modificare la query.
La risposta accettata (e votata per centinaia di volte) ha un grosso difetto. Volevo evidenziarlo nel commento, ma sarà solo sepolto laggiù in 30 commenti di qualcosa, quindi sottolineando qui.
Ho acquisito un'applicazione enterprise costruita in quel modo e la mia reazione iniziale è stata WTH ? ViewModels nel livello di servizio? Non volevo cambiare la convenzione perché erano trascorsi anni di sviluppo, quindi ho continuato a restituire ViewModels. Ragazzo, si è trasformato in un incubo quando abbiamo iniziato a usare WPF. Noi (il team di sviluppatori) dicevamo sempre: quale ViewModel? Quello vero (quello che abbiamo scritto per il WPF) o quello dei servizi? Sono stati scritti per un'applicazione Web e avevano persino il flag IsReadOnly per disabilitare la modifica nell'interfaccia utente. Maggiore, grande difetto e tutto a causa di una sola parola: ViewModel !!
Prima di commettere lo stesso errore, ecco alcuni altri motivi oltre alla mia storia sopra:
Restituire un ViewModel dal livello di servizio è un no enorme. È come dire:
Se si desidera utilizzare questi servizi, è meglio utilizzare MVVM ed ecco il ViewModel che è necessario utilizzare. Ahia!
I servizi stanno assumendo che verranno visualizzati da qualche parte in un'interfaccia utente. Cosa succede se viene utilizzato da un'applicazione non UI come servizi Web o servizi Windows?
Questo non è nemmeno un vero ViewModel. Un vero ViewModel ha osservabilità, comandi ecc. Questo è solo un POCO con una cattiva reputazione. (Vedi la mia storia sopra per perché i nomi contano.)
L'applicazione che consuma meglio dovrebbe essere un livello di presentazione (i ViewModel sono usati da questo livello) e comprendere meglio C #. Un altro Ouch!
Per favore, non farlo!
Di solito un repository viene utilizzato come impalcatura per popolare le entità: un livello di servizio esce e genera una richiesta. È probabile che inserirai un repository nel tuo livello di servizio.
Il livello repository è implementato per accedere al database e consente di estendere le operazioni CRUD sul database. Considerando che un livello di servizio è costituito dalla logica aziendale dell'applicazione e può utilizzare il livello di repository per implementare determinate logiche che coinvolgono il database. In un'applicazione, è meglio disporre di un livello repository e di un livello di servizio separati. Avere repository e livelli di servizio separati rende il codice più modulare e disaccoppia il database dalla logica aziendale.