如何使用仓储 - CodeFirst
前言
本文主要介绍在Adnc框架中实体如何映射到数据库,示例采用CodeFirst模式,当然你也可以使用DBFirst模式。 本文所有操作都是在Adnc.Cust
微服务中完成,其它微服务定义方式都一样。
如何映射
第一步,创建实体
我们需要在Adnc.Cus.Croe
工程下的Entities
目录创建实体,如Customer
。
如果采用经典三层模式开发,我们定义的实体必须直接继承或间接继承
EfEntity
类。如果采用DDD架构模式开发, 我们定义的实体必须直接继承或间接继承
AggregateRoot
类。Adnc.Cust 是采用经典三层模式开发的,DDD架构模式请参考Adnc.Ord/Adnc.Whse。实现模式都一样。
EfEntity
经典三层开发模式中所有实体类的基类
public abstract class EfBasicAuditEntity : EfEntity, IBasicAuditInfo
public abstract class EfFullAuditEntity : EfEntity, IFullAuditInfo
namespace Adnc.Cus.Core.Entities
{
[Description("客户表")]
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
。
namespace Adnc.Cus.Core.Entities.Config
{
//EntityTypeConfiguration<TEntity>抽象类实现了IEntityTypeConfiguration<TEntity>接口
public class CustomerConfig : EntityTypeConfiguration<Customer>
{
//覆写Configure基类方法
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(16);
builder.Property(x => x.Nickname).IsRequired().HasMaxLength(16);
builder.Property(x => x.Realname).IsRequired().HasMaxLength(16);
}
}
}
很多示例中CustomerConfig
是直接继承IEntityTypeConfiguration<TEntity>
这个接口。我这里稍微封装了下。创建了一个EntityTypeConfiguration<TEntity>
抽象类并实现了IEntityTypeConfiguration<TEntity>
接口。然后我们实体关系映射类再继承这个抽象类。这样做主要是为了统一处理一些公共特性字段的映射。如软删除、并发列映射等等,代码如下。
namespace Adnc.Core.Shared.Entities.Config
{
public abstract class EntityTypeConfiguration<TEntity> : IEntityTypeConfiguration<TEntity>
where TEntity : Entity
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
var entityType = typeof(TEntity);
//映射主键
builder.HasKey(x => x.Idji);
builder.Property(x => x.Id).ValueGeneratedNever();
//如果实体实现了IConcurrency接口,映射RowVersion字段
if (typeof(IConcurrency).IsAssignableFrom(entityType))
{
builder.Property("RowVersion").IsRequired().IsRowVersion().ValueGeneratedOnAddOrUpdate();
}
//如果实体实现了ISoftDelete接口,映射IsDeleted字段
if (typeof(ISoftDelete).IsAssignableFrom(entityType))
{
builder.Property("IsDeleted").HasDefaultValue(false);
builder.HasQueryFilter(d => EF.Property<bool>(d, "IsDeleted") == false);
}
}
}
}
第三步,创建EntityInfo类
在Entities
创建一个EntityInfo
类,并实现IEntityInfo
接口。这个类每个工程只需要定义一个,是公用的。我的项目模板生成工具写好后,项目模板生成工具生成的项目会包含这个类。GetEntitiesInfo()
方法就是在当前程序集中查找继承了EfEntity
的类,并放入集合中。
namespace Adnc.Cus.Core.Entities
{
public class EntityInfo : AbstractEntityInfo
{
public override (Assembly Assembly, IEnumerable<Type> Types) GetEntitiesInfo()
{
var assembly = this.GetType().Assembly;
var entityTypes = base.GetEntityTypes(assembly);
return (assembly, entityTypes);
}
}
}
第四步,注入EntityInfo
到容器
我们在工程根目录下的AdncCusCoreModule
注册类里面注册。我的项目模板生成工具写好后,项目模板生成工具生成的项目会包含这个类并且同时会注册EntityInfo
和其它组件。
namespace Adnc.Cus.Core
{
public class AdncCusCoreModule : Module
{
/// <summary>
/// Autofac注册
/// </summary>
/// <param name="builder"></param>
protected override void Load(ContainerBuilder builder)
{
//注册其它组件
//todo
//注册EntityInfo
builder.RegisterType<EntityInfo>().As<IEntityInfo>().InstancePerLifetimeScope();
//注册其它组件
//todo
}
}
}
第五步,生成迁移代码并更新到数据库
- 设置
Adnc.Cus.WebApi
为启动项目(迁移命令会从这个工程读取数据库连接串) - 在VS工具中打开Nuget的程序包管理器控制台(工具=>Nuget包管理器=>程序包管理器控制台)
- 设置“程序包管理器控制台”默认项目为
Adnc.Cus.Migrations
- 执行命令
add-migration Update2021030401
。执行成功后,会在Adnc.Cus.Migrations
工程的Migrations
目录下生成迁移文件。 - 执行命令
update-database
,更新到数据库。
实体是如何与数据库关联起来的呢?
我们看Adnc.Infr.EfCore
工程的AdncDbContext
类的源码。
namespace Adnc.Infr.EfCore
{
public class AdncDbContext : DbContext
{
private readonly UserContext _userContext;
private readonly IEntityInfo _entityInfo;
private readonly UnitOfWorkStatus _unitOfWorkStatus;
public AdncDbContext([NotNull] DbContextOptions options
, UserContext userContext
//注入IEntityInfo
, [NotNull] IEntityInfo entityInfo
, UnitOfWorkStatus unitOfWorkStatus)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//获取实体信息
var (Assembly, Types) = _entityInfo.GetEntitiesInfo();
//这里
foreach (var entityType in Types)
{
modelBuilder.Entity(entityType);
}
//这里
modelBuilder.ApplyConfigurationsFromAssembly(Assembly);
base.OnModelCreating(modelBuilder);
}
}
}