在洋葱建筑中放置视图模型/ DTO

Bo *_*sen 7 c# asp.net-mvc entity-framework onion-architecture

我目前正在重构ASP.NET MVC项目以使用洋葱arcitecture,因为它似乎适合未来发展的需要.

我已经设置了我认为需要使用的图层,我的解决方案现在看起来像这样:

在此输入图像描述

所以,基本上我已经理解,该ClientName.Core项目根本不应该参考其他项目.本ClientName.Infrastructure应具有的一个参考ClientName.Core.Interfaces文件夹ClientName.Core定义项目中的服务,ClientName.Infrastructure我的DbContext和域实体是分开的,因此只有实体在核心项目中.

我把头靠在墙上的是,ClientName.Infrastructure项目不应该将域实体返回到调用它的客户端.这将在核心项目和任何"违反"洋葱原则的UI之间创建引用.正如我所读到的,解决这个问题的方法是让基础设施服务返回DTO.但是,如果我PersonDtoPersonService类中返回ie PersonDto,则ClientName.Core项目需要知道该对象,因为这是接口所在的位置.

所以问题是:我在哪里放置DTO/ViewModel /用于UI /客户端的其他模型?我是否创建了一个单独的类库来保存这些模型,并让UI,基础结构和核心项目都引用它?

非常感谢任何帮助/提示,​​因为我对此有点困惑;-)

提前致谢.

编辑

根据Euphorics的回答,我在这里编写示例代码只是为了检查我是否正确,可能还有一些后续问题.

所以基本上,在我的ClientName.Core层中,我有我的实体,其中包含业务逻辑,即a PersonFirm实体可能如下所示:

(Exists in ClientName.Core/Entities)
public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }   
    public Firm Firm { get; set; }
}

(Exists in ClientName.Core/Entities)
Public class Firm
{
    public int Id { get; set; }
    public string Name { get; set; }
    public IEnumerable<Person> Employees { get; set; }

    public IEnumerable<Person> GetEmployeesByName(string name)
    {
        return Employees.Where(x => x.Name.Equals(name));
    }

    public void AddEmployee(Person employee)
    {
        Employees.Add(employee);
    } 

    public void CreateFirm(Firm firm)
    {
        // what should happen here? Using entity framework, I have no reference to the DbContext here...
    }
}

(Exists in ClientName.Infrastructure/Services)
public class FirmService : IFirmService
{
    private readonly IDbContext _context;

    public FirmService(IDbContext context)
    {
        _context = context;
    } 

    public Firm GetById(int firmId)
    {
        return _context.Firms.Find(firmId);
    } 

    // So basically, this should not call any domain business logic?
    public void CreateFirm(CreateFirmFormViewModel formViewModel)
    {
        Firm firm = new Firm()
        {
           Name = formViewModel.Name;
        } 

        _context.Firms.Add(firm);
        _context.SaveChanges();
    } 

    public IEnumerable<Person> GetEmployeesByName(int firmId, string name)
    {
        Firm firm = _context.Firms.Find(firmId);
        return firm.GetEmployeesByName(name);
    }
}
Run Code Online (Sandbox Code Playgroud)

我是否正确,每个读取查询应该直接在实体上定义(可能作为实体扩展,因为我使用实体框架),任何创建/更新/删除只会发生在基础设施层的服务中?

read(即IEnumerable<Person> GetEmployeesByName(int firmId, string name)方法)方法是否也应该在FirmService接口/类上?

再次感谢 :-)

Eup*_*ric 6

首先,您不是Person从返回PersonService,而是从IPersonService。这是巨大的概念差异。PersonService 只实现IPersonService定义的内容。它不关心它使用什么接口或对象。

简而言之,Core 项目应该包括业务实体中的所有业务逻辑。如果您没有任何实际逻辑,那将只是简单的 DTO。然后,Infrastructure将负责从数据库中保存/加载这些实体。如果它使用自己的实体创建自己的模型,或者它重用由 core is 定义的模型,那么它的Infrastructure实现细节。同时,UI(或Web)项目将与项目中的实体一起Core工作,但它不会关心持久化是如何发生的。它只是想“做生意”。

这里的关键点是核心(或业务逻辑)是可自动测试的,无需涉及UI或数据库代码。只要你能做到这一点,一些 DTO 在哪里并不重要。

编辑:

这开始变得困难了。您遇到了相互冲突的设计要求。一方面,您有明确的域设计。另一方面,您希望您的模型可以使用 EF 持久化。有一点很清楚:您将妥协并扭曲您的域模型,以便它可以在 EF 中使用。

现在是具体问题。第一个是创建公司。在这里,您在域所理解的“新公司”之间存在冲突。例如。确保新公司具有有效名称。另一方面,您希望创建实例并使 EF 知道它。我这样做的方法是CreateFirmFirm类中删除并更改CreateFirmIFirmService以便它只接受创建新公司所需的参数Firm(例如,只是名称)并返回新创建的公司。此外,存储库不应调用域的想法是错误的。例如,存储库可能会调用域来验证它创建的公司是否有效,如果不是,则不要将其添加到 EF 中。

我看到的第二个问题是在公司中保留员工集合。虽然这对域来说很好,但对于持久性来说却是灾难(除非你认为在这种情况下延迟加载很好)。再次。没有简单的方法如何满足“公司拥有员工集合”的领域需求以及将实体持久化到数据库中的能力。第一个选择是不拥有此集合并将其移动到IFirmService可以正确实现的地方。第三种选择是最极端的。那就是Firm在使所有“get”成为抽象方法的同时创建一个抽象类,在基础设施项目中创建它的实现,并使用具体实例将具有的 EF 上下文来实现 get 方法。此外,这可能只有在具体实例Firm 是在基础设施中创建的,这是我上面建议的。

我个人喜欢第三个选项,因为它使方法保持良好和有凝聚力。但一些英孚纯粹主义者可能不同意我的观点。