Entity Framework是否支持直接映射至封装式领域模型?
Entity Framework 与领域模型的双向自动映射可行性
领域模型
以下是封装后的Restaurant、MaitreD、Table类的公开API,其中Table类无公共构造函数,仅通过静态方法创建实例:
public sealed class Restaurant { public Restaurant(int id, string name, MaitreD maitreD) public int Id { get; } public string Name { get; } public MaitreD MaitreD { get; } public override bool Equals(object? obj) public override int GetHashCode() }
public sealed class MaitreD { public MaitreD( TimeOfDay opensAt, TimeOfDay lastSeating, TimeSpan seatingDuration, params Table[] tables) public MaitreD( TimeOfDay opensAt, TimeOfDay lastSeating, TimeSpan seatingDuration, IEnumerable<Table> tables) public TimeOfDay OpensAt { get; } public TimeOfDay LastSeating { get; } public TimeSpan SeatingDuration { get; } public IEnumerable<Table> Tables { get; } public override bool Equals(object? obj) public override int GetHashCode() }
public sealed class Table { public static Table Standard(int seats) public static Table Communal(int seats) public int Capacity { get; } public override bool Equals(object? obj) public override int GetHashCode() }
拟用数据库架构
SQL Server中的表结构如下:
CREATE TABLE [dbo].[Restaurants] ( [Id] INT NOT NULL, [Name] NVARCHAR (50) NOT NULL, [OpensAt] TIME NOT NULL, [LastSeating] TIME NOT NULL, [SeatingDuration] TIME NOT NULL PRIMARY KEY CLUSTERED ([Id] ASC) ) CREATE TABLE [dbo].[Tables] ( [Id] INT NOT NULL IDENTITY, [RestaurantId] INT NOT NULL REFERENCES [dbo].[Restaurants](Id), [Capacity] INT NOT NULL, [IsCommunal] BIT NOT NULL )
核心问题
能否将上述数据库架构(或类似架构)直接映射至上述领域模型,实现EF自动完成数据库数据与领域模型间的双向映射?
限制条件
- 仅可调整数据库架构,领域模型不可修改;
- 领域模型需遵循依赖倒置原则,不得引用EF、添加EF属性或实现EF接口;
- 需保持领域模型的封装性,不可添加可写属性、默认构造函数等;
- 仅可通过
OnModelCreating或其他DbContextAPI进行配置。
答案
可以实现符合要求的双向自动映射,直接使用现有数据库架构即可,只需在EF Core中通过OnModelCreating完成针对性配置,核心配置要点如下:
1. 处理Table类的实例化
EF Core支持通过工厂方法创建实体,无需破坏Table类的封装。在配置中指定根据数据库的IsCommunal字段,选择对应的静态方法创建实例:
modelBuilder.Entity<Table>(b => { // 映射数据库的Id字段为阴影属性(领域模型无需暴露该属性) b.Property<int>("Id").UseIdentityColumn(); b.HasKey("Id"); // 配置工厂方法:根据IsCommunal判断创建Standard或Communal类型的Table b.HasFactory((int capacity, bool isCommunal) => isCommunal ? Table.Communal(capacity) : Table.Standard(capacity)); // 映射领域模型的Capacity属性与数据库字段 b.Property(t => t.Capacity); // 映射存储Table类型的IsCommunal阴影属性 b.Property<bool>("IsCommunal"); });
2. 映射Restaurant与MaitreD的一对一关系
Restaurant的构造函数依赖MaitreD,且数据库中Restaurants表已包含MaitreD的所有属性字段,因此将MaitreD配置为Restaurant的拥有实体(Owned Entity),EF会自动将MaitreD的属性映射到Restaurants表的对应列:
modelBuilder.Entity<Restaurant>(b => { b.HasKey(r => r.Id); b.Property(r => r.Name).HasMaxLength(50); // 将MaitreD配置为Restaurant的拥有实体 b.OwnsOne(r => r.MaitreD, md => { md.Property(m => m.OpensAt).HasColumnType("time"); md.Property(m => m.LastSeating).HasColumnType("time"); md.Property(m => m.SeatingDuration).HasColumnType("time"); // 配置MaitreD与Table的一对多关联关系 md.HasMany<Table>().WithOne().HasForeignKey("RestaurantId"); }); });
3. 双向映射能力验证
- 读取数据:EF会通过配置的工厂方法创建
Table实例,通过拥有实体映射构建完整的MaitreD对象,最终组装成符合领域模型规范的Restaurant实例; - 保存数据:当修改领域模型(如新增
Table、调整MaitreD的时间属性)时,EF会自动将变更同步回对应的数据库表,无需手动做模型转换。
内容的提问来源于stack exchange,提问作者Mark Seemann
相关产品推荐
相关产品推荐

