Gui*_*mez 13 asp.net-mvc design-patterns dependency-injection unit-of-work repository-pattern
我正在设计一个Web应用程序和一个Windows服务,并希望将工作单元+存储库层与服务层结合使用,我在将它们放在一起时遇到一些麻烦,以便客户端应用程序控制数据的事务处理.工作单位.
工作单元具有在事务中注册的所有存储库的集合以及提交和回滚操作
public interface IUnitOfWork : IDisposable
{
IRepository<T> Repository<T>() where T : class;
void Commit();
void Rollback();
}
Run Code Online (Sandbox Code Playgroud)
通用存储库具有将在特定模型(表)的数据层上执行的操作
public interface IRepository<T> where T : class
{
IEnumerable<T> Get(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, IList<ISortCriteria<T>> sortCriterias = null);
PaginatedList<T> GetPaged(Expression<Func<T, bool>> filter = null, IList<Expression<Func<T, object>>> includedProperties = null, PagingOptions<T> pagingOptions = null);
T Find(Expression<Func<T, bool>> filter, IList<Expression<Func<T, object>>> includedProperties = null);
void Add(T t);
void Remove(T t);
void Remove(Expression<Func<T, bool>> filter);
}
Run Code Online (Sandbox Code Playgroud)
工作单元的具体实现使用引擎下的实体框架(DbContext)来保存对数据库的更改,并且每单元工作创建一个新的DbContext类实例
public class UnitOfWork : IUnitOfWork
{
private IDictionary<Type, object> _repositories;
private DataContext _dbContext;
private bool _disposed;
public UnitOfWork()
{
_repositories = new Dictionary<Type, object>();
_dbContext = new DataContext();
_disposed = false;
}
Run Code Online (Sandbox Code Playgroud)
如果工作单元中的存储库不存在于当前工作单元实例中,则会在访问时创建存储库.存储库将DbContext作为构造函数参数,因此它可以有效地在当前工作单元中工作
public class Repository<T> : IRepository<T> where T : class
{
private readonly DataContext _dbContext;
private readonly DbSet<T> _dbSet;
#region Ctor
public Repository(DataContext dbContext)
{
_dbContext = dbContext;
_dbSet = _dbContext.Set<T>();
}
#endregion
Run Code Online (Sandbox Code Playgroud)
我还有一个服务类,它封装业务工作流逻辑并在构造函数中获取它们的依赖项.
public class PortfolioRequestService : IPortfolioRequestService
{
private IUnitOfWork _unitOfWork;
private IPortfolioRequestFileParser _fileParser;
private IConfigurationService _configurationService;
private IDocumentStorageService _documentStorageService;
#region Private Constants
private const string PORTFOLIO_REQUEST_VALID_FILE_TYPES = "PortfolioRequestValidFileTypes";
#endregion
#region Ctors
public PortfolioRequestService(IUnitOfWork unitOfWork, IPortfolioRequestFileParser fileParser, IConfigurationService configurationService, IDocumentStorageService documentStorageService)
{
if (unitOfWork == null)
{
throw new ArgumentNullException("unitOfWork");
}
if (fileParser == null)
{
throw new ArgumentNullException("fileParser");
}
if (configurationService == null)
{
throw new ArgumentNullException("configurationService");
}
if (documentStorageService == null)
{
throw new ArgumentNullException("configurationService");
}
_unitOfWork = unitOfWork;
_fileParser = fileParser;
_configurationService = configurationService;
_documentStorageService = documentStorageService;
}
#endregion
Run Code Online (Sandbox Code Playgroud)
Web应用程序是一个ASP.NET MVC应用程序,控制器也会在构造函数中注入其依赖项.在这种情况下,注入工作单元和服务类.该操作执行服务公开的操作,例如在存储库中创建记录并使用DocumentStorageService将文件保存到文件服务器,然后在控制器操作中提交工作单元.
public class PortfolioRequestCollectionController : BaseController
{
IUnitOfWork _unitOfWork;
IPortfolioRequestService _portfolioRequestService;
IUserService _userService;
#region Ctors
public PortfolioRequestCollectionController(IUnitOfWork unitOfWork, IPortfolioRequestService portfolioRequestService, IUserService userService)
{
_unitOfWork = unitOfWork;
_portfolioRequestService = portfolioRequestService;
_userService = userService;
}
#endregion
[HttpPost]
[ValidateAntiForgeryToken]
[HasPermissionAttribute(PermissionId.ManagePortfolioRequest)]
public ActionResult Create(CreateViewModel viewModel)
{
if (ModelState.IsValid)
{
// validate file exists
if (viewModel.File != null && viewModel.File.ContentLength > 0)
{
// TODO: ggomez - also add to CreatePortfolioRequestCollection method
// see if file upload input control can be restricted to excel and csv
// add additional info below control
if (_portfolioRequestService.ValidatePortfolioRequestFileType(viewModel.File.FileName))
{
try
{
// create new PortfolioRequestCollection instance
_portfolioRequestService.CreatePortfolioRequestCollection(viewModel.File.FileName, viewModel.File.InputStream, viewModel.ReasonId, PortfolioRequestCollectionSourceId.InternalWebsiteUpload, viewModel.ReviewAllRequestsBeforeRelease, _userService.GetUserName());
_unitOfWork.Commit();
}
catch (Exception ex)
{
ModelState.AddModelError(string.Empty, ex.Message);
return View(viewModel);
}
return RedirectToAction("Index", null, null, "The portfolio construction request was successfully submitted!", null);
}
else
{
ModelState.AddModelError("File", "Only Excel and CSV formats are allowed");
}
}
else
{
ModelState.AddModelError("File", "A file with portfolio construction requests is required");
}
}
IEnumerable<PortfolioRequestCollectionReason> portfolioRequestCollectionReasons = _unitOfWork.Repository<PortfolioRequestCollectionReason>().Get();
viewModel.Init(portfolioRequestCollectionReasons);
return View(viewModel);
}
Run Code Online (Sandbox Code Playgroud)
在Web应用程序上,我使用Unity DI容器将每个http请求的工作单元的相同实例注入到所有调用者,因此控制器类获取一个新实例,然后使用该工作单元的服务类获得相同的实例作为控制者.这样,服务将一些记录添加到存储库中,该存储库在工作单元中注册,并且可以由控制器中的客户端代码提交.
关于上述代码和体系结构的一个问题.如何摆脱服务类中的工作依赖单元?理想情况下,我不希望服务类具有工作单元的实例,因为我不希望服务提交事务,我只是希望服务引用它需要使用的存储库,并让控制器(客户端代码)在看到拟合时提交操作.
在Windows服务应用程序中,我希望能够获得一组具有单个工作单元的记录,例如所有处于挂起状态的记录.然后我想遍历所有这些记录并查询数据库以单独获取每个记录,然后在每个循环期间检查每个记录的状态,因为状态可能已经从我查询的所有时间改变到我想要操作的时间一个人.我现在遇到的问题是我当前的架构不允许我为同一个服务实例提供多个工作单元.
public class ProcessPortfolioRequestsJob : JobBase
{
IPortfolioRequestService _portfolioRequestService;
public ProcessPortfolioRequestsJob(IPortfolioRequestService portfolioRequestService)
{
_portfolioRequestService = portfolioRequestService;
}
Run Code Online (Sandbox Code Playgroud)
上面的Job类将构造函数中的服务作为依赖项,并由Unity再次解析.解析和注入的服务实例取决于工作单元.我想在服务类上执行两个get操作,但因为我在相同的工作单元实例下运行,所以我无法实现.
对于你们所有的大师们,你们对我如何重新构建我的应用程序,工作单元+存储库+服务类以实现上述目标有什么建议吗?
我打算使用工作单元+存储库模式来实现我的服务类的可测试性,但我对其他设计模式持开放态度,这些模式将使我的代码同时可维护和可测试,同时保持关注点的分离.
更新1 添加继承自EF的DbContext的DataContext类,我在其中声明了我的EF DbSets和配置.
public class DataContext : DbContext
{
public DataContext()
: base("name=ArchSample")
{
Database.SetInitializer<DataContext>(new MigrateDatabaseToLatestVersion<DataContext, Configuration>());
base.Configuration.ProxyCreationEnabled = false;
}
public DbSet<PortfolioRequestCollection> PortfolioRequestCollections { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Configurations.Add(new PortfolioRequestCollectionConfiguration());
base.OnModelCreating(modelBuilder);
}
}
Run Code Online (Sandbox Code Playgroud)
Eri*_*sch 18
如果您使用工作单元(UoW)的目的是为了测试性,那么您走错了路.工作单元对可测性无效.它的主要目的是为不同的数据源提供原子事务,为尚未提供它的数据层提供UoW功能,或者以更容易替换的方式包装现有的UoW ......通过使用通用存储库无效(无论如何,这将它与实体框架紧密结合).
我建议你彻底摆脱工作单元.实体框架已经是一个UoW.甚至微软也改变了主意,不再推荐使用EF的UoW.
因此,如果你摆脱了UoW,那么你可以使用存储库来包装你的EF查询.我不建议使用通用存储库,因为这会在您的代码中泄露您的数据层实现(您的UoW已经在做的事情),而是创建具体的repoTsitories(如果您愿意,可以在内部使用通用存储库,但是通用存储库不应泄漏到您的存储库之外).
这意味着您的服务层将获取所需的特定具体存储库.例如,IPortfolioRepository.然后你有一个PortfolioRepository继承自IPortfolioRepository 的类,它将你的EF DbContext作为一个参数,由你的Depndency Injection(DI)框架注入.如果将DI容器配置为以"PerRequest"为基础实例化EF上下文,则可以将同一实例传递给所有存储库.您可以Commit在存储库上调用一个方法SavesChanges,但它会保存对所有更改的更改,而不仅仅是对该存储库的更改.
就Testability而言,您有两种选择.您可以模拟具体的存储库,也可以使用EF6的内置模拟功能.
我自己也经历过那个地狱,这就是我所做的:
完全放弃 UoW。EF 的DBContext 基本上是一个 UoW。重新发明轮子没有意义。
每MSDN:
数据库上下文类
表示工作单元和存储库模式的组合,使您能够查询数据库并将更改组合在一起,然后将这些更改作为一个单元写回存储。
服务层 + 回购层似乎是一个不错的选择。然而,repos 总是一个有漏洞的抽象,尤其DbSet是当 DBContext相当于存储库时。
如果你问我 2 美分,我会说使用服务层 + EF,一个包装业务逻辑,另一个包装 UOW/Repository 模式。
或者,特别是对于 Windows 服务,我发现转向基于命令查询的方法效果更好。它不仅有助于可测试性,还有助于异步任务,即使在请求结束后我也不必担心保持 DBContext 处于活动状态(DBContext 现在与命令处理程序绑定并且只要异步命令保持活动状态就保持活动状态)活)。
现在,如果您最近最终消化了有关 UOW/Repository 模式的所有事实,那么肯定的是,即使只是阅读命令查询模式也会让您心痛。我一直在这条路上走,但相信我,至少值得花时间研究一下并尝试一下。
这些帖子可能会有所帮助:
如果你足够勇敢(在通过 CQRS 消化之后),那么看看MediatR,它实现了中介模式(它基本上用通知包装了命令查询)并允许通过 pub-sub 工作。发布-订阅模型非常适合 Windows 服务和服务层。
| 归档时间: |
|
| 查看次数: |
17544 次 |
| 最近记录: |