C# 以3层模式使用存储库进行域建模

C# 以3层模式使用存储库进行域建模,c#,entity-framework,domain-driven-design,3-tier,C#,Entity Framework,Domain Driven Design,3 Tier,请注意,这段代码是我通常如何编写代码的代码示例,但我刚刚删除了将焦点从我的问题中移除的代码。我期待着倾听 我可以理解我至少需要10次。在我发布图片之前,我的图片说明了我的问题。。。因此,请点击此链接查看我在codereview.stackexchange.com上的原始问题- 我一直在努力解决一些架构问题,我自己也很难弄清楚 我试图用域模型和存储库模式构建项目的基本结构 当我想要实现一些业务逻辑和不同类型的UI(例如WinForms和MVC)时,构建POCO类和存储库非常容易。我觉得我错过了一些

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

我可以理解我至少需要10次。在我发布图片之前,我的图片说明了我的问题。。。因此,请点击此链接查看我在codereview.stackexchange.com上的原始问题-

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

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

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

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

模型

达尔

基本法

测试控制台

下面是我的模型a模型类的一个示例:

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;
        }
    }
问题是每次我想要得到I.ex时,我都不知道如何将我的模型和DAL与UI项目(控制台等)进一步解耦。当我想要使用BL项目中的方法时,我当然必须从控制台项目引用我的模型项目

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

现在我觉得我有很多依赖性,这些依赖性把事情紧密地联系在一起

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


提前感谢

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

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

public class Logic {
    private readonly IRepository<Person> _personRep;
    public Logic()
    {
        _personRep = new Repository<Person>(new GenericContext());
    }
}
公共类逻辑{
私人只读IRepository\u personRep;
公共逻辑()
{
_personRep=new Repository(new GenericContext());
}
}
而是使用依赖项注入:

public class Logic {
    private readonly IRepository<Person> _personRep;
    public Logic(IRepository<Person> personRep)
    {
        _personRep = personRep;
    }
}
公共类逻辑{
私人只读IRepository\u personRep;
公共逻辑(IRepository personRep)
{
_personRep=personRep;
}
}

要分离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文件夹,ListPersonDto在List文件夹中,Query fodler中的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或服务层

添加到上面的内容中

首先是一个createperson方法。用户界面层将创建一个dto来设置信息,并调用下面的创建方法。Create dto不需要包含用于并发性的Id、时间戳(rowversion)等,因为在创建新对象时不需要这些信息—您所需要的只是可由repo添加的对象的成员。在本例中,仅使用名字和姓氏。其他数据(如用户上下文数据等)也可以在这些对象中传递,但您不需要其他任何东西

        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实例的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);
        }
它增加了额外的工作,但不幸的是,没有简单的方法可以做到这一点,而使用上面的模式将有助于将事情分开,并使跟踪问题变得更容易。您的dto应该只具有操作所需的内容,这样可以更容易地查看哪些内容被遗漏或未包含。AutoMapper是这个场景中的必备工具,它使生活变得更加轻松,并减少了您的输入,有很多使用AutoMapper的好例子


希望这有助于找到解决方案。

感谢您对我的代码的反馈。这是有道理的,但是我如何在BL中命名我的类呢?我的模型中是否有foreach模型对象的类(Model.Person BL.Person)。这里的命名约定是什么?我是否只有一个类负责所有模型对象
        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;
        }
        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);
        }
        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;
        }
AutoMapper.Mapper.CreateMap<Person, ListPersonDto>()
    .ForMember(dest => dest.Name, opt => opt.ResolveUsing(src => { return string.Format("{0} {1}", src.FirstName, src.Surname); } ))
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);
        }
    }
}