前言
本文主要介绍在ADNC
框架中实体如何映射到数据库,示例采用CodeFirst模式,当然你也可以使用DBFirst模式。
本文所有操作都是在Adnc.Cust
微服务中完成,其它微服务定义方式都一样。
如何映射
第一步,创建实体
我们需要在Adnc.Cus.Repository
工程下的Entities
目录创建实体,如Customer
(如果是DDD模式则是Adnc.Xxx.Domain工程)。
如果采用经典三层模式开发,我们定义的实体必须直接继承或间接继承
EfEntity
类。
如果采用DDD架构模式开发, 我们定义的实体必须直接继承或间接继承AggregateRoot
或者DomainEntity
。
Adnc.Cust 是采用经典三层模式开发的,DDD架构模式请参考Adnc.Ord/Adnc.Whse。实现模式都一样。
EfEntity
经典三层开发模式中所有实体类的基类
//重要派生类
public abstract class EfBasicAuditEntity : EfEntity, IBasicAuditInfo
{
}
//重要派生类
public abstract class EfFullAuditEntity : EfEntity, IFullAuditInfo
{
}
创建一个实体类完整代码如下:
namespace Adnc.Cus.Entities
{
/// <summary>
/// 客户表
/// </summary>
public class Customer : EfFullAuditEntity
{
public string Account { get; set; }
public string Nickname { get; set; }
public string Realname { get; set; }
public virtual CustomerFinance FinanceInfo { get; set; }
public virtual ICollection<CustomerTransactionLog> TransactionLogs { get; set; }
}
}
第二步,定义映射关系
在Entities/Config
目录下创建映射关系类,如CustomerConfig
通过
fluentapi
创建映射关系。
namespace Adnc.Cus.Entities.Config
{
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
public override void Configure(EntityTypeBuilder<Customer> builder)
{
base.Configure(builder);
builder.HasOne(d => d.FinanceInfo).WithOne(p => p.Customer).HasForeignKey<CustomerFinance>(d => d.Id).OnDelete(DeleteBehavior.Cascade);
builder.HasMany(d => d.TransactionLogs).WithOne().HasForeignKey(p => p.CustomerId).OnDelete(DeleteBehavior.Cascade);
builder.Property(x => x.Account).IsRequired().HasMaxLength(CustConsts.Account_MaxLength);
builder.Property(x => x.Nickname).IsRequired().HasMaxLength(CustConsts.Nickname_MaxLength);
builder.Property(x => x.Realname).IsRequired().HasMaxLength(CustConsts.Realname_Maxlength);
}
}
}
很多示例中CustomerConfig
是直接继承IEntityTypeConfiguration<TEntity>
这个接口。我这里稍微封装了下。创建了一个EntityTypeConfiguration<TEntity>
抽象类并实现了IEntityTypeConfiguration<TEntity>
接口。然后我们实体关系映射类再继承这个抽象类。这样做主要是为了统一处理一些公共特性字段的映射。如软删除、并发列映射等等,代码如下。
public abstract class EntityTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEntity>
where TEntity : Entity
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
var entityType = typeof(TEntity);
ConfigureKey(builder, entityType);
ConfigureConcurrency(builder, entityType);
ConfigureQueryFilter(builder, entityType);
}
protected virtual void ConfigureKey(EntityTypeBuilder<TEntity> builder, Type entityType)
{
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).HasColumnOrder(1).ValueGeneratedNever();
}
protected virtual void ConfigureConcurrency(EntityTypeBuilder<TEntity> builder, Type entityType)
{
if (typeof(IConcurrency).IsAssignableFrom(entityType))
builder.Property("RowVersion").IsRequired().IsRowVersion().ValueGeneratedOnAddOrUpdate();
}
protected virtual void ConfigureQueryFilter(EntityTypeBuilder<TEntity> builder, Type entityType)
{
if (typeof(ISoftDelete).IsAssignableFrom(entityType))
{
builder.Property("IsDeleted")
.HasDefaultValue(false)
.HasColumnOrder(2);
builder.HasQueryFilter(d => !EF.Property<bool>(d, "IsDeleted"));
}
}
}
第三步,创建EntityInfo类
在Entities
创建一个EntityInfo
类,并实现IEntityInfo
接口。这个类每个工程只需要定义一个,是公用的。我的项目模板生成工具写好后,项目模板生成工具生成的项目会包含这个类。GetEntitiesTypeInfo()
方法就是在当前程序集中查找继承了EfEntity
的类,并放入集合中。
namespace Adnc.Cus.Entities
{
public class EntityInfo : AbstractEntityInfo
{
public override IEnumerable<EntityTypeInfo> GetEntitiesTypeInfo()
{
return base.GetEntityTypes(this.GetType().Assembly).Select(x => new EntityTypeInfo() { Type = x, DataSeeding = default });
}
}
}
第四步,注入EntityInfo
到容器,services.AddEfCoreContextWithRepositories()
扩展方法中会统一注册。
public abstract class AbstractApplicationDependencyRegistrar : IDependencyRegistrar
{
protected virtual void AddEfCoreContextWithRepositories(Action<IServiceCollection> action = null)
{
action?.Invoke(Services);
var serviceType = typeof(IEntityInfo);
var implType = RepositoryOrDomainAssembly.ExportedTypes.FirstOrDefault(type => type.IsAssignableTo(serviceType) && type.IsNotAbstractClass(true));
if (implType is null)
throw new NullReferenceException(nameof(IEntityInfo));
else
Services.AddSingleton(serviceType, implType);
//注册其他服务
}
}
第五步,生成迁移代码并更新到数据库
- 设置
Adnc.Cus.WebApi
为启动项目(迁移命令会从这个工程读取数据库连接串) - 在VS工具中打开Nuget的程序包管理器控制台(工具=>Nuget包管理器=>程序包管理器控制台)
- 设置“程序包管理器控制台”默认项目为
Adnc.Cus.Migrations
- 执行命令
add-migration Update2021030401
。执行成功后,会在Adnc.Cus.Migrations
工程的Migrations
目录下生成迁移文件。 - 执行命令
update-database
,更新到数据库。
实体如何关联数据库
我们看Adnc.Infra.EfCore.MySQL
工程的AdncDbContext
类的源码。
namespace Adnc.Infra.EfCore.MySQL
{
public class AdncDbContext : DbContext
{
private readonly Operater _operater;
private readonly IEntityInfo _entityInfo;
private readonly UnitOfWorkStatus _unitOfWorkStatus;
//构造函数注入IEntityInfo接口
public AdncDbContext([NotNull] DbContextOptions options, Operater operater,IEntityInfo entityInfo, UnitOfWorkStatus unitOfWorkStatus)
: base(options)
{
_operater = operater;
_entityInfo = entityInfo;
_unitOfWorkStatus = unitOfWorkStatus;
Database.AutoTransactionsEnabled = false;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasCharSet("utf8mb4 ");
//添加实体到模型上下文
var entityInfos = _entityInfo.GetEntitiesTypeInfo().ToList();
Guard.Checker.NotNullOrAny(entityInfos, nameof(entityInfos));
foreach (var info in entityInfos)
{
if (info.DataSeeding.IsNullOrEmpty())
modelBuilder.Entity(info.Type);
else
modelBuilder.Entity(info.Type).HasData(info.DataSeeding);
}
//从程序集加载fuluentapi加载配置文件
var assembly = entityInfos.FirstOrDefault().Type.Assembly;
modelBuilder.ApplyConfigurationsFromAssembly(assembly);
//这里做两件事情
//1、统一把表名,列名转换成小写。
//2、读取实体的注释<Summary>部分填充Comment
var types = entityInfos.Select(x => x.Type);
var entityTypes = modelBuilder.Model.GetEntityTypes().Where(x => types.Contains(x.ClrType)).ToList();
entityTypes.ForEach(entityType =>
{
modelBuilder.Entity(entityType.Name, buider =>
{
var typeSummary = entityType.ClrType.GetSummary();
buider.ToTable(entityType.ClrType.Name.ToLower()).HasComment(typeSummary);
var properties = entityType.GetProperties().ToList();
properties.ForEach(property =>
{
var memberSummary = entityType.ClrType.GetMember(property.Name).FirstOrDefault().GetSummary();
buider.Property(property.Name)
.HasColumnName(property.Name.ToLower())
.HasComment(memberSummary);
});
});
});
}
}
}
WELL DONE
全文完