继承关系示例
场景
领域建横过程中经常会涉及到继承与派生。比如为促销活动设置的奖品会有多种,实物奖品、优惠券、红包等等。这些奖品有共同的特性,也有各自独特的特性。我们可以定义一个抽象甚类来描述共同特性,然后由它派生出类型的奖品。 另外,继承关系还可能与关联关系整合在一起,使问题更加复杂。比如我们举行促销活动时会配属一些奖品,这是一种关联关系。但参与关联的奖品不是具体的奖品类型,因为配属给活动的奖品可能不只一种类型,因此应该以奖品的抽象基类作为关联方 持久化过程中我们该如何处理继承关系及其与关联关系的融合使用呢?
解决方案
实体模型
/// <summary>
///活动
/// </summary>
public class Activity
{
/// <summary>
///活动ID
/// </summary>
private int _id;
/// <summary>
///活动名称
/// </summary>
private string _name;
/// <summary>
///礼物
/// </summary>
private List<Prize> _prizeList;
/// <summary>
///活动ID
/// </summary>
public int Id
{
get => _id;
set => _id = value;
}
/// <summary>
///活动名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}
/// <summary>
///礼物
/// </summary>
public List<Prize> PrizeList
{
get => _prizeList;
set => _prizeList = value;
}
}
/// <summary>
///实体礼物
/// </summary>
public class InKindPrize : Prize
{
/// <summary>
///礼物名称
/// </summary>
private string _name;
/// <summary>
///礼物名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}
}
/// <summary>
///奖品(抽象基类)
/// </summary>
public abstract class Prize
{
/// <summary>
///活动ID
/// </summary>
private int _activityId;
/// <summary>
///奖品ID
/// </summary>
private int _id;
/// <summary>
///奖品ID
/// </summary>
public int Id
{
get => _id;
set => _id = value;
}
/// <summary>
///活动ID
/// </summary>
public int ActivityId
{
get => _activityId;
set => _activityId = value;
}
}
/// <summary>
///红包
/// </summary>
public class RedEnvelope : Prize
{
/// <summary>
///数额
/// </summary>
private int _amount;
/// <summary>
///数额
/// </summary>
public int Amount
{
get => _amount;
set => _amount = value;
}
}
模型配置
/// <summary>
///奖品构造器
/// </summary>
public class PrizeConstrustor : InstanceConstructor
{
/// <summary>
///构造奖品构造器
/// </summary>
public PrizeConstrustor()
{
//要求将Type字段作为参数传入 以便确定类型
SetParameter("Type", "Type");
//还需要将实现类的属性也加入 因为在构造基类时是不知道实现类的具体属性的 需要一并查出后构造
SetParameter("Amount", "Amount");
SetParameter("Name", "Name");
}
/// <summary>构造对象。</summary>
/// <returns>构造出的对象。</returns>
/// <param name="arguments">构造函数参数。</param>
public override object Construct(object[] arguments = null)
{
//此时第一个参数即为Type
if (arguments != null && arguments.Length == 3)
{
//规定0 是InKindPrize
if ((int)arguments[0] == 0)
//构造实体奖品对象 其中第三个参数即为Name 与构造函数中的顺序一致
return new InKindPrize { Name = arguments[2].ToString() };
//规定1 是RedEnvelope
if ((int)arguments[0] == 1)
//构造红包对象 其中第二个参数即为Amount 与构造函数中的顺序一致
return new RedEnvelope { Amount = (int)arguments[1] };
throw new ArgumentException("奖品构造失败,未知的奖品类型");
}
throw new ArgumentException("奖品构造失败,参数不匹配");
}
}
/// <summary>
///继承关系配置
/// </summary>
public class ImmplimentConfiguration : MySqlContextConfigProvider
{
/// <summary>由派生类实现,获取数据库连接字符串。</summary>
protected override string ConnectionString => Configuration.MySqlConnectionString;
/// <summary>获取一个值,该值指示是否启用存储结构映射</summary>
protected override bool EnableStructMapping => true;
/// <summary>使用指定的建模器创建对象数据模型。</summary>
/// <param name="modelBuilder"></param>
protected override void CreateModel(ModelBuilder modelBuilder)
{
//配置活动实体型
var activityEntity = modelBuilder.Entity<Activity>();
//配置主键
activityEntity.HasKeyAttribute(p => p.Id);
//奖品是抽象的 需要自定义虚拟属性和构造器来支持 同时实现类配置DeriveFrom即可
//为奖品配置实体型
var prizeEntity = modelBuilder.Entity<Prize>();
//配置主键
prizeEntity.HasKeyAttribute(p => p.Id);
//为Prize配置一个虚拟的Type属性 此属性不需要定义于类内 但需要在数据源内定义
//只需要配置取值器 而且返回值无所谓 因为不能直接初始化抽象类Prize 实现类InKindPrize和RedEnvelope会各自配置此属性覆盖父类的配置
prizeEntity.Attribute("Type", typeof(int)).HasValueGetter(p => -1);
//还需要将实现类的属性也在基类内注册 以保证构造基类时这些属性正确的被赋值 这些属性都不需要设值器 只需要返回固定值的取值器
//因为取值器也只是在保存时做检查使用 会被具体的实现类的值覆盖
//具体可以参考PrizeConstrustor类内实现
prizeEntity.Attribute("Amount", typeof(int)).HasValueGetter(p => -1);
prizeEntity.Attribute("Name", typeof(string)).HasValueGetter(p => "");
//配置一个自定义的构造器 此构造器将Type设为构造用参数 用以区分构造哪个具体的实现类
//逻辑参考PrizeConstrustor类内实现
prizeEntity.HasConstructor(new PrizeConstrustor());
//为实体奖品配置实体型
var inKindPrizeEntity = modelBuilder.Entity<InKindPrize>();
//配置主键
inKindPrizeEntity.HasKeyAttribute(p => p.Id);
//为InKindPrize配置一个虚拟的Type属性 此属性不需要定义于类内 但需要在数据源内定义
//规定Type为0的是InKindPrize 取值固定为0 无需设值
inKindPrizeEntity.Attribute("Type", typeof(int)).HasValueGetter(p => 0)
.HasValueSetter<int>((prize, value) => { });
//配置为从Prize派生而来
inKindPrizeEntity.DeriveFrom(typeof(Prize));
//都存储在Prize里
//注意此处映射表为Prize的话 则未考虑单独查询InKindPrize
//如果需要单独查询InKindPrize的话 可以创建筛选条件为Type == 0的视图v_InKindPrize
//然后配置为ToTable("v_InKindPrize")
inKindPrizeEntity.ToTable("Prize");
//为红包配置实体型
var redEnvelopEntity = modelBuilder.Entity<RedEnvelope>();
//配置主键
redEnvelopEntity.HasKeyAttribute(p => p.Id);
//为RedEnvelope配置一个虚拟的Type属性 此属性不需要定义于类内 但需要在数据源内定义
//规定Type为1的是RedEnvelope 取值固定为1 无需设值
redEnvelopEntity.Attribute("Type", typeof(int)).HasValueGetter(p => 1)
.HasValueSetter<int>((prize, value) => { });
//配置为从Prize派生而来
redEnvelopEntity.DeriveFrom(typeof(Prize));
//都存储在Prize里
//注意此处映射表为Prize的话 则未考虑单独查询RedEnvelope
//如果需要单独查询RedEnvelope的话 可以创建筛选条件为Type == 1的视图v_RedEnvelope
//然后配置为ToTable("v_RedEnvelope")
redEnvelopEntity.ToTable("Prize");
//配置关联型
var activityAssPrize = modelBuilder.Association<Activity, Prize>();
//配置关联端 Activity关联端为End1这个属性 在关联表中Activity的主键Id映射为ActivityId
activityAssPrize.AssociationEnd(p => p.End1).HasMapping("Id", "ActivityId");
//配置关联端 Prize关联端为End2这个属性 在关联表中Prize的主键Id映射为Id
activityAssPrize.AssociationEnd(p => p.End2).HasMapping("Id", "Id");
//关联表是Prize
activityAssPrize.ToTable("Prize");
//配置活动实体型中的关联引用 左端就是关联型上自 己类型的这一端 即End1 右端即为另外一端End2
activityEntity.AssociationReference(p => p.PrizeList).HasLeftEnd("End1").HasRightEnd("End2");
}
}