我正在尝试了解实体框架 (EF) 能做什么、不能做什么。特别是,我有兴趣了解 EF 是否可以处理真实的领域模型。作为一个例子,考虑几个类,它们一起模拟一家餐厅及其一些业务规则和环境。
我将提供一个相当详细的示例,以使事情足够现实,希望问题能够传达一些设计选择,而这些选择不会在更简单的重现中消失。
首先,您有一个模拟餐厅的类。在这里,我只展示公共 API,因为由于封装,实现细节应该是无关紧要的:
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()
}
实际上,这个类并不是特别令人兴奋,因为所有有趣的事情都发生在
MaitreD
类中(同样,我只显示(部分)公共 API - 我没有省略任何属性,但省略了一些属性无副作用的方法):
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()
}
其中
TimeOfDay
是 TimeSpan
的包装,在更现代的 .NET 中可以用 TimeOnly
代替。最后,Table
类有这个API:
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()
}
请注意(根据设计)
Table
没有公共构造函数。
可以使用此数据库模式将此类对象的数据存储在 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
)
餐厅 ID 是外部分配的,因此
[Restaurants].[Id]
列不是 IDENTITY
列并不是错误。
是否可以使用 EF 将数据库架构(或类似的东西)直接映射到上述域模型?
当涉及到数据库模式时,我愿意接受建议,但不接受领域模型。领域模型类(
Restaurant
、MaitreD
和Table
)可能不会引用 EF(由于依赖倒置原则),因此添加 EF 属性或实现 EF 接口不是一个选项。
同样,领域模型类应该保持其封装性,因此可写属性(“setter”)、默认构造函数等都是不可能的。
另一方面,使用 OnModelCreating 或其他 DbContext API 来配置系统是公平的。
需要明确的是,我do已经知道如何在上述数据库模式上构建 EF,以便它为表和派生的
DbContext
生成 EF 特定的类。
这意味着我可以像这样从 EF 类映射到我的域模型:
public async Task<Restaurants.Restaurant?> GetRestaurant(string name)
{
using var db = new RestaurantsContext(ConnectionString);
var dbRestaurant =
await db.Restaurants.FirstOrDefaultAsync(r => r.Name == name);
if (dbRestaurant == null)
return null;
return ToDomainModel(dbRestaurant);
}
private static Restaurants.Restaurant ToDomainModel(Restaurant restaurant)
{
return new Restaurants.Restaurant(
restaurant.Id,
restaurant.Name,
new MaitreD(
new TimeOfDay(restaurant.OpensAt),
new TimeOfDay(restaurant.LastSeating),
restaurant.SeatingDuration,
restaurant.Tables.Select(ToDomainModel).ToList()));
}
private static Restaurants.Table ToDomainModel(Table table)
{
if (table.IsCommunal)
return Restaurants.Table.Communal(table.Capacity);
else
return Restaurants.Table.Standard(table.Capacity);
}
然而,我想弄清楚的是 EF 是否可以自动处理从数据库数据到域模型类的映射(以及相反的方式)。
EF 不会自动将数据模型转换为领域模型。但是,您可以使用 AutoMapper(流行库)来实现: https://automapper.org/
您可以像下面这样定义映射配置文件来定义从数据模型到域模型的映射逻辑,反之亦然:
using AutoMapper;
public RestaurantProfile : Profile
{
public RestaurantProfile()
{
CreateMap<Restaurant, Restaurants.Restaurant>().ReverseMap();
}
}
然后您可以将 AutoMapper 中的 IMapper 服务注入到您的类构造函数中并按如下方式使用:
private readonly IMapper _mapper;
public YourClassConstructor(IMapper mapper)
{
_mapper = mapper;
}
public async Task<Restaurants.Restaurant?> GetRestaurant(string name)
{
using var db = new RestaurantsContext(ConnectionString);
var dbRestaurant =
await db.Restaurants.FirstOrDefaultAsync(r => r.Name == name);
if (dbRestaurant == null)
return null;
return _mapper.Map<Restaurants.Restaurant, DomainModels.Restaurant>(dbRestaurant);
}