Відповіді:
Шар репозиторію надає додатковий рівень абстрагування доступу до даних. Замість того, щоб писати
var context = new DatabaseContext();
return CreateObjectQuery<Type>().Where(t => t.ID == param).First();
щоб отримати один елемент із бази даних, ви використовуєте інтерфейс сховища
public interface IRepository<T>
{
IQueryable<T> List();
bool Create(T item);
bool Delete(int id);
T Get(int id);
bool SaveChanges();
}
і дзвоніть Get(id)
. Шар репозиторію розкриває основні операції з CRUD .
Службовий рівень розкриває логіку бізнесу, яка використовує сховище. Приклад служби може виглядати так:
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);
}
Хоча List()
метод репозиторію повертає всіх користувачів, ListUsers()
IUserService може повертати лише тих, до яких користувач має доступ.
У ASP.NET MVC + EF + SQL SERVER я маю такий потік зв'язку:
Перегляди <- Контролери -> Сервісний рівень -> Рівень сховища -> EF -> SQL Server
Сервісний рівень -> Рівень сховища -> EF Ця частина працює на моделях.
Вид <- Контролери -> Сервісний рівень Ця частина працює на моделях перегляду.
Редагувати:
Приклад потоку для / Orders / ByClient / 5 (ми хочемо побачити замовлення для конкретного клієнта):
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);
}
}
Це інтерфейс для обслуговування замовлення:
public interface IOrderService
{
OrdersByClientViewModel GetByClient(int id);
}
Цей інтерфейс повертає модель перегляду:
public class OrdersByClientViewModel
{
CientViewModel Client { get; set; } //instead of ClientView, in simple project EF Client class could be used
IEnumerable<OrderViewModel> Orders { get; set; }
}
Це реалізація інтерфейсу. Для створення моделі перегляду використовуються класи моделей та сховище:
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<>
до GenericRepository<>
своєї бібліотеки IOC. Ця відповідь дуже стара. Я думаю, що найкращим рішенням є об'єднання всіх сховищ в одному класі, що називається UnitOfWork
. Він повинен містити сховище кожного типу та один метод, який називається SaveChanges
. Усі сховища повинні мати один контекст EF.
Як сказав Карнотавр, сховище несе відповідальність за відображення ваших даних із формату зберігання для ваших бізнес-об'єктів. Він повинен керувати як читанням, так і записом даних (видалення, оновлення теж) із та у сховище.
Мета рівня обслуговування, з іншого боку, полягає в об'єднанні бізнес-логіки в єдине місце для сприяння повторному використанню коду та роз'єднанню проблем. Що зазвичай це означає для мене на практиці при створенні сайтів Asp.net MVC, це те, що я маю цю структуру
[Контролер] викликає [Службу (служб)], хто викликає [сховище]]
Один із принципів, який я вважав корисним - це звести до мінімуму логіку в контролерах і сховищах.
У контролерах це тому, що це допомагає тримати мене СУХОМО. Дуже часто зустрічається те, що мені потрібно використовувати ту саму фільтрацію або логіку десь в іншому місці, і якщо я помістив її в контролер, я не можу використовувати його повторно.
У сховищах це відбувається тому, що я хочу мати змогу замінити своє сховище (або ORM), коли станеться щось краще. І якщо у мене є логіка в сховищі, мені потрібно переписати цю логіку, коли я зміню сховище. Якщо мій сховище повертає лише IQueryable, а сервіс виконує фільтрацію з іншого боку, мені потрібно буде лише замінити відображення.
Наприклад, нещодавно я замінив кілька моїх сховищ Linq-To-Sql на EF4, а ті, де я залишився вірним цьому принципу, могли замінити за лічені хвилини. Де я мав деяку логіку, це було замість них години.
onBeforeBuildBrowseQuery
і може використовувати конструктор запитів для зміни запиту.
Прийнята відповідь (і її сотні разів відкликано) має великий недолік. Я хотів вказати на це у коментарі, але він просто заринеться там у 30-ти коментарях, що вказують тут.
Я взяв на себе корпоративну програму, яка була побудована таким чином, і моя початкова реакція була WTH ? ViewModels в рівні обслуговування? Я не хотів змінювати конвенцію, тому що в неї пішли роки розвитку, тому я продовжував повертати ViewModels. Хлопчик, це перетворилося на кошмар, коли ми почали використовувати WPF. Ми (команда розробників) завжди казали: який ViewModel? Справжній (той, про який ми писали для WPF) або сервіс? Вони були написані для веб-програми та навіть мали прапор IsReadOnly для відключення редагування в інтерфейсі користувача. Основний, головний недолік і все через одне слово: ViewModel !!
Перш ніж ви зробите ту саму помилку, ось ще кілька причин на додаток до моєї історії вище:
Повернення ViewModel з рівня обслуговування - це величезне "ні". Це як сказати:
Якщо ви хочете скористатися цими послугами, вам краще використовувати MVVM, а ось ViewModel, який вам потрібно використовувати. Ой!
Служби роблять припущення, що вони будуть десь відображатися в інтерфейсі. Що робити, якщо його використовує програма, що не користується інтерфейсом, наприклад веб-сервіси або служби Windows?
Це навіть не справжній ViewModel. Справжній ViewModel має спостережливість, команди тощо. Це просто POCO з неправильним ім'ям. (Дивіться мою історію вище, чому імена мають значення.)
Захоплюючий додаток краще бути презентаційним шаром (ViewModels використовується цим шаром), і він краще зрозуміє C #. Ще одне!
Будь ласка, не робіть цього!
Зазвичай сховище використовується як ліси для заселення ваших організацій - сервісний рівень вийшов би і подав запит. Цілком ймовірно, що ви помістите сховище під службовий рівень.
Шар репозиторію реалізується для доступу до бази даних та допомагає розширити операції CRUD над базою даних. Тоді як сервісний рівень складається з бізнес-логіки програми і може використовувати рівень репозиторію для реалізації певної логіки, що включає базу даних. У додатку краще мати окремий рівень сховища та рівень обслуговування. Маючи окремі шари сховища та сервісу, зробіть код більш модульним і відокремте базу даних від бізнес-логіки.