【问题标题】:Domain modelling with Repository in a 3-tier pattern以 3 层模式使用存储库进行域建模
【发布时间】:2014-04-28 11:27:58
【问题描述】:

请注意,此代码是我通常如何编写代码的代码示例,但我刚刚删除了会从我的问题中转移焦点的代码。我期待着聆听。

我可以理解我需要至少 10 次重复。在我发布图片之前,我的图片说明了我的问题...所以请点击此链接在 codereview.stackexchange.com 上查看我的原始问题 - https://codereview.stackexchange.com/questions/44237/domain-modelling-with-repository

我一直在努力解决一些架构问题,我自己也很难搞清楚。

我正在尝试使用域模型和存储库模式构建项目的基本结构。

当我想实现一些业务逻辑和不同类型的 UI(例如 WinForms 和 MVC)时,构建 POCO 类和存储库很容易我觉得我错过了一些东西,因为我觉得我的代码是紧密耦合的当我需要获取一个对象并显示它时,我总是必须参考 POCO 类。

我首先在 C#(vs2012) 中构建以下项目:

型号

DAL

BL

测试控制台

这是我的模型一个模型类的示例:

namespace Panda.Model
{
    public class Person : IEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }

        public Person()
        {

        }
        public Person(string name)
        {
            this.Name = name;
        }
    }
}

这是我的 BL 项目中 Persons 类的示例代码:

using Panda.DAL.Repositories;
using Panda.DAL.Contexts;
using Panda.Model;
namespace Panda.BL
{
    public class Logic
    {
        private readonly IRepository<Person> _personRep;
        public Logic()
        {
            _personRep = new Repository<Person>(new GenericContext());
        }

        public LinkedList<Person> ListOfPersons()
        {
            LinkedList<Person> persons = new LinkedList<Person>();
            persons.AddFirst(new Person("Nicklas"));
            persons.AddFirst(new Person("Martin"));
            persons.AddFirst( new Person("Kresten"));
            return persons;
        }
    }

我的 DAL 项目由采用 IEntity 类型的类的通用存储库组成:

public class Repository<T> : IRepository<T> where T : class, IEntity
    {
        /// <summary>
        /// The Generic Repository class that can use all Model classes when istantiating it.
        /// It holds all the generic methods for insert, select, delete and update.
        /// </summary>
        internal DbSet<T> DbSet;
        internal GenericContext Context;
        public Repository(GenericContext context)
        {
            this.Context = context;
            DbSet = context.Set<T>();
        }

我在控制台应用程序中的 program.cs 文件的代码如下所示:

using Panda.BL;

namespace Panda.TestConsole
{
    public class Program
    {
        static void Main(string[] args)
        {
            Logic lol = new Logic();

            foreach (var item in lol.ListOfPersons())
            {
                Console.WriteLine(item.Name);
            }
        }
    }
}

问题是我不知道如何将我的模型和 DAL 与我的 UI 项目(控制台等)进一步分离。每次我想获得 i.ex。当我想使用 BL 项目中的方法时,我当然必须从控制台项目中引用我的模型项目。

我对整个 DDD 和 3 层模式的理解是,当您想要添加新的 UI 项目(例如控制台、WebForms 或 MVC)时,您应该只能与 BL 交谈(参考)但是现在当我想在 BL 项目中使用方法时,我总是必须同时引用 Models 和 BL。

现在我觉得有很多依赖关系将事物紧密耦合..

我真的很期待听到你对这个让我困惑了一段时间的想法。

提前致谢

【问题讨论】:

    标签: c# entity-framework domain-driven-design 3-tier


    【解决方案1】:

    现在我也在编写一些 3 层应用程序来训练我的技能。我还像你一样创建了一个项目结构:BLL、DAL、模型、UI(一个 MVC 项目)和测试层。根据我的经验,我知道您的例如主应用程序(在我的例子中是带有 MVC 项目的 UI 层)应该只引用 BLL 和模型!您不应添加对 DAL 的引用。 BLL 应该使用 Model 和 DAL。最后,DAL 应该只引用模型。就是这样。

    顺便说一句,你应该避免这种情况:

    public class Logic {
        private readonly IRepository<Person> _personRep;
        public Logic()
        {
            _personRep = new Repository<Person>(new GenericContext());
        }
    }
    

    改为使用依赖注入:

    public class Logic {
        private readonly IRepository<Person> _personRep;
        public Logic(IRepository<Person> personRep)
        {
            _personRep = personRep;
        }
    }
    

    【讨论】:

    • 感谢您对我的代码的反馈。这是有道理的,但是我如何在 BL 中命名我的课程?我的模型(Model.Person BL.Person)中是否有一个 foreach 模型对象的类。这里的命名约定是什么?我只有一个类来处理所有模型对象吗?
    • 不客气。有关更多详细信息,您可以访问pluralsight 网站。他们的图书馆中有这个架构模块,并且有一个关于 n 层应用程序的特殊课程。真的很有帮助!我会告诉你我是如何在我的应用程序中做事的。第 1 - 我有一个 Model.Category(域) 第 2 - 我有一个 Repository.CategoryRepository (DAL) 第 3 - 我有一个 BusinessLogic.CategoryLogic (BLL) 模型很明显。在 CategoryRepository 中,我正在访问数据库并检索映射的对象。然后在我的 CategoryLogic 中我做了一些我需要稍后在我的 MVC 应用程序中使用的逻辑。
    • 我发现了一些可能对你有用的东西:codeproject.com/Articles/70061/… - 我也要去读它:)
    • 该链接是对 Rep 和 N 层模型的最佳解释。
    • 很高兴它对您有所帮助:)
    【解决方案2】:

    为了解耦 UI,我使用了 DataTransferObject 模式,以便我的服务层或 BAL 是唯一的层,然后使用域和存储库。

    DTO 只是 poco,仅包含您正在传输的对象的信息,仅此而已。

    为了将数据从域对象映射到 dto,然后我使用 AutoMapper(上帝发送)

    您的 UI 层无论是什么,都只会处理与当时正在完成的工作相关的 dto。无论是来自 Web 服务层还是服务库本身。

    域层

    更改了上面的示例以显示它如何更好地工作,并添加了并发字段以显示如何使用 dto 以及何时需要处理它。

    public class Person : IEntity
    {
        [key]
        public int Id { get; set; }
    
        [Required]
        [StringLength(20)]
        public string FirstName { get; set; }
    
        [StringLength(50)]
        public string Surname { get; set; }
    
        [Timestamp]
        public byte[] RowVersion { get; set; }
    
        public Person() { }
    
        public Person(string firstName, string surname)
        {
            FirstName = firstName;
            Surname = surname;
        }
    }
    

    DTO 层

    为了便于查看,我通常在一个命名空间中将每个 dto 类分开到自己的文件中,并创建文件夹来存储相关的 dto。即。 CreatePersonDto 将与所有其他 Create Dto 一起进入 Create 文件夹,List 文件夹中的 ListPersonDto,项目内 Query 文件夹中的 QueryPersonDto,使用对您的类文件的引用添加额外的内容,但这没什么。

    namespace Panda.DataTransferObjects
    {
        public class PersonDto
        {
            public int? Id { get; set; }
    
            public string FirstName { get; set; }
    
            public string Surname { get; set; }
    
            public byte[] RowVersion { get; set; }
        }
    
        public class CreatePersonDto
        {
            public string FirstName { get; set; }
    
            public string Surname { get; set; }
        }
    
    
        public class EditPersonDto
        {
            public int Id { get; set; }
    
            public string FirstName { get; set; }
    
            public string Surname { get; set; }
    
            public byte[] RowVersion { get; set; }
    
    
            // user context info, i would usually use a separate ServiceContextDto to do
            // this, if you need to store whom changed what and when, and how etc 
            // ie. log other information of whats going on and by whom.
            // Needed in Create and Edit DTO's only
            public string ChangedBy { get; set; }
        }
    
        public class ListPersonDto
        {
            public string Name { get; set; }
        }
    
        public class QueryPersonDto
        {
            public int? Id { get; set; }
    
            public string FirstName { get; set; }
    
            public string Surname { get; set; }
        }
    
    }
    

    BAL 或服务层

    添加到上面的内容中

    首先是一个 Create person 方法。您的 UI 层将创建一个 d 来设置信息并调用下面的 create 方法。创建 dto 不需要包含 ID、用于并发的时间戳(行版本)等,因为在创建新对象时不需要这些信息,您所需要的只是可以由存储库添加的对象成员。在此示例中,仅限 FirstName 和 Surname。用户上下文数据等其他数据也可以在这些对象中传递,但您不需要其他任何内容。

            public int CreatePerson(CreatePersonDto dto)
            {
                //checks to ensure dto is valid
    
                var instance = new Person(dto.FirstName, dto.Surname);                                
    
                // do your stuff to persist your instance of person. ie. save it
    
                return instance.Id;
            }
    

    其次是 Person 实例的 Get。您将您之后的人员实例的 id 传递给它,然后它会检索它并返回一个 PersonDto。首先,您需要通过存储库从持久层获取您的 Person 对象,然后您需要将该对象转换为 Dto 以返回给客户端。对于我使用 AutoMapper 的映射,它对这种类型的模式有很大帮助,您可以从一个对象到另一个对象进行大量映射,这就是它的用途。

            public PersonDto Get(int id) {
                 Person instance = // repo stuff to get person from store/db
    
                 //Manual way to map data from one object to the other.
                 var personDto = new PersonDto();
                 personDto.Id = instance.Id;
                 personDto.FirstName = instance.firstName;
                 personDto.Surname = instance.Surname;
                 personDto.RowVersion = instance.RowVersion;
    
                 return personDto;
    
    
                 // As mentioned I use AutoMapper for this, so the above becomes a 1 liner.
                 // **Beware** there is some configuration for this to work in this case you
                 // would have the following in a separate automapper config class.
                 // AutoMapper.CreateMap<Person, PersonDto>();
                 // Using AutoMapper all the above 6 lines done for you in this 1.
                 return Mapper.Map<Person, PersonDto>(instance);
            }
    

    ListPersonDto

    如前所述,为此使用 AutoMapper,查询中的对象转换之类的事情变得轻松。

            public IEnumerable<ListPersonDto> ListOfPersons(QueryPersonDto dto = null)
            {
                // check dto and setup and querying needed
                // i wont go into that
    
                // Using link object mapping from the Person to ListPersonDto is even easier
                var listOfPersons = _personRep.Where(p => p.Surname == dto.Surname).Select(Mapper.Map<Person, ListPersonDto>).ToList();
    
                return listOfPersons;
            }
    

    考虑到 ListPersonDto 仅包含名称,为了完整起见,上述自动映射器签名看起来如下所示。

    AutoMapper.Mapper.CreateMap<Person, ListPersonDto>()
        .ForMember(dest => dest.Name, opt => opt.ResolveUsing(src => { return string.Format("{0} {1}", src.FirstName, src.Surname); } ))
    

    因此,您的应用将只需要查看 BAL 和 dto 层。

    public class Program
    {
        static void Main(string[] args)
        {
            Logic lol = new Logic();
    
            CreatePersonDto dto = new CreatePersonDto { FirstName = "Joe", Surname = "Bloggs" };
            var newPersonId = lol.Create(dto);
    
            foreach (var item in lol.ListOfPersons())
            {
                Console.WriteLine(item.Name);
            }
    
            //or to narrow down list of people
            QueryPersonDto queryDto = new QueryPersonDto { Surname = "Bloggs" }
    
            foreach (var item in lol.ListOfPersons(queryDto))
            {
                Console.WriteLine(item.Name);
            }
        }
    }
    

    它增加了额外的工作,但不幸的是,没有简单的方法可以做到这一点,并且使用像上面这样的模式将有助于分离事物并更容易追踪问题。您的 dto 应该只拥有操作所必需的东西,因此它可以更容易地查看哪些东西被遗漏或不包括在内。 AutoMapper 是这种情况下的必备工具,它让生活变得更轻松,减少了打字,周围有很多使用 automapper 的好例子。

    希望这有助于更接近解决方案。

    【讨论】:

    • 嘿,马克,感谢您提供进一步解耦 UI 的广泛示例。我也考虑过使用 DTO POCO 类,但最终得到了令人困惑的代码,但您的解决方案看起来很棒,我会尝试一下。再次感谢您的精彩解释!
    • 没问题,只要记住为了 1 的目的保持 dto 的简单,并且它保持简单。我上面没有提到的一件事是更新。如果您显示数据(例如详细信息视图),则使用 PersonDto 填充您的表单或显示,然后在将其发回时使用 EditPersonDto。即使 2 个 dto 类似,您也可以将每个 dto 保留在手头,这有助于简化了解应该使用什么以及何时使用的过程。不幸的是,没有明确的指南来展示适当的多层实现,而且它并不简单,有时会让人感到困惑。祝你好运
    猜你喜欢
    • 2012-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-21
    • 2017-09-19
    • 2012-06-02
    • 1970-01-01
    相关资源
    最近更新 更多