您应该根据业务需求设计您的领域模型类,并以最有效的方式实现业务领域不变量。
这意味着不会对您的域模型实体造成任何基础架构问题的负担。如果您将这些类放入域项目并将 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 的优势。