使用3层模式的Repository进行域建模

时间:2014-03-21 07:41:44

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

请注意,此代码是我通常编写代码的代码示例,但我刚刚删除了可以从我的问题中删除焦点的代码。我期待着倾听。

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

我一直在努力解决一些我很难搞清楚自己的建筑问题。

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

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

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

模型

DAL

BL

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

以下是我的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>();
        }

我在Console应用程序中的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项目中的方法时,我必须从我的控制台项目引用我的Model项目。

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

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

我真的很期待听到你对此的看法,这一直困扰着我。

提前致谢

2 个答案:

答案 0 :(得分:0)

现在我也在编写一些3层应用程序作为我的技能培训。我还创建了一个项目结构:BLL,DAL,Model,UI(MVC项目)和Tests图层。根据我的经验,我知道你的主应用程序(在我的情况下是带有MVC项目的UI层)应该只引用BLL和Model!您不应该添加对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;
    }
}

答案 1 :(得分:0)

要解耦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文件夹中的所有其他Create Dto,ListPersonDto,项目中Query fodler中的QueryPersonDto一起进入Create文件夹,添加额外的使用对类文件的引用,但没有任何内容。< / p>

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或服务层

添加到上面的内容

首先是创建人员方法。您的UI层将创建一个dto设置信息并调用下面的create方法。创建dto不需要包含用于并发的Id,时间戳(rowversion)等,因为在创建新对象时不需要任何信息,只需要可以由repo添加的对象的成员。在此示例中仅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;
        }

其次是Get of the Person实例。你传递它后面的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只包含名称。

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

因此,您的应用只需要查看BAL&amp; dto layer。

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是这种情况必须具备的,它可以让生活变得更轻松,并且减少了你的输入,有很多很好的例子可以使用自动播放器。

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