使用三层模式的Repository进行领域建模

本文关键字:Repository 领域建模 模式 三层 | 更新日期: 2023-09-27 17:51:14

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

我可以理解,我需要至少10代表之前,我可以张贴图像和我的图像说明我的问题…所以请点击这个链接查看我的原始问题codereview.stackexchange.com - https://codereview.stackexchange.com/questions/44237/domain-modelling-with-repository

我一直在努力解决一些架构上的问题,我自己很难解决这些问题。

我试图用域模型和存储库模式构建一个项目的基本结构。

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

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

模型木豆

提单

TestConsole

下面是一个模型类的例子:

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

下面是我的person类在我的BL项目中的示例代码:

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类型的Class:

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

问题是,我不知道如何从我的UI项目(控制台等)进一步解耦我的模型和DAL。每次我想要我的ex。当我想要使用来自BL项目的方法时,我必须从控制台项目引用我的Model项目。

我对整个DDD和三层模式的理解是,当你想要添加一个新的UI项目时,你应该只能够与BL交谈(引用)。但是现在,当我想在BL项目中使用方法时,我总是要参考模型和BL。

现在我觉得我有很多依赖关系,这些依赖关系把事情耦合得太紧了。

我真的很期待听到你对这个困扰了我一段时间的问题的看法。

提前感谢

使用三层模式的Repository进行领域建模

现在我也在写一些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;
    }
}

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

DTO只是poco,它只包含要传输的对象的信息,仅此而已。

将数据从域对象映射到dto I,然后使用AutoMapper (a god send)

你的UI层,无论它可能是什么,只会处理在那个时间点上正在完成的工作相关的数据。无论是来自Web服务层还是服务库本身。

领域层

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

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。ie。CreatePersonDto将与所有其他Create Dto一起进入Create文件夹,ListPersonDto在列表文件夹中,QueryPersonDto在项目内的Query文件夹中,添加额外的使用引用到您的类文件,但这没什么。

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层将创建一个dd来设置信息并调用下面的create方法。创建dto不需要包含Id,时间戳(rowversion)并发等,因为创建新对象时不需要这些信息,您需要的只是可以通过repo添加的对象的成员。在本例中,只有FirstName和姓氏。其他数据,如用户上下文数据等,也可以在这些对象中传递,但你不需要其他任何东西。

        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。你把person实例的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只包含name,上面的自动签名将看起来像下面这样。

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的好例子。