什么是Repository模式,为什么要使用它?
使用Repository模式,我们在应用程序的数据访问和业务逻辑层之间创建一个抽象层。通过使用它,我们正在推广一种更松散耦合的方法来访问数据库中的数据。此外,代码更清洁,更易于维护和重用。数据访问逻辑位于一个单独的类中,或者称为存储库的类集,负责持久化应用程序的业务模型。
实施 存储库模式是本文的主题。
那么我们开始吧。
创建模型
首先创建一个名为Entities的新类库(.NET Core)项目,并在其中创建一个名为Models的文件夹,该文件夹将包含所有模型类。模型类将表示数据库中的表格,并将为您提供将数据库中的数据映射到.NET Core的功能。之后,将该项目引用到主项目中。
在Models文件夹中创建两个类并添加代码,如下所示:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Entities.Models
{
[Table("owner")]
public class Owner
{
[Key]
public Guid OwnerId { get; set; }
[Required(ErrorMessage = "Name is required")]
[StringLength(60, ErrorMessage = "Name can't be longer than 60 characters")]
public string Name { get; set; }
[Required(ErrorMessage = "Date of birth is required")]
public DateTime DateOfBirth { get; set; }
[Required(ErrorMessage = "Address is required")]
[StringLength(100, ErrorMessage = "Address cannot be loner then 100 characters")]
public string Address { get; set; }
}
}
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Entities.Models
{
[Table("account")]
public class Account
{
[Key]
public Guid AccountId { get; set; }
[Required(ErrorMessage = "Date created is required")]
public DateTime DateCreated { get; set; }
[Required(ErrorMessage = "Account type is required")]
public string AccountType { get; set; }
[Required(ErrorMessage = "Owner Id is required")]
public Guid OwnerId { get; set; }
}
}
如您所见,有两个使用属性Table(“tableName”)装饰的模型。该属性将在数据库中配置相应的表名。此外,每个主键都具有用于配置表的主键的装饰属性Key。所有必填字段都具有[必需的] 属性,并且如果要约束字符串,则可以使用属性[StringLength]。
请注意,这些模型是干净的,它们只有那些与表格中的列匹配的属性。我喜欢尽可能保持模型的清洁 ,这是做到这一点的方法。稍后,我们将创建带有属性的DTO类,以将一个所有者与其所有帐户连接起来,或将一个帐户与其所有者连接起来。
上下文类和数据库连接
现在,让我们创建上下文类,它将成为与数据库通信的中间件组件。它具有包含来自数据库的表数据的DbSet属性。
在实体项目的根目录下,创建RepositoryContext类并按如下所示进行编码:
using Entities.Models;
using Microsoft.EntityFrameworkCore;
namespace Entities
{
public class RepositoryContext: DbContext
{
public RepositoryContext(DbContextOptions options)
:base(options)
{
}
public DbSet Owners { get; set; }
public DbSet Accounts { get; set; }
}
}
要启用.NET核心部分和MySQL数据库之间的通信,您需要安装名为Pomelo.EntityFrameworkCore.MySql的第三方库。在主项目中,您可以使用NuGet包管理器或包管理器控制台进行安装。
安装后,打开 appsettings.json文件并在其中添加数据库连接设置。
{
"Logging": {
"IncludeScopes": false,
"Debug": {
"LogLevel": {
"Default": "Warning"
}
},
"Console": {
"LogLevel": {
"Default": "Warning"
}
}
},
"mysqlconnection": {
"connectionString": "server=localhost;userid=root;password=yourpass;database=accountowner;"
}
}
在 ServiceExtensions类中,您将编写用于配置MySQL上下文的代码。
首先,添加 using指令,然后添加 方法ConfigureMySqlContext:
using Microsoft.Extensions.Configuration;
using Microsoft.EntityFrameworkCore;
public static void ConfigureMySqlContext(this IServiceCollection services, IConfiguration config)
{
var connectionString = config["mysqlconnection:connectionString"];
services.AddDbContext(o => o.UseMySql(connectionString));
}
借助 IConfiguration配置参数,您可以访问 appsettings.json文件并从中获取所需的全部数据。之后,在 ConfigureServices方法的Startup类中,将上下文服务添加到services.AddMvc()上面的IOC 。
services.ConfigureMySqlContext(Configuration);
存储库模式逻辑
建立与数据库的连接后,是时候创建通用存储库来为我们提供所有的 CRUD方法。因此,可以在项目中的任何存储库类上调用所有方法。
此外,创建使用该通用存储库的通用存储库和存储库类不会是最后一步。你会更进一步,创建一个围绕着存储库类的包装并将其作为服务注入。因此,您可以实例化这个包装器,然后在您的任何控制器内调用您需要的任何存储库类。 当我们在项目中使用它时,你会理解这个包装的优点。
首先,让我们为Contracts项目中的存储库创建一个接口。
namespace Contracts
{
public interface IRepositoryBase
{
IEnumerable FindAll();
IEnumerable FindByCondition(Expression> expression);
void Create(T entity);
void Update(T entity);
void Delete(T entity);
void Save();
}
}
在创建接口之后,您将创建一个 名为Repository的新类库(.NET Core)项目(将Contracts 的引用添加到此项目中),并在Repository项目中创建抽象类RepositoryBase,它将实现接口 IRepositoryBase 。
也参考这个项目到主项目。
提示:如果您在组件中引用EntityFrameworkCore时遇到问题,那么对于这个新的Repository项目,您需要右键单击Repository项目,然后单击Unload项目。当项目卸载时,右键点击它并选择Edit Repository.csproj。将此代码添加到打开的文件中:
最后,保存文件并右键单击Repository项目并单击Reload Project。
RepositoryBase类应如下所示:
using Contracts;
using Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace Repository
{
public abstract class RepositoryBase : IRepositoryBase where T : class
{
protected RepositoryContext RepositoryContext { get; set; }
public RepositoryBase(RepositoryContext repositoryContext)
{
this.RepositoryContext = repositoryContext;
}
public IEnumerable FindAll()
{
return this.RepositoryContext.Set();
}
public IEnumerable FindByCondition(Expression> expression)
{
return this.RepositoryContext.Set().Where(expression);
}
public void Create(T entity)
{
this.RepositoryContext.Set().Add(entity);
}
public void Update(T entity)
{
this.RepositoryContext.Set().Update(entity);
}
public void Delete(T entity)
{
this.RepositoryContext.Set().Remove(entity);
}
public void Save()
{
this.RepositoryContext.SaveChanges();
}
}
}
这个抽象类以及IRepositoryBase接口使用泛型类型T来处理。这个类型T给RepositoryBase类提供了更多的可重用性。这意味着您现在不必为RepositoryBase指定确切的模型(类),稍后您将执行此操作。
存储库用户类
现在您已拥有RepositoryBase类,请创建 将继承此抽象类的用户类。每个用户类都有自己的接口,用于其他模型特定的方法。此外,通过继承RepositoryBase类,它们将可以访问RepositoryBase中的所有方法。通过这种方式,我们将逻辑分离出来,这对于我们所有的存储库用户类都很常见,并且也针对每个用户类本身。
让我们为您的Owner和Account类在Contracts项目中创建接口。
不要忘记从实体项目添加引用。
using Entities.Models;
namespace Contracts
{
public interface IAccountRepository: IRepositoryBase
{
}
using Entities.Models;
namespace Contracts
{
public interface IAccountRepository: IRepositoryBase
{
}
}
现在在Repository项目中创建一个存储库用户类:
using Contracts;
using Entities;
using Entities.Models;
namespace Repository
{
public class OwnerRepository: RepositoryBase, IOwnerRepository
{
public OwnerRepository(RepositoryContext repositoryContext)
:base(repositoryContext)
{
}
}
}
using Contracts;
using Entities;
using Entities.Models;
namespace Repository
{
public class AccountRepository: RepositoryBase, IAccountRepository
{
public AccountRepository(RepositoryContext repositoryContext)
:base(repositoryContext)
{
}
}
}
完成这些步骤后,您已完成创建存储库和存储库用户类的工作。但还有更多的 事情要做。
创建存储库包装
想象一下,如果在 控制器内部,您需要收集所有所有者并仅收集特定账户(例如国内账户)。您需要实例化OwnerRepository和AccountRepository类,然后调用FindAll和FindByCondition方法。
当你只有两个类时,也许这不是问题,但是如果你需要5个不同类或甚至更多的逻辑。记住这一点,让我们围绕您的存储库用户类创建一个包装。然后将其放入IOC中,最后将其注入控制器的构造函数中。现在,使用该包装器实例,您可以调用您需要的任何存储库类。
在合同项目中创建一个新界面:
namespace Contracts
{
public interface IRepositoryWrapper
{
IOwnerRepository Owner { get; set; }
IAccountRepository Account { get; set; }
}
}
向Repository项目添加一个新类:
using Contracts;
using Entities;
namespace Repository
{
public class RepositoryWrapper: IRepositoryWrapper
{
public IOwnerRepository Owner { get; set; }
public IAccountRepository Account { get; set; }
public RepositoryWrapper(RepositoryContext repositoryContext)
{
this.Owner = new OwnerRepository(repositoryContext);
this.Account = new AccountRepository(repositoryContext);
}
}
}
在ServiceExtensions类中添加以下代码:
public static void ConfigureRepositoryWrapper(this IServiceCollection services)
{
services.AddScoped();
}
在ConfigureServices方法中的Startup类中 ,在 services.AddMvc()行的上方添加以下代码:
services.ConfigureRepositoryWrapper();
所有你需要做的就是测试这个代码,就像你在本系列的第3部分中使用自定义记录器 一样。
在 Values控制器内注入RepositoryWrapper服务,并调用 RepositoryBase类中的任何方法。像这样的东西:
[Route("api/[controller]")]
public class ValuesController : Controller
{
private IRepositoryWrapper _repoWrapper;
public ValuesController(IRepositoryWrapper repoWrapper)
{
_repoWrapper = repoWrapper;
}
// GET api/values
[HttpGet]
public IEnumerable Get()
{
var domesticAccounts = _repoWrapper.Account.FindByCondition(x => x.AccountType.Equals("Domestic"));
var owners = _repoWrapper.Owner.FindAll();
return new string[] { "value1", "value2" };
}
}
将断点放在 Get()方法中,您将看到从数据库返回的数据。
结论
存储库模式增加了代码中的抽象级别。对于不熟悉模式的开发人员来说,这可能会使代码更难理解。但是,一旦你熟悉它,它将减少冗余代码的数量, 并使得逻辑更容易维护。
在这篇文章中,你已经了解到:
什么是存储库模式
如何创建模型和模型属性
如何创建上下文类和数据库连接
创建存储库逻辑的正确方法
以及围绕您的存储库类创建包装的方式
谢谢大家阅读这篇文章,我希望你阅读一些有用的信息。
在下一篇文章中很快会看到,我们将使用存储库逻辑创建HTTP请求。
对于任何建议 或问题,请不要犹豫,在下面的部分留下评论。
领取专属 10元无门槛券
私享最新 技术干货