c# - 将存储过程用于存储库模式和服务类
问题描述
背景
在我的 Web 应用程序中,我创建了存储过程,然后创建了一个 edmx 文件以使用存储过程来处理所有数据库交互。
但是我开始怀疑我这样做是否正确,因为正如您将在下面的示例中看到的那样,
即使被调用的方法不需要数据库工作,我也会在
Context
每次调用时实例化两个实例Controller
我
Context
在每个存储库中实例化一个实例,所以当一个请求需要从存储库 A 和 B 获取数据时,我有两个Context
.
存储库 A
public class RepositoryA
{
private readonly Context _context;
public RepositoryA()
{
_context = new Context();
}
public List<CLASS> GetA(int id)
{
return _context.GetByID(id);
}
}
存储库 B
public class RepositoryB
{
private readonly Context _context;
public RepositoryB()
{
_context = new Context();
}
public List<CLASS> GetB(int id)
{
return _context.GetByID(id);
}
}
控制器
public class Controller
{
private readonly IRepositoryA _reposA;
private readonly IRepositoryB _reposB;
public Controller() : this(new RepositoryA(), new RepositoryB())
{}
public Controller(IRepositoryA a, IRepositoryB b)
{
_respoA = a;
_reposB = b;
}
public ActionResult METHOD()
{
//do something with both RepositoryA and RepositoryB
var dataFromA = _reposA.GetA(ID);
var dataFromB = _reposB.GetB(ID);
return View(someData);
}
}
现在的问题是:我不确定这是否应该是正常的实现,所以我一直试图弄清楚如何以更有效和可测试的方式进行设置,我尝试了类似的方法。
我相信这个设计解决了我的一些顾虑:
每次调用 Controller 时都会调用服务,但不会每次都实例化 Context(每个请求都会实例化 Context)。
当服务同时需要存储库 A 和 B 时,它使用相同的上下文
但是,通过Service
设置方式,我无法Service
使用测试数据进行单元测试,因为我无法使用我的模拟存储库。
public class Controller
{
private Service _service;
public Controller()
{
_service = new Service();
}
public ActionResult METHOD()
{
_service.DoSomethingWithRepoAandB();
return View();
}
}
public class Service
{
public void DoSomethingWithRepoAandB()
{
using (var _context = new Context())
{
RepositoryA a = new RepositoryA(_context);
RepositoryB b = new RepositoryB(_context);
something = a.DoSomethingWithA();
otherThing = b.DoOtherThingWithB();
}
}
}
所以,我想我应该这样设置Service
。
使用这种设计,Context
每次Controller
被调用时都会被实例化(除非我Service
在一个Controller
方法中实例化),但我可以通过传递模拟存储库来进行单元测试。
public class Service
{
private readonly Context _context;
private IRepositoryA _a;
private IRepositoryB _b;
public Service()
{
_context = new Context();
_a = new RepositoryA(_context);
_b = new RepositoryB(_context);
}
// Pass Mock Repositories in unit tests
public Service(RepositoryA a, RepositoryB b)
{
_a = a;
_b = b;
}
public void DoSomethingWithRepoAandB()
{
something = _a.DoSomethingWithA();
otherThing =_b.DoOtherThingWithB();
}
}
我这样做是完全错误的还是在正常的轨道上?我会很感激任何建议。
解决方案
您的服务不应该有任何关于您的上下文的信息,这些信息应该只能由存储库访问,遵循 SOLID 原则。
您的图层的访问权限应如下所示:
控制器 -> 服务 -> 存储库 -> 上下文
在您的 IRepository 接口上,您将拥有要调用的方法,您可以手动实例化它们,但要正确实现它,您必须设置依赖注入。
此外,您的 Repositories 构造函数不会接收任何上下文作为参数,因此您不能执行以下操作:
RepositoryA a = new RepositoryA(_context);
另一方面,您的控制器也不应该访问存储库,因为它会破坏您的架构
public ActionResult METHOD()
{
//do something with both RepositoryA and RepositoryB
var dataFromA = _reposA.GetA(ID);
var dataFromB = _reposB.GetB(ID);
return View(someData);
}
这是正确的代码,以供进一步理解:
public class RepositoryA : IRepositoryA
{
private readonly Context _context;
public RepositoryA(Context context)
{
_context = context;
}
public List<CLASS> GetA(int id)
{
return _context.GetByID(id);
}
}
public interface IRepositoryA
{
List<CLASS> GetA(int id);
}
public class RepositoryB : IRepositoryB
{
private readonly Context _context;
public RepositoryB(Context context)
{
_context = context;
}
public List<CLASS> GetB(int id)
{
return _context.GetByID(id);
}
}
public interface IRepositoryB
{
List<CLASS> GetB(int id);
}
public class Controller
{
private IService _service;
public Controller(IService service)
{
_service = service;
}
public ActionResult METHOD(int id)
{
//do something with both RepositoryA and RepositoryB THROUGH the service. The service needs to hold the business rules, and repositories should only care about querying data and handling contexts.
var data = _service.DoSomethingWithRepoAandB(id)
return View(data);
}
}
public class Service : IService
{
private IRepositoryA _a;
private IRepositoryB _b;
// Pass Mock Repositories in unit tests -> PS: You can't have 2 constructors if you're using dependency injection.
public Service(RepositoryA a, RepositoryB b)
{
_a = a;
_b = b;
}
public void DoSomethingWithRepoAandB(int id)
{
var something = _a.GetA(id);
var otherThing = _b.GetB(id);
}
}
public interface IService
{
void DoSomethingWithRepoAandB(int id);
}
public class Bootstrapper
{
//this class should be in a separated assembly, responsible for handling the dependency injection. Using Simple Injection syntax just as an example
public static void RegisterServices(Container container) //IoC Container
{
container.Register<IService, Service>(Lifestyle.Scoped);
container.Register<IRepositoryA, RepositoryA>(Lifestyle.Scoped);
container.Register<IRepositoryB, RepositoryB>(Lifestyle.Scoped);
container.Register<Context>(() => {
var options = // Configure your ContextOptions here
return new Context(options);
});
}
}
public class Startup
{
//This is your startup configuration if you're using WebApi. If you're on MVC, you can do this on your Global.asax
public void Configuration()
{
var container = new Container();
container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();
BootStrapper.RegisterServices(container);
}
}
推荐阅读
- python - 如何更改以 loc 为条件的 pandas 数据框中的最后一个值?
- python - 通过多个两列创建新的numpy nd-array
- asp.net-core - Azure Pipelines:迁移到 .NET Core 3.1 后,Dotnet 发布失败,退出代码为 1
- docker - 升级到 ChromeDriver v80 和 Chrome v80 后,Chrome 无法在使用 Selenium 的 docker 容器中启动
- image - 如何有效地将图像作为 pubspec.yaml 中的资产处理?
- c# - Asp.Net - 使用 IHostedService 的计划任务
- flutter - Flutter 更改 Drawer UserAccountsDrawerHeader 布局
- php - Dockerizing Drupal 8 on AWS ECS 错误:Drupal 目标组中的任务失败 ELB 健康检查
- spring - 运行测试时跳过 JPA 生命周期方法
- jquery - JQuery async .each 返回空承诺