【问题标题】:Clean Arcitecture EF Core DbContext project清洁架构 EF Core DbContext 项目
【发布时间】:2023-04-05 23:54:02
【问题描述】:

寻找有关清洁架构方法和 EF Core 的一般指导。

我有一个 Web 应用 (UI) 项目、域/业务项目和我的数据/基础设施项目,用于所有 crud 操作。

在拉入和创建 DbContext 时,上下文本身是否应该存储在 Data 项目中,而 Entities(表类)应该存储在 Domain/Business 项目中,因为它们没有直接的 crud 选项。

或者 DbContext 和相应的实体(表)应该一起在 Data 项目中。

感谢任何帮助。

【问题讨论】:

    标签: entity-framework entity-framework-core asp.net-core-mvc clean-architecture


    【解决方案1】:

    您应该根据业务需求设计您的领域模型类,并以最有效的方式实现业务领域不变量。

    这意味着不会对您的域模型实体造成任何基础架构问题的负担。如果您将这些类放入域项目并将 DbContext 和 EFCore 配置类之类的东西放入数据层,则可以更容易地遵守此原则。

    您可以查看参考Microsoft reference project,其中正在遵循此方法。

    放置在核心(或业务域} 项目中的order entity(这里也是一个聚合根)除了 EFCore 所需的无参数私有构造函数之外没有任何基础架构问题 - 可接受的交易-关闭。

    using Ardalis.GuardClauses;
    using Microsoft.eShopWeb.ApplicationCore.Interfaces;
    using System;
    using System.Collections.Generic;
    
    namespace Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate
    {
        public class Order : BaseEntity, IAggregateRoot
        {
            private Order()
            {
                // required by EF
            }
    
            public Order(string buyerId, Address shipToAddress, List<OrderItem> items)
            {
                Guard.Against.NullOrEmpty(buyerId, nameof(buyerId));
                Guard.Against.Null(shipToAddress, nameof(shipToAddress));
                Guard.Against.Null(items, nameof(items));
    
                BuyerId = buyerId;
                ShipToAddress = shipToAddress;
                _orderItems = items;
            }
    
            public string BuyerId { get; private set; }
            public DateTimeOffset OrderDate { get; private set; } = DateTimeOffset.Now;
            public Address ShipToAddress { get; private set; }
    
            // DDD Patterns comment
            // Using a private collection field, better for DDD Aggregate's encapsulation
            // so OrderItems cannot be added from "outside the AggregateRoot" directly to the collection,
            // but only through the method Order.AddOrderItem() which includes behavior.
            private readonly List<OrderItem> _orderItems = new List<OrderItem>();
    
            // Using List<>.AsReadOnly() 
            // This will create a read only wrapper around the private list so is protected against "external updates".
            // It's much cheaper than .ToList() because it will not have to copy all items in a new collection. (Just one heap alloc for the wrapper instance)
            //https://msdn.microsoft.com/en-us/library/e78dcd75(v=vs.110).aspx 
            public IReadOnlyCollection<OrderItem> OrderItems => _orderItems.AsReadOnly();
    
            public decimal Total()
            {
                var total = 0m;
                foreach (var item in _orderItems)
                {
                    total += item.UnitPrice * item.Units;
                }
                return total;
            }
        }
    }
    

    order entity database configuration 放置在基础架构/数据项目中,以检测域和数据库层之间的映射,使域项目独立于此类基础架构问题。

    using Microsoft.EntityFrameworkCore;
    using Microsoft.EntityFrameworkCore.Metadata.Builders;
    using Microsoft.eShopWeb.ApplicationCore.Entities.OrderAggregate;
    
    namespace Microsoft.eShopWeb.Infrastructure.Data.Config
    {
        public class OrderConfiguration : IEntityTypeConfiguration<Order>
        {
            public void Configure(EntityTypeBuilder<Order> builder)
            {
                var navigation = builder.Metadata.FindNavigation(nameof(Order.OrderItems));
    
                navigation.SetPropertyAccessMode(PropertyAccessMode.Field);
    
                builder.OwnsOne(o => o.ShipToAddress, a =>
                {
                    a.WithOwner();
                    
                    a.Property(a => a.ZipCode)
                        .HasMaxLength(18)
                        .IsRequired();
    
                    a.Property(a => a.Street)
                        .HasMaxLength(180)
                        .IsRequired();
    
                    a.Property(a => a.State)
                        .HasMaxLength(60);
    
                    a.Property(a => a.Country)
                        .HasMaxLength(90)
                        .IsRequired();
    
                    a.Property(a => a.City)
                        .HasMaxLength(100)
                        .IsRequired();
                });
            }
        }
    }
    

    这样,您就可以清晰地分离关注点,并可以专注于业务层中的业务逻辑,从而获得更好的逻辑可测试性,并且您仍然能够利用 EFCore 的优势。

    【讨论】:

    • 感谢详细的回答。如果我可以问一个跟进。我们来自带有 edmx 文件的传统 asp.net ef 6 方法。我们公司坚持使用数据库优先的方法。大多数 ef 核心示例采用代码优先方法。我已经克服了这个障碍,可以使用 EF Core Power Tools 将文件放置在我需要的地方。但我想知道这是否会影响您对文件放置位置的看法。似乎您在上面提供的相同逻辑将适用,但我会验证。谢谢!
    • 文件的确切放置位置并不像确保域模型类是根据业务需求构建的那样重要,以最好地支持反映域和实现业务逻辑,而不是基于如何数据库中的持久性是结构化的。这还包括使用通用语言来命名类、方法和字段。如果这些领域模型类还允许您进行单元测试,那么它可以实现业务逻辑而无需数据库的负担,那么无论您必须使用什么技术,您都已经处于一个好位置。
    • 虽然我个人总是更喜欢代码优先,但只要您仍有遵循域模型的代码,数据库优先就不应该成为您的方式。数据库优先的危险在于人们从关系数据库的角度思考并以这种方式创建这些结构,然后尝试将业务逻辑塞入这些结构中。我想要将域逻辑和持久性问题分开的原因——除非没有复杂的逻辑并且 CRUD 会这样做——是两者同样重要,我希望能够相互独立地发展和调整这两个部分。
    猜你喜欢
    • 1970-01-01
    • 2016-04-24
    • 2014-06-22
    • 2021-06-17
    • 2021-02-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    相关资源
    最近更新 更多