使用三层模式的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。
现在我觉得我有很多依赖关系,这些依赖关系把事情耦合得太紧了。
我真的很期待听到你对这个困扰了我一段时间的问题的看法。
提前感谢
现在我也在写一些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的好例子。