将我的 DAL 代码重构为领域驱动设计或更现代的设计 (C# 3.5)?

Posted

技术标签:

【中文标题】将我的 DAL 代码重构为领域驱动设计或更现代的设计 (C# 3.5)?【英文标题】:Re-factor my DAL code to a Domain Driven Design or more modern design (C# 3.5)? 【发布时间】:2014-07-11 21:48:43 【问题描述】:

开发仅限于 Visual Studio 2010(客户认可的软件)。我们需要通过存储过程来访问数据。我想避免过于复杂的日程安排。我看到的大多数设计都涉及 EF 和 LINQ,不知道如何为 procs 设计?

我想创建一个单独的代码库项目(使用 Web UI):

Application.Domain
      -  Interact get/put stored procedures, entities

Application.Web
      - containing Web UI (JQuery, AJAX), WCF Service

谁能给我关于如何处理 Application.Domain 的示例代码?

示例,我已阅读:

http://www.developer.com/net/dependency-injection-best-practices-in-an-n-tier-modular-application.html

http://www.kenneth-truyers.net/2013/05/12/the-n-layer-myth-and-basic-dependency-injection/

DAL\AppDAL.cs:

public static IEnumerable<TasCriteria> GetTasCriterias()
    
        using (var conn = new SqlConnection(_connectionString))
        
            var com = new SqlCommand();
            com.Connection = conn;
            com.CommandType = CommandType.StoredProcedure;

            com.CommandText = "IVOOARINVENTORY_GET_TASCRITERIA";
            var adapt = new SqlDataAdapter();
            adapt.SelectCommand = com;
            var dataset = new DataSet();
            adapt.Fill(dataset);

            var types = (from c in dataset.Tables[0].AsEnumerable()
                         select new TasCriteria()
                         
                              TasCriteriaId = Convert.ToInt32(c["TasCriteriaId"]),
                              TasCriteriaDesc= c["CriteriaDesc"].ToString()
                         ).ToList<TasCriteria>();

            return types;
        

    

模型\TasCriteria.cs:

public class TasCriteria
    
        public int TasCriteriaId  get; set; 
        public string TasCriteriaDesc  get; set; 
    

服务\Service.svc:

 [OperationContract]
        [WebInvoke(ResponseFormat = WebMessageFormat.Json,
            BodyStyle = WebMessageBodyStyle.WrappedRequest, Method = "GET")]
        public List<TasCriteria> GetTasCriteriaLookup()
        
            var tc = InventoryDAL.GetTasCriterias();
            return tc.ToList();
        

【问题讨论】:

为什么一定要使用存储过程?注意:您可以在使用存储过程时使用 EF(和 L2S)。如果这样做,最好将它们限制为简单的 CRUD 操作,这样您的业务逻辑就不会泄漏到 DB 层。但我想知道为什么你必须首先使用存储过程。 是的,EF 可以使用 procs。我们有一个积极的时间表,并有可用的过程。我们觉得使用它们而不是重写 LINQ 会更快。应用程序的很大一部分是报告,所有逻辑都在 procs 中......重写它们会很耗时。 你说你不确定如何为存储过程设计这个。你的设计不会改变。看起来你的层布置得很好。具体来说,你在哪里卡住了?如果你使用 EF,你应该能够将你的 sproc 拖到设计器上并开始使用它。 我想我觉得我的设计太简单了。为了了解您的观点,我认为引擎盖下的 EF 就是 ADO.NET。使用 EF 进行额外的抽象层是否会导致 procs 的开销(可以认为是 db 之上的层抽象)? 如果我拥有所有 procs,拥有 EF 的优势在哪里? 【参考方案1】:

如果你:

时间紧迫 通过 sprocs/views 已将大部分业务逻辑放在 DB 端 之前没有与 EF 合作过

我建议你看看Microsoft Enterprise Library,尤其是数据应用程序块。它将简化您的 所有 DAL 功能(不使用任何 ORM 框架),并在 Unity 的帮助下遵循依赖倒置原则,Unity 是来自 Microsoft 的依赖注入容器。

一些有用的数据应用程序块概念:

输出映射器

输出映射器获取从数据库返回的结果集(在 行和列的形式)并将数据转换为一个序列 对象。

// Create and execute a sproc accessor that uses default parameter and output mappings 
var results = db.ExecuteSprocAccessor<Customer>("CustomerList", 2009, "WA");

阅读整个Retrieving Data as Objects 主题。

参数映射器

参数映射器获取您要传递给 查询并将每一个转换为一个 DbParameter 对象。

// Use a custom parameter mapper and the default output mappings
IParameterMapper paramMapper = new YourCustomParameterMapper();
var results = db.ExecuteSprocAccessor<Customer>("Customer List", paramMapper, yourCustomParamsArray);

对于实体生成,我会尝试使用这个tool。它从存储过程返回的结果集中构建一个 POCO 类。我还没有尝试过这个工具,也许有更好的选择,但它可以让你开始,所以你不必手动做。

如果您使用的是 .NET 3.5,那么您必须使用 Enterprise Library 5.0。

我希望这会引导你朝着正确的方向前进。

【讨论】:

谢谢你的建议,我差点忘了企业库。 一个请求,对 paramMapper 和 CustomParamsArray 有点困惑......你能给我举个例子吗?例如,我的过程“Tracking_Get_Requests”中有一个参数“@requestId int”,它是如何工作的? var results = db.ExecuteSprocAccessor("Tracking_Get_Requests", 5);这将返回 requestId 等于 5 的跟踪请求。对于结果对象,此处使用默认映射,它将 TrackingRequest 对象的每个属性与其从存储过程返回的按名称相关的列映射。对于 sproc 参数,默认映射意味着它将按位置映射参数,因此值 5 将自动映射到 @requestId。【参考方案2】:

首先,确保您使用依赖注入(例如 ninject 或 unity(或许多其他免费提供的))抽象 DAL。很可能让您的 DAL 松散耦合,因此如果您稍后决定 EF(或任何其他 ORM)不是最好的课程,那么更改它不会花费血...

您不希望有一个带有静态方法的 AppDAL 类来调用 SP。如果只是为了单元测试,至少添加一个接口并使用注入。

无论您将使用 EF 还是 Nhibernate 或任何其他 ORM,该决定都应该封装在您的 DAL 中,而不是泄漏到其他层中。域层应该使用来自 DAL 的存储库类的接口(并且那些包含对所选 ORM 或数据访问类的引用)。

这些存储库将调用存储过程并返回您的模型类 (POCO)。

在我最近的一个项目中,我们有这个接口来提供基本的 CRUD 操作:

public interface IRepository<T> where T : DomainEntity

    T Get(Int64 id);
    void SaveOrUpdate(T entity);
    void Delete(T entity);
    IQueryable<T> Find();

DomainEntity 是一个非常简单的类,所有模型类都继承。 在我们需要使用存储过程的极少数情况下,我会创建一个额外的接口来提供 GetXXXEntity 方法(1 个或多个),它会执行对 SP 的实际调用。

所以,当我需要使用它的 ID 从数据库中获取实体时,它看起来像:

_diWrapper.GetRepository<Person>().Get(id);
_diWrapper.GetRepository<Order>().Get(id);

_diWrapper 是我的依赖注入容器的包装器(在本例中为 ninject)。我使用了一个包装器,因此如果需要,我可以轻松地将 ninject 替换为其他东西。

在我需要使用 linq 的常见情况下:

_diWrapper.GetRepository<Person>().Find().Where(q => q.Name == "Jack").ToList();

重要的是我可以相当快地用其他任何东西替换 Nhibernate。

我强烈建议您查看 Fluent NHibernate,因为它提供了一个不需要太多编码的简单解决方案。

编辑:这是实现 IRepository 接口的存储库类的示例:

public class NhibernateRepository<T> : IRepository<T> where T : DomainEntity, new()

    private ISession _session;

    public NhibernateRepository()
    
        _session = BaseNHibernateHelper<NHibernateHelper>.GetCurrentSession();
    

    public T Get(Int64 id)
    
        return _session.Get<T>(id);
    

    public void SaveOrUpdate(T entity)
    
        _session.SaveOrUpdate(entity);
    

    public void Delete(T entity)
    
        _session.Delete(entity);
    

    public IQueryable<T> Find() 
    
        return _session.Query<T>();
    

请注意,在构造函数中,我使用了另一个我创建的用于包装会话工厂的休眠助手。这就是我依赖 nhibernate 的地方。

如果我想用另一个 ORM 替换 NH,我只需要修改存储库类(以及底层的支持类),你可以看到 NH 不会泄漏到存储库类之外,并且没有人使用它了解 NH 的用法。

【讨论】:

我真的很喜欢你的方法......如果我以后想更改 ORM,它的灵活代码......快速请求,你能提供一个实现 IRepository 的示例吗?例如:PersonRepository、OrderRepository? 抽象 ORM 会阻止您使用 100% 的强大功能。有很多来自经验丰富的开发人员的资源/文章说,在您的应用程序和 ORM 之间添加额外的抽象层是一个坏主意Say No to the Repository Pattern in your DAL 和 NOT using repository pattern, use the ORM as is (EF) 抱歉询问细节: T : DomainEntity,DomainEntity 来自哪里?得到缺少的指令错误? DomainEntity 是您的基本实体抽象类/接口。通常它只有一个属性,如“int ID get;private set”,所有实体都必须继承自它。 正是@JernejGorički 所写的,只是我的 Id 属性也有一个公共集,因为 NH 设置了它。除此之外没有什么...【参考方案3】:

我注意到大多数人都在谈论实施/技术,但没有人提到领域驱动设计的应用或主旨。好吧,DDD 不一定是你可以通过添加 dapper/ef/enterprise 库块来实现的。这些可以提供帮助,SOLID 和 cqs 命令/查询分离之类的东西也可以提供帮助,但这些仅仅是促成因素,还有更多的考虑和问题需要提出。看看 infoq 上的“领域驱动设计快速”以获得更多想法。

【讨论】:

以上是关于将我的 DAL 代码重构为领域驱动设计或更现代的设计 (C# 3.5)?的主要内容,如果未能解决你的问题,请参考以下文章

你或许以为你不需要领域驱动设计

DDD:如何领用领域驱动设计来避免写流水账代码

领域驱动设计--战术模式简介

在重构业务系统时,应用领域驱动设计

领域驱动设计在马蜂窝优惠中心重构中的实践

领域驱动设计在马蜂窝优惠中心重构中的实践