目录
介绍
存储库模式
优点
隔离数据访问逻辑
单元测试
高速缓存
数据源迁移
复杂查询被封装
实现的经验法则
项目结构
依赖注入(DI)
DI的优点
使用代码
数据访问层
工作单位(UOW)
业务层
API
争议
为什么?
我的结论
- 下载源代码 - 271.7 KB
在学习之后实现存储库模式时,我遇到了许多正确实现的问题。但是我没有找到适合实施的完整解决方案。这促使我写了这篇文章。
本文将指导您使用存储库模式创建一个小应用程序,其中包含ASP.NET Core中的工作单元。本文主要针对初级到中级程序员。在本文中,我想提供实现的总体情况。
在这里,我不想提供通用存储库模式实现的详细信息。每当我搜索存储库模式实现时,我都会遇到许多带有通用存储库模式的示例。
完成本文后,您将正确理解特定存储库模式的实现。
存储库模式存储库在域和数据映射层之间进行调解,其作用类似于内存中的域对象集合。
当我们想要封装逻辑以访问数据源时,存储库模式很有用。这里,存储库描述了访问数据源的类或组件。
存储库充当数据源和应用程序的业务层之间的中介。它在数据源中查询数据,将数据从数据源映射到业务实体,并将业务实体中的更改持久保存到数据源。
我们为什么要封装?
当我们想要从业务层分离数据访问功能时,我们将转移到存储库模式。
当通用存储库为最常见的数据操作类型(如更新、提取和删除)定义通用方法时,它非常有用。
在某些情况下,我们可能不需要对所有类型的存储库执行常见操作。
所以我们需要特定的存储库。这是基于我们将要实施的项目。
在存储库模式实现中,业务逻辑到数据访问逻辑和API到业务逻辑使用接口相互通信。数据访问层隐藏了业务逻辑的数据访问细节。详细说明,业务逻辑可以在不了解数据源的情况下访问数据访问层。
例如,业务层不知道数据访问层是使用LINQ to SQL还是ADO.NET等。
优点以下是存储库模式的主要优点。
隔离数据访问逻辑数据访问功能是集中的。因此,业务层将不知道数据来自何处。它可能来自任何数据源或缓存或模拟数据。
单元测试基于前面的内容,这将理解业务层不知道数据来自何处。模拟数据访问层很容易。所以这将有助于我们为业务逻辑编写单元测试。
我们不能为数据访问层编写任何测试吗?为什么不?我们可以为这一层编写集成测试。
高速缓存由于数据访问功能是集中的,我们可以为该层实现缓存。
数据源迁移我们可以轻松地从一个数据源迁移到另一个数据源。迁移时,这不会影响我们的业务逻辑。
复杂查询被封装复杂查询被封装并移动到该层。因此,可以从业务层重用查询。
当任何开发人员在编写查询方面都很强时,他/她可以独立地处理查询,而另一个开发人员可以专注于业务逻辑。
实现的经验法则- 每个存储库都应该基于Domain实现,而不是基于数据库实体。
- 每个存储库都不应该相互联系。
- IQueryable不应该是存储库模式实现的返回类型。他们应该只返回IEnumerable。
- 它们不应该保存/删除/添加任何数据到数据库。所有细节都应该在内存中。我们可能会考虑如何进行粗暴操作。在这里,工作单元扮演着这个角色。工作单元将详细信息保存到数据库或回滚。这有什么好处?这将一次性保存存储库中发生的多个事务。
- 数据层不应该实现业务逻辑。业务逻辑应该在业务层中实现。它们应该返回数据的表示,业务层应该封装返回或解封装请求。
以下是我们要实现的项目结构。请从链接下载样本。这里PL使用Angular应用程序。ASP.NET Core已应用于API和业务层,然后用于数据访问层。
业务层和数据访问层将具有单独的约定(接口)。业务层和数据访问层将取决于抽象而不是具体实现。
这是因为依赖注入。因此,任何层都不会有关于另一层的知识。当我们进行模拟和测试时,这很容易。
- 表示层(PL)
- API
- 业务层(BL)
- 数据访问层(DAL)
有关应用程序流程,请参阅下图。PL将联系API。API将联系BL。BL将联系DAL。
我们将进行一个松散耦合的实现。业务层将不知道数据访问层。API不会知道BL。
对于此实现,我们将实现依赖注入(DI)。
依赖注入(DI)什么是依赖注入?
较高级别的模块不应该依赖于较低级别的模块。依赖注入主要用于将具体实现注入到使用抽象的类中,即内部接口。这使得能够开发松散耦合的代码。
详细地说,如果你的ClassA需要使用 ClassB,请让我们的ClassA知道一个IClassB接口而不是一个ClassB。通过这次执行,我们可以在ClassB不破坏主机代码的情况下多次更改实现。
DI的优点- 干净、更易读的代码
- 类或对象松散耦合
- 模拟对象很容易
请考虑以下示例以实现此实现。
- 用户的CRUD操作
- 产品的CRUD操作
- 向/从用户添加或删除产品。只能为用户分配一个产品。
现在我们必须确定问题的领域。基于上面的示例,我们确定了两个领域。
- 用户领域
- 产品领域
基于经验法则,我们需要基于领域创建存储库。因此,在此示例中,我们将为上述两个领域创建两个存储库:
- 用户存储库
- 产品存储库
要创建UserRepository和ProductRepository,分别创建将实现仓库接口IUserRepository,IProductRepository。
IUserRepository
public interface IUserRepository
{
void AddUser(User user);
IEnumerable GetUsers();
bool DeleteUser(long userId);
User GetUser(long Id);
}
IProductRepository
public interface IProductRepository
{
void AddProduct(Product product);
Product GetProduct(long id);
IEnumerable GetProducts();
bool DeleteProduct(long productId);
IEnumerable GetUserProducts(long userId);
void AddProductToUser(long userId, long productId);
}
现在创建将实现抽象的具体类,即接口。
这些具体的类将具有实际的实现。在这里,我们可以注意到:
- 每个添加或删除都在内存中实现,而不是在数据源中实现
- 数据源没有更新。
UserRepository
public class UserRepository : IUserRepository
{
private readonly AppDbContext context;
public UserRepository(AppDbContext dbContext)
{
this.context = dbContext;
}
public void AddUser(User user)
{
context.Users.Add(user);
}
public bool DeleteUser(long userId)
{
var removed = false;
User user = GetUser(userId);
if (user != null)
{
removed = true;
context.Users.Remove(user);
}
return removed;
}
public User GetUser(long Id)
{
return context.Users.Where(u => u.Id == Id).FirstOrDefault();
}
public IEnumerable GetUsers()
{
return context.Users;
}
}
ProductRepository
public class ProductRepository : IProductRepository
{
private readonly AppDbContext context;
public ProductRepository(AppDbContext dbContext)
{
this.context = dbContext;
}
public void AddProduct(Product product)
{
context.Products.Add(product);
}
public void AddProductToUser(long userId, long productId)
{
context.UserProducts.Add(new UserProduct()
{
ProductId = productId,
UserId = userId
});
}
public bool DeleteProduct(long productId)
{
var removed = false;
Product product = GetProduct(productId);
if (product != null)
{
removed = true;
context.Products.Remove(product);
}
return removed;
}
public Product GetProduct(long id)
{
return context.Products.Where(p => p.Id == id).FirstOrDefault();
}
public IEnumerable GetProducts()
{
return context.Products;
}
public IEnumerable GetUserProducts(long userId)
{
return context.UserProducts
.Include(up => up.Product)
.Where(up => up.UserId == userId)
.Select(p => p.Product)
.AsEnumerable();
}
}
工作单位(UOW)
从上面的实现中,我们可以理解应该使用存储库:
- 从数据源读取数据
- 在内存中添加/删除数据
那么添加/更新/删除将如何影响数据源?UOW扮演这个角色。UOW知道每个存储库。这有助于一次实现多个事务。
对于这种实现,需要实现如上所述。创建一个具体的UnitOfWork 将实现抽象,即接口IUnitOfWork。
IUnitOfWork
public interface IUnitOfWork
{
IUserRepository User { get; }
IProductRepository Product { get; }
Task CompleteAsync();
int Complete();
}
UnitOfWork
public class UnitOfWork : IUnitOfWork
{
private readonly AppDbContext dbContext;
public UnitOfWork(AppDbContext dbContext)
{
this.dbContext = dbContext;
}
private IUserRepository _User;
private IProductRepository _Product;
public IUserRepository User
{
get
{
if (this._User == null)
{
this._User = new UserRepository(dbContext);
}
return this._User;
}
}
public IProductRepository Product
{
get
{
if (this._Product == null)
{
this._Product = new ProductRepository(dbContext);
}
return this._Product;
}
}
public async Task CompleteAsync()
{
return await dbContext.SaveChangesAsync();
}
public int Complete()
{
return dbContext.SaveChanges();
}
public void Dispose() => dbContext.Dispose();
}
我们已经使用UOW为DAL完成了存储库模式实现。
以下是愚蠢的。执行此操作后,我对如何在保存数据之前需要从另一个存储库获取数据进行检查感到困惑。例如,将产品添加到用户时,请检查用户或产品是否存在。
此方案将违反规则,即存储库不应在其中进行交互。发生了什么?我现在应该怎么做?在这里,我的理解是错误的。业务逻辑不应出现在存储库模式中。这只是数据访问的封装。每个逻辑验证都应该移到业务层。业务层将了解负责验证的所有存储库。
业务层现在我们需要专注于业务层。在这一层中,我们将注入UOW而不是所有必需的存储库。UOW知道所有的存储库,我们可以使用UOW访问。
例如,为了实现Product的BL,我们将创建一个接口IProduct ,并需要创建一个将实现的IProduct接口的具体类BLProduct 。
下面的BLProduct中,所有必要的验证和业务逻辑已经完成,我们可以在AddProductToUser 方法中注意作为多个存储库使用的示例。
IProduct
public interface IProduct
{
Product UpsertProduct(Product product);
IEnumerable GetProducts();
bool DeleteProduct(long productId);
IEnumerable GetUserProducts(long userId);
bool AddProductToUser(long userId, long productId);
}
BLProduct
public class BLProduct : IProduct
{
private readonly IUnitOfWork uow;
public BLProduct(IUnitOfWork uow)
{
this.uow = uow;
}
public bool AddProductToUser(long userId, long productId)
{
if (userId
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?