Odpowiedzi:
Warstwa repozytorium zapewnia dodatkowy poziom abstrakcji w stosunku do dostępu do danych. Zamiast pisać
var context = new DatabaseContext();
return CreateObjectQuery<Type>().Where(t => t.ID == param).First();
aby uzyskać pojedynczy element z bazy danych, używasz interfejsu repozytorium
public interface IRepository<T>
{
IQueryable<T> List();
bool Create(T item);
bool Delete(int id);
T Get(int id);
bool SaveChanges();
}
i zadzwoń Get(id)
. Warstwa repozytorium udostępnia podstawowe operacje CRUD .
Warstwa usługi udostępnia logikę biznesową, która korzysta z repozytorium. Przykładowa usługa może wyglądać następująco:
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);
}
Podczas gdy List()
metoda repozytorium zwraca wszystkich użytkowników, ListUsers()
IUserService może zwrócić tylko tych, z których użytkownik ma dostęp.
W ASP.NET MVC + EF + SQL SERVER mam taki przepływ komunikacji:
Widoki <- Kontrolery -> Warstwa usługi -> Warstwa repozytorium -> EF -> SQL Server
Warstwa serwisowa -> Warstwa repozytorium -> EF Ta część działa na modelach.
Widoki <- Kontrolery -> Warstwa serwisowa Ta część działa na modelach widoków.
EDYTOWAĆ:
Przykład przepływu dla / Orders / ByClient / 5 (chcemy zobaczyć zamówienie dla konkretnego klienta):
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);
}
}
Oto interfejs do obsługi zamówień:
public interface IOrderService
{
OrdersByClientViewModel GetByClient(int id);
}
Ten interfejs zwraca model widoku:
public class OrdersByClientViewModel
{
CientViewModel Client { get; set; } //instead of ClientView, in simple project EF Client class could be used
IEnumerable<OrderViewModel> Orders { get; set; }
}
To jest implementacja interfejsu. Wykorzystuje klasy modeli i repozytorium do tworzenia modelu widoku:
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<>
ją GenericRepository<>
w bibliotece IOC. Ta odpowiedź jest bardzo stara. Myślę, że najlepszym rozwiązaniem jest połączenie wszystkich repozytoriów w jednej klasie o nazwie UnitOfWork
. Powinien zawierać repozytorium każdego typu i wywoływaną jedną metodę SaveChanges
. Wszystkie repozytoria powinny mieć jeden kontekst EF.
Jak powiedział Carnotaurus, repozytorium jest odpowiedzialne za mapowanie danych z formatu pamięci na obiekty biznesowe. Powinien obsługiwać zarówno sposób odczytu, jak i zapisywania danych (usuwania, aktualizacji również) zi do pamięci.
Z drugiej strony, celem warstwy usługowej jest hermetyzacja logiki biznesowej w jednym miejscu w celu promowania ponownego użycia kodu i separacji problemów. Co to zwykle oznacza dla mnie w praktyce przy tworzeniu stron Asp.net MVC, to fakt, że mam taką strukturę
[Kontroler] wywołuje [Usługa (y)], który wywołuje [repozytorium (-a)]
Jedną z zasad, które uważam za przydatne, jest ograniczenie logiki do minimum w kontrolerach i repozytoriach.
W kontrolerach dzieje się tak dlatego, że pomaga mi zachować SUCHOŚĆ. Bardzo często muszę używać tego samego filtrowania lub logiki gdzie indziej, a jeśli umieściłem go w kontrolerze, nie mogę go ponownie użyć.
W repozytoriach dzieje się tak dlatego, że chcę móc zastąpić moją pamięć masową (lub ORM), gdy pojawi się coś lepszego. A jeśli mam logikę w repozytorium, muszę zmienić tę logikę po zmianie repozytorium. Jeśli moje repozytorium zwraca tylko IQueryable, a usługa wykonuje filtrowanie z drugiej strony, będę musiał jedynie zastąpić odwzorowania.
Na przykład niedawno zastąpiłem kilka moich repozytoriów Linq-To-Sql EF4, a te, w których przestrzegałem tej zasady, można wymienić w ciągu kilku minut. Tam, gdzie miałem trochę logiki, to kwestia godzin.
onBeforeBuildBrowseQuery
i mogę użyć konstruktora zapytań do zmiany zapytania.
Przyjęta odpowiedź (i głosowana setki razy) ma poważną wadę. Chciałem zwrócić na to uwagę w komentarzu, ale zostanie tam pochowany w 30 komentarzach, więc tutaj wskazuję.
Przejęłam tak zbudowaną aplikację korporacyjną, a moją pierwszą reakcją było WTH ? ViewModels w warstwie serwisowej? Nie chciałem zmieniać konwencji, ponieważ minęły lata rozwoju, więc kontynuowałem powrót do ViewModels. Chłopcze, kiedy zaczęliśmy używać WPF, zamieniło się w koszmar. My (zespół deweloperów) zawsze mówiliśmy: który ViewModel? Prawdziwy (ten, który napisaliśmy dla WPF) czy usługowy? Zostały napisane dla aplikacji internetowej, a nawet miały flagę IsReadOnly, aby wyłączyć edycję w interfejsie użytkownika. Poważna, poważna wada i wszystko z powodu jednego słowa: ViewModel !!
Zanim popełnisz ten sam błąd, oto kilka innych powodów oprócz mojej powyższej historii:
Zwrócenie ViewModel z warstwy usługi to ogromne nie. To tak, jakby powiedzieć:
Jeśli chcesz korzystać z tych usług, lepiej korzystaj z MVVM, a oto ViewModel, którego potrzebujesz. Auć!
Usługi zakładają, że gdzieś będą wyświetlane w interfejsie użytkownika. Co się stanie, jeśli jest używana przez aplikację inną niż interfejs użytkownika, taką jak usługi sieciowe lub usługi systemu Windows?
To nawet nie jest prawdziwy ViewModel. Prawdziwy ViewModel ma obserwowalność, polecenia itp. To tylko POCO o złej nazwie. (Zobacz moją historię powyżej, dlaczego nazwy mają znaczenie.)
Aplikacja konsumująca lepiej jest warstwą prezentacji (ViewModels są używane przez tę warstwę) i lepiej rozumie C #. Kolejny ouch!
Proszę nie rób tego!
Zwykle repozytorium jest używane jako rusztowanie do zapełniania twoich bytów - warstwa usługi wychodziła i generowała żądanie. Prawdopodobnie umieścisz repozytorium pod warstwą usług.
Warstwa repozytorium jest zaimplementowana w celu uzyskania dostępu do bazy danych i pomaga rozszerzyć operacje CRUD na bazie danych. Natomiast warstwa usług składa się z logiki biznesowej aplikacji i może wykorzystywać warstwę repozytorium do implementacji określonej logiki obejmującej bazę danych. W aplikacji lepiej jest mieć oddzielną warstwę repozytorium i warstwę usługi. Posiadanie osobnych warstw repozytorium i usług czyni kod bardziej modułowym i oddziela bazę danych od logiki biznesowej.