在EntityFramworkCore 配置种子数据中有降到如何配置种子数据,接下里就讲一讲数据仓储模式。
目录
1.动机
1.1 时下数据驱动应用是如何访问数据库
传统的数据库应用结构如下:
从上图可以看出,对雇员的CURD操作是直接通过controller访问EFcore 的data context,EF将来自上层的CURD操作转化为SQL语句。那这样做有什么缺点呢:当然上述方法肯定是行之有效的,但是这样我们会把对数据的查询,修改,删除操作深度绑定到Controller这一层,这样的设计或者实现会造成代码冗余,且不利于后续维护,一旦数据访问逻辑发生了一点点小改动都需要对Controller进行修改。
1.2Repository Design Pattern
所以,这里就引入了数据仓储模式,这也是成为业界通用的做法,引入数据仓储模式后的架构如下:
简而言之就是我们在领域层和数据映射层之间加了一层,这一层类似于集合访问接口,专门用于领域层访问数据。换句话说,Repository Design Pattern在数据访问层和其它应用层之间扮演者一个中间人,应用获取数据的所有操作都交给这个中间人。好处是自然而然的将上层应用和低层数据访问隔离开来,当你改动一端时,不用修改另一端。而且测试Controller也会变得简单,你可以模拟一个内存数据库,而不用真的配置一个真实的数据库。
2.实现
2.1具体仓储的实现
为了方便依赖注入,我们首先定义一个仓库的接口:
简单起见我就定义下面几个方法(你可以根据具体的业务添加):
using RepositoryPatternStudy.Models;
namespace RepositoryPatternStudy.Repository
{
public interface IEmployeeRepository
{
IEnumerable<Employee> GetAll();
Employee GetById(int id);
void Insert(Employee employee);
void Update(Employee employee);
void Delete(Employee employee);
void Save();
}
}
下面添加具体的实现:
public class EmployeeRepository : IEmployeeRepository,IDisposable
{
private readonly EmployeeDbContext _context;
public EmployeeRepository(EmployeeDbContext context)
{
_context = context;
}
public void Delete(int employeeID)
{
Employee employee = _context.Employees.Find(employeeID);
_context.Employees.Remove(employee);
}
public IEnumerable<Employee> GetAll()
{
return _context.Employees.ToList();
}
public Employee? GetById(int id)
=> _context.Employees.Find(id);
public void Insert(Employee employee)
=>_context.Employees.Add(employee);
public void Save()
=>_context.SaveChanges();
public void Update(Employee employee)
=>_context.Entry(employee).State=EntityState.Modified;
public bool disposed=false;
protected virtual void Dispose(bool disposing)
{
if(!disposed)
{
if(disposing)
_context.Dispose();
}
disposed=true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
为了验证我们的正确性,我们可以新建一个EmployeeController:
public class EmployeeController : Controller
{
private readonly IEmployeeRepository _employeeRepository;
public EmployeeController(IEmployeeRepository employeeRepository)
=>_employeeRepository = employeeRepository;
[HttpGet]
public List<Employee> Index()
{
var model=_employeeRepository.GetAll();
return model.ToList();
}
}
(在这里用了依赖注入,所以要记得在配置文件中添加服务:)
builder.Services.AddTransient<IEmployeeRepository,EmployeeRepository>();
运行程序打开Employee对于的页面:
可以看到成功的拿到了数据库的数据。至此具体的仓储就实现完成了!
当然,你可以添加相应的视图,使得显示效果更更好:
@model IEnumerable<RepositoryUsingEFinMVC.DAL.Employee>
@{
ViewBag.Title = "Index";
}
<h2>Index</h2>
<p>
@Html.ActionLink("Add Employee", "AddEmployee")
</p>
<table class="table">
<tr>
<th>
@Html.DisplayNameFor(model => model.Name)
</th>
<th>
@Html.DisplayNameFor(model => model.Gender)
</th>
<th>
@Html.DisplayNameFor(model => model.Salary)
</th>
<th>
@Html.DisplayNameFor(model => model.Dept)
</th>
<th></th>
</tr>
@foreach (var item in Model)
{
<tr>
<td>
@Html.DisplayFor(modelItem => item.Name)
</td>
<td>
@Html.DisplayFor(modelItem => item.Gender)
</td>
<td>
@Html.DisplayFor(modelItem => item.Salary)
</td>
<td>
@Html.DisplayFor(modelItem => item.Dept)
</td>
<td>
@Html.ActionLink("Edit", "EditEmployee", new { EmployeeId = item.EmployeeID }) |
@Html.ActionLink("Delete", "DeleteEmployee", new { EmployeeId = item.EmployeeID })
</td>
</tr>
}
</table>
显示效果如下:
2.2泛型仓储
因为我们只有一个实体类型:Employee,但实际上的项目实体肯定不止一个,可能还有订单order,顾客Custome等,如果没有泛型仓储,我们可能需要为每个实体构建一个仓库,这是很麻烦的。
有了前面的基础,我们构建泛型仓库就简单很多,首先定义一个泛型仓库接口:
public interface IGenericRepository<T> where T:class
{
IEnumerable<T> GetAll();
T GetById(object id);
void Insert(T obj);
void Update(T obj);
void Delete(object id);
void Save();
}
然后实现它:
public class GenericRepository<T> : IGenericRepository<T> where T : class
{
private readonly EmployeeDbContext _context;
private readonly DbSet<T> table;
public GenericRepository(EmployeeDbContext context)
{
_context = context;
table = context.Set<T>();
}
public void Delete(object id)
{
T existing=table.Find(id);
table.Remove(existing);
}
public IEnumerable<T> GetAll()
=>table.ToList();
public T GetById(object id)
=> table.Find(id);
public void Insert(T obj)
=>table.Add(obj);
public void Save()
=>_context.SaveChanges();
public void Update(T obj)
{
table.Attach(obj);
_context.Entry(obj).State = EntityState.Modified;
}
}
将泛型仓储注册到服务:
builder.Services.AddTransient(typeof(IGenericRepository<>), typeof(GenericRepository<>));
然后在Controller中将泛型仓储替换原来的仓储:
private readonly IEmployeeRepository _employeeRepository;
private readonly IGenericRepository<Employee> _repository;
public EmployeeController(IEmployeeRepository employeeRepository, IGenericRepository<Employee> genericRepository)
{
_employeeRepository = employeeRepository;
_repository = genericRepository;
}
[HttpGet]
public ActionResult Index()
{
//var model=_employeeRepository.GetAll();
var model = _repository.GetAll();
return View(model);
可以得到相同的运行结果(我这里同时将两种类型的仓储都注册进去了)。
2.3 同时使用两种仓库
泛型仓储一般是比较通用的方法,也就是常见的CURD,一旦想定制化仓库,比扩展一些功能,则需要自行实现,但是如果已经有了泛型仓储,那么通过继承,我们可以只实现不同的部分,这样也带来了相当大的遍历。
操作如下:
public interface IEmployeeRepositoryEx:IGenericRepository<Employee>
{
IEnumerable<Employee> GetEmployeesByGender(string gender);
IEnumerable<Employee> GetEmployeesByDepartment(string Dept);
}
上面是一个扩充了两个方法的新接口,其实现如下:
public class EmployeeeRepositoryEx : GenericRepository<Employee>, IEmployeeRepositoryEx
{
// private readonly IGenericRepository<Employee> _repository;
// private readonly IEmployeeRepositoryEx _employeeRepositoryEx;
private readonly EmployeeDbContext _context;
public EmployeeeRepositoryEx(EmployeeDbContext context):base(context)
// IGenericRepository<Employee> genericRepository,IEmployeeRepositoryEx employeeRepositoryEx):base(context)
{
// _repository = genericRepository;
// _employeeRepositoryEx = employeeRepositoryEx;
_context = context;
}
public IEnumerable<Employee> GetEmployeesByDepartment(string Dept)
{
return _context.Employees.Where(x=>x.Dept == Dept);
}
public IEnumerable<Employee> GetEmployeesByGender(string gender)
{
return _context.Employees.Where(x=>x.Gender == gender);
}
}
将IEmployeeRepository注册,并替换后,可以同时使用泛型接口和新接口的方法,从而达到了定制化扩展,同样你可以放在EmployeeController中尝试。
到此为止,仓储模型就基本讲完了,