Здравсвуйте, хотелось создать абстрактный слой доступа к данным используя проекты типа NetStandart 2.0
Думаю использовать один проект: работа с данными "DAL", в котором будут зависимости от EntityFarmeworkCore. В нем будет находится Context и Entities. Также обертка в виде обобщенного репозиория.
Второй проект бизнес логики "BL". зависимость от "DAL". Набор конкретных методов которые работают с данными репозиториев. Например: IList GetAllDepartments() Department GetDepartmentByName(string departmentName)
Начал искать похожие реализации и наткнулся на скачанную статью где описывается подобная архитектура (ссылку на статью не помню). Суть подхода в том, что в репозитории на каждое атомарное действие создается короткоживущий контекст, по сути без отслеживания трекинга изменений, методы репозитория возвращают не запрос Iquereble, а сразу IList. Слой бизнес логики использует эти методы, предоставляя понятный интерфейс наружу.
Реализовал тестовый пример и возникли вопросы правильно ли я все делаю. Привожу пример кода где используются всего 2 сущности Department и Employee.
Entities:
public interface IEntitie
{
int Id { get; set; }
}
public class Department : IEntitie
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; } = new HashSet<Employee>();
}
public class Employee : IEntitie
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public int DepartmentId { get; set; }
public virtual Department Department { get; set; }
public ICollection<Address> Addresses { get; set; } = new HashSet<Address>();
}
DbContext:
public class Context : Microsoft.EntityFrameworkCore.DbContext
{
#region Reps
public DbSet<Department> Departments { get; set; }
public DbSet<Employee> Employes { get; set; }
#endregion
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
var service = new GetConnectionStringService(); //получение строки подключения
optionsBuilder.UseSqlServer(service.Connection);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//явное описание связи 1 ко многим
//Employee < --> Departments One2Many----------------------------
modelBuilder.Entity<Employee>()
.HasOne(s => s.Department)
.WithMany(s => s.Employees)
.HasForeignKey(s => s.DepartmentId)
.OnDelete(DeleteBehavior.Restrict);
}
}
Repository:
public interface IGenericDataRepository<T> where T : class
{
IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties);
IList<T> GetList(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
T GetSingle(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties);
void Add(params T[] items);
void Update(params T[] items);
void Remove(params T[] items);
}
public class GenericDataRepository<T> : IGenericDataRepository<T> where T : class
{
public virtual IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties)
{
List<T> list;
using (var context = new Context())
{
IQueryable<T> dbQuery = context.Set<T>();
//Apply eager loading
foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
dbQuery = dbQuery.Include<T, object>(navigationProperty);
list = dbQuery
.AsNoTracking()
.ToList<T>();
}
return list;
}
public virtual IList<T> GetList(Func<T, bool> where,
params Expression<Func<T, object>>[] navigationProperties)
{
List<T> list;
using (var context = new Context())
{
IQueryable<T> dbQuery = context.Set<T>();
//Apply eager loading
foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
dbQuery = dbQuery.Include<T, object>(navigationProperty);
list = dbQuery
.AsNoTracking()
.Where(where)
.ToList<T>();
}
return list;
}
public virtual T GetSingle(Func<T, bool> where,
params Expression<Func<T, object>>[] navigationProperties)
{
T item = null;
using (var context = new Context())
{
IQueryable<T> dbQuery = context.Set<T>();
//Apply eager loading
foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
dbQuery = dbQuery.Include<T, object>(navigationProperty);
item = dbQuery
.AsNoTracking() //Don't track any changes for the selected item
.FirstOrDefault(where); //Apply where clause
}
return item;
}
public virtual void Add(params T[] items)
{
using (var context = new Context())
{
foreach (T item in items)
{
context.Entry(item).State = EntityState.Added;
}
context.SaveChanges();
}
}
public virtual void Update(params T[] items)
{
using (var context = new Context())
{
foreach (T item in items)
{
context.Entry(item).State = EntityState.Modified;
}
context.SaveChanges();
}
}
public virtual void Remove(params T[] items)
{
using (var context = new Context())
{
foreach (T item in items)
{
context.Entry(item).State = EntityState.Deleted;
}
context.SaveChanges();
}
}
}
конкретные репозитории, если нужны специфические свойства или переопределение virtual методов
public interface IDepartmentRepository : IGenericDataRepository<Department>
{
}
public interface IEmployeeRepository : IGenericDataRepository<Employee>
{
}
public interface IAdressRepository : IGenericDataRepository<Address>
{
}
public class DepartmentRepository : GenericDataRepository<Department>, IDepartmentRepository
{
}
public class EmployeeRepository : GenericDataRepository<Employee>, IEmployeeRepository
{
}
public class AddressRepository : GenericDataRepository<Address>, IAdressRepository
{
}
интерфейс бизнес логики:
public interface IBusinessLayer
{
IList<Department> GetAllDepartments();
Department GetDepartmentByName(string departmentName);
void AddDepartment(params Department[] departments);
void UpdateDepartment(params Department[] departments);
void RemoveDepartment(params Department[] departments);
IList<Employee> GetEmployeesByDepartmentName(string departmentName);
void AddEmployee(Employee employee);
void UpdateEmploee(Employee employee);
void RemoveEmployee(Employee employee);
Employee GetEmployeeById(int employeeId);
}
реализация бизнес логики:
public class BuinessLayer : IBusinessLayer
{
private readonly IDepartmentRepository _deptRepository;
private readonly IEmployeeRepository _employeeRepository;
private readonly IAdressRepository _adressRepository;
#region ctor
public BuinessLayer()
{
_deptRepository = new DepartmentRepository();
_employeeRepository = new EmployeeRepository();
_adressRepository= new AddressRepository();
}
public BuinessLayer(IDepartmentRepository deptRepository,
IEmployeeRepository employeeRepository)
{
_deptRepository = deptRepository;
_employeeRepository = employeeRepository;
}
#endregion
public IList<Department> GetAllDepartments()
{
return _deptRepository.GetAll();
}
public Department GetDepartmentByName(string departmentName)
{
return _deptRepository.GetSingle(
d => d.Name.Equals(departmentName),
d => d.Employees); //include related employees
}
public void AddDepartment(params Department[] departments)
{
/* Validation and error handling omitted */
_deptRepository.Add(departments);
}
public void UpdateDepartment(params Department[] departments)
{
/* Validation and error handling omitted */
_deptRepository.Update(departments);
}
public void RemoveDepartment(params Department[] departments)
{
/* Validation and error handling omitted */
_deptRepository.Remove(departments);
}
public IList<Employee> GetEmployeesByDepartmentName(string departmentName)
{
return _employeeRepository.GetList(e => e.Department.Name.Equals(departmentName));
}
public void AddEmployee(Employee employee)
{
/* Validation and error handling omitted */
_employeeRepository.Add(employee);
}
public void UpdateEmploee(Employee employee)
{
/* Validation and error handling omitted */
_employeeRepository.Update(employee);
}
public void RemoveEmployee(Employee employee)
{
/* Validation and error handling omitted */
_employeeRepository.Remove(employee);
}
public Employee GetEmployeeById(int employeeId)
{
return _employeeRepository.GetSingle(
d => d.Id.Equals(employeeId),
d => d.Addresses); //include related employees
}
}
ВОПРОСЫ:
я так понимаю слой BL включает в себя нужные репозитории, из которых можно получить(загрузить)связанные данные. на набор всего из 2-ух сушностей такое большое кол-во методов в IBusinessLayer, это немного напрягает.
Пример приведенн всего с одной связью 1 ко многим, а если будет глубина зависимостей больше, например у Employee появится набор адрессов (1 ко многим с таблицей адресов) тогда каким образом можно указать такую вложенность в методе репозитория с указанием navigationProperties? Необходимо явно указать .ThenInclude() и передать зависимое свойство, как это сделать например в GetAll?
public virtual IList<T> GetAll(params Expression<Func<T, object>>[] navigationProperties)
{
List<T> list;
using (var context = new Context())
{
IQueryable<T> dbQuery = context.Set<T>();
foreach (Expression<Func<T, object>> navigationProperty in navigationProperties)
dbQuery = dbQuery.Include<T, object>(navigationProperty); //???
list = dbQuery
.AsNoTracking()
.ToList<T>();
}
return list;
}
И как быть с реализацией многие ко многим которая разбивается на 2 реализации 1 ко многим. может я плохо понимаю как задавать Expression?
Может кто-то работал с подобным подходом прошу высказать свои мысли. Я бы сделал классическую реализацию репозитория возвращающую IQuereble и уже в методах бизнес логики составлял запросы (с необходимой глубиной вложенности), но методов в слое бизнес логики было бы опять много...
Подход, описанный выше вполне оправдан, не рекомендовал бы возвращать IQueryable, поскольку каждый слой должен инкапсулировать свою логику от внешнего мира, перенос части логики DAL нарушает само понятие разделения на слои.
К примеру, в какой то момент вы решили некоторые запросы перенести на Dapper(или другой микро-ORM) для увеличения производительности, однако вам придется менять:
Да, кол-во методов будет расти с ростом вашего приложения, этого никак не избежать (понимаю стремление к идеалу архитектуры, но такого, увы, не найти). Выше в комментариях привели CQRS, но в этом случае будет расти кол-во классов(причем со скоростью, гораздо большей, чем кол-во репозиториев).
Оборудование для ресторана: новинки профессиональной кухонной техники
Частный дом престарелых в Киеве: комфорт, забота и профессиональный уход
Добрый вечер! Есть двумерный массив arr и некорректная проверка на вхождение цифры 1