C# 以3层模式使用存储库进行域建模
请注意,这段代码是我通常如何编写代码的代码示例,但我刚刚删除了将焦点从我的问题中移除的代码。我期待着倾听 我可以理解我至少需要10次。在我发布图片之前,我的图片说明了我的问题。。。因此,请点击此链接查看我在codereview.stackexchange.com上的原始问题- 我一直在努力解决一些架构问题,我自己也很难弄清楚 我试图用域模型和存储库模式构建项目的基本结构 当我想要实现一些业务逻辑和不同类型的UI(例如WinForms和MVC)时,构建POCO类和存储库非常容易。我觉得我错过了一些东西,因为我觉得我的代码是紧密耦合的,每当我需要获取对象并显示它时,我总是必须引用POCO类 我首先在C#(vs2012)中构建以下项目: 模型 达尔 基本法 测试控制台 下面是我的模型a模型类的一个示例: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类和存储库非常容易。我觉得我错过了一些
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);
}
}
}