C# 将存储过程用于存储库模式和服务类

C# 将存储过程用于存储库模式和服务类,c#,asp.net-mvc,entity-framework-6,C#,Asp.net Mvc,Entity Framework 6,背景 在我的web应用程序中,我创建存储过程,然后创建一个edmx文件,以使用存储过程处理所有数据库交互 但我开始怀疑我是否做对了,因为正如你在下面的例子中看到的 每次调用控制器时,我都会实例化上下文的两个实例,即使被调用的方法不需要数据库工作 我正在每个存储库中实例化上下文的一个实例,因此当请求需要从存储库a和B获取数据时,我有两个上下文的实例 存储库A public class RepositoryA { private readonly Context _conte

背景

在我的web应用程序中,我创建存储过程,然后创建一个edmx文件,以使用存储过程处理所有数据库交互

但我开始怀疑我是否做对了,因为正如你在下面的例子中看到的

  • 每次调用
    控制器
    时,我都会实例化
    上下文的两个实例,即使被调用的方法不需要数据库工作

  • 我正在每个存储库中实例化
    上下文的一个实例,因此当请求需要从存储库a和B获取数据时,我有两个
    上下文的实例

  • 存储库A

    public class RepositoryA
    {
                private readonly Context _context;
    
                public RepositoryA()
                {
                    _context = new Context();
                }
    
                public List<CLASS> GetA(int id)
                {
                    return _context.GetByID(id);
                }
    }
    
    现在的问题是:我不确定这是否应该是正常的实现,所以我一直在试图找出如何以更有效和可测试的方式设置它,我尝试了类似的方法

    我相信这种设计解决了我的一些顾虑:

  • 每次调用控制器时都会调用服务,但上下文不会每次都实例化(每个请求都实例化上下文)

  • 当服务同时需要存储库a和B时,它使用相同的上下文

  • 但是,由于
    服务
    的设置方式,我无法使用测试数据对
    服务
    进行单元测试,因为我无法使用模拟存储库

    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();
                     }                    
                }
    }
    
    所以,我想我应该像这样设置
    服务

    通过这种设计,
    上下文
    将在每次调用
    控制器
    时被实例化(除非我在
    控制器
    方法中实例化
    服务
    ),但我可以通过传递模拟存储库来进行单元测试

    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();  
        }
    }
    

    我这样做是完全错误的还是正确的?我非常感谢您的建议。

    您的服务不应该包含任何关于您的上下文的信息,这些信息应该只能由存储库根据可靠的原则访问

    层的访问权限应如下所示:

    控制器->服务->存储库->上下文

    在IRepository接口上,您将拥有要调用的方法,您可以手动实例化它们,但要正确实现它,您必须设置依赖项注入

    此外,您的存储库构造函数不会接收任何上下文作为参数,因此您不能执行以下操作:

    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);
        }
    }
    
    public class RepositoryA:IRepositoryA
    {
    私有只读上下文\u上下文;
    公共资源(上下文)
    {
    _上下文=上下文;
    }
    公共列表GetA(int-id)
    {
    return\u context.GetByID(id);
    }
    }
    公共接口Irepositoria
    {
    列表GetA(int-id);
    }
    公共类RepositoryB:IRepositoryB
    {
    私有只读上下文\u上下文;
    公共报告B(上下文)
    {
    _上下文=上下文;
    }
    公共列表GetB(内部id)
    {
    return\u context.GetByID(id);
    }
    }
    公共接口IRepositoryB
    {
    列表GetB(int-id);
    }
    公共类控制器
    {
    私人电视服务;
    公共控制员(互联网服务)
    {
    _服务=服务;
    }
    公共操作结果方法(int-id)
    {
    //通过服务对RepositoryA和RepositoryB进行处理。服务需要保存业务规则,存储库应该只关心查询数据和处理上下文。
    var data=_service.DoSomethingWithRepoAandB(id)
    返回视图(数据);
    }
    }
    公共课服务:IService
    {
    私人电子邮箱;
    私人电子邮箱;
    //在单元测试->PS中传递模拟存储库:如果使用依赖项注入,则不能有2个构造函数。
    公共服务(安置a、安置b)
    {
    _a=a;
    _b=b;
    }
    公共无效DoSomethingWithRepoAandB(内部id)
    {
    var something=_a.GetA(id);
    var otherThing=_b.GetB(id);
    }
    }
    公共接口设备
    {
    void DoSomethingWithRepoAandB(内部id);
    }
    公共类引导程序
    {
    //这个类应该在一个单独的程序集中,负责处理依赖项注入
    公共静态void RegisterServices(容器容器)//IoC容器
    {
    容器。寄存器(生活方式。范围);
    容器。寄存器(生活方式。范围);
    容器。寄存器(生活方式。范围);
    容器。寄存器(()=>{
    var options=//在此处配置您的上下文选项
    返回新上下文(选项);
    });
    }
    }
    公营创业
    {
    //如果您使用的是WebApi,这是您的启动配置。如果您使用的是MVC,则可以在Global.asax上执行此操作
    公共无效配置()
    {
    var container=新容器();
    container.Options.DefaultScopedLifestyle=新的AsyncScopedLifestyle();
    BootStrapper.RegisterServices(容器);
    }
    }
    
    您的存储库是否随时都在处理上下文?在某种程度上,上下文可能会过时,这是否可以接受?@TiesonT。现在还没有明确地处理它。您的存储库为您的体系结构提供了什么功能?您可以删掉它,直接在服务类中使用上下文。这将避免跨多个上下文实例共享实体的问题,并简化代码。将上下文注入服务构造函数,并将服务注入控制器。如果您关心上下文实例化,可以拆分控制器以移出无db操作。你可以这样做,这样服务就可以进行测试。你检查过类似的东西吗@Jasen,存储库可以保存数据、获取数据或按id列出列表等。我考虑为所有模型/虚拟仪器建立一个存储库
    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);
        }
    }