多方关联
场景
首先让我们学习几个领域概念,电商领域的。"属性" (Property)用于描述产品某方面的特性,不同类目的产品具有不同的属性体系,如衣服有颜色、尺码、款式、适穿季节、布料、工艺等属性,而手机则有品牌、系统类型、机身大小、机身施色、摄像头像素、内存大小等腐性,具体到某个产品,它在上述特性上会呈现出具体的表现,比如某件衣服的颜色是红色、某部手机内存大小是256G,这里所说的"红色"、"256G"就是属性值 某件产品在某一特性上具有某种表现,在领域建模时我们把三者间的这种关系抽象成关联,称为“属性取值"(PropertyTakingValue) . 通常这个关联会有自己的属性。大家回忆一下在淘宝购物的情景。比如在购买衣服时,你选中某种颜色后,网页上展示的衣服照片会更改为对应的颜色。这是因为商家为不同的颜色属性值上传了相对应的衣服照片,该照片通常称为“属性图片” (实际上是个Ur)。"属性图片"作一个属性,既不属于Product,也不属于Property和PropertyValue,而是PropertyTakingValue这个关联的属性。PropertyTakingValue属于多方关联,而且也是显式关联。与前述多对多关联一样,大家多是把它当作实体类处理,并将其与Product,Property和PropertyValue分别建立关联。特别地,如果遇到隐式的多方关联,可能还需要"无中生有"地定义一个辅助类。 这些做法都是从持久化的需要出发对领域模型做出的扭曲,这些扭曲会导致我们的领域模型无法反映领域现实世界,为后续承载业务需求造成混乱。 那么,在坚持面向对象理念的前提下,我们应当如何处理多方关联呢?
解决方案
实体模型
/// <summary>
///产品
/// </summary>
public class Product
{
/// <summary>
///产品编号
/// </summary>
private string _code;
/// <summary>
///产品名称
/// </summary>
private string _name;
/// <summary>
///属性取值
/// </summary>
private List<PropertyTakingValue> _propertyTakingValues;
/// <summary>
///隐式的属性取值
/// </summary>
private List<Tuple<Property, PropertyValue>> _propertyValues;
/// <summary>
///产品编号
/// </summary>
public string Code
{
get => _code;
set => _code = value;
}
/// <summary>
///产品名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}
/// <summary>
///属性取值
/// </summary>
public List<PropertyTakingValue> PropertyTakingValues
{
get => _propertyTakingValues;
set => _propertyTakingValues = value;
}
/// <summary>
///隐式的属性取值
/// </summary>
public List<Tuple<Property, PropertyValue>> PropertyValues
{
get => _propertyValues;
set => _propertyValues = value;
}
}
/// <summary>
///属性
/// </summary>
public class Property
{
/// <summary>
///属性编码
/// </summary>
private string _code;
/// <summary>
///属性名称
/// </summary>
private string _name;
/// <summary>
///属性编码
/// </summary>
public string Code
{
get => _code;
set => _code = value;
}
/// <summary>
///属性名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}
}
/// <summary>
///属性取值
/// </summary>
public class PropertyTakingValue
{
/// <summary>
///产品
/// </summary>
private Product _product;
/// <summary>
///产品编码
/// </summary>
private string _productCode;
/// <summary>
///属性
/// </summary>
private Property _property;
/// <summary>
///属性编码
/// </summary>
private string _propertyCode;
/// <summary>
///属性取值图片
/// </summary>
private string _propertyPhotoUrl;
/// <summary>
///属性取值
/// </summary>
private PropertyValue _propertyValue;
/// <summary>
///属性取值编码
/// </summary>
private string _propertyValueCode;
/// <summary>
///属性取值图片
/// </summary>
public string PropertyPhotoUrl
{
get => _propertyPhotoUrl;
set => _propertyPhotoUrl = value;
}
/// <summary>
///产品编码
/// </summary>
public string ProductCode
{
get => _productCode;
set => _productCode = value;
}
/// <summary>
///产品
/// </summary>
public Product Product
{
get => _product;
set => _product = value;
}
/// <summary>
///属性编码
/// </summary>
public string PropertyCode
{
get => _propertyCode;
set => _propertyCode = value;
}
/// <summary>
///属性
/// </summary>
public Property Property
{
get => _property;
set => _property = value;
}
/// <summary>
///属性取值编码
/// </summary>
public string PropertyValueCode
{
get => _propertyValueCode;
set => _propertyValueCode = value;
}
/// <summary>
///属性取值
/// </summary>
public PropertyValue PropertyValue
{
get => _propertyValue;
set => _propertyValue = value;
}
}
/// <summary>
///属性值
/// </summary>
public class PropertyValue
{
/// <summary>
///属性值编码
/// </summary>
private string _code;
/// <summary>
///具体值
/// </summary>
private string _value;
/// <summary>
///属性值编码
/// </summary>
public string Code
{
get => _code;
set => _code = value;
}
/// <summary>
///具体值
/// </summary>
public string Value
{
get => _value;
set => _value = value;
}
}
模型配置
public class MultiConfiguration : 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 productEntity = modelBuilder.Entity<Product>();
//配置主键
productEntity.HasKeyAttribute(p => p.Code).HasKeyIsSelfIncreased(false);
//将属性配置为实体型
var propertyEntity = modelBuilder.Entity<Property>();
//配置主键
propertyEntity.HasKeyAttribute(p => p.Code).HasKeyIsSelfIncreased(false);
//将属性取值配置为实体型
var propertyValueEntity = modelBuilder.Entity<PropertyValue>();
//配置主键
propertyValueEntity.HasKeyAttribute(p => p.Code).HasKeyIsSelfIncreased(false);
//多方关联 即关联型上有数个关联端
//配置显式的多方关联的关联型
var multiAssociation = modelBuilder.Association<PropertyTakingValue>();
//第一个关联端 Product 此关联端在关联表PropertyTakingValue中的映射为Product的主键Code映射为ProductCode
multiAssociation.AssociationEnd(p => p.Product).HasMapping("Code", "ProductCode");
//第二个关联端 Property 此关联端在关联表PropertyTakingValue中的映射为Property的主键Code映射为PropertyCode
multiAssociation.AssociationEnd(p => p.Property).HasMapping("Code", "PropertyCode");
//第三个关联端 PropertyValue 此关联端在关联表PropertyTakingValue中的映射为PropertyValue的主键Code映射为PropertyValueCode
multiAssociation.AssociationEnd(p => p.PropertyValue).HasMapping("Code", "PropertyValueCode");
//配置关联引用 多方关联的左端是确定的 即关联型上的Product 右端无需配置
productEntity.AssociationReference(p => p.PropertyTakingValues).HasLeftEnd("Product");
//配置隐式的多方关联的关联型 此处的关联型为PropertyTakingValueImp 定义于基础设施层 因为是用于辅助Obase的数据模型而非领域模型需要的
var multiImpAssociation = modelBuilder.Association<PropertyTakingValueImp>();
//第一个关联端 Product 此关联端在关联表PropertyTakingValue中的映射为Product的主键Code映射为ProductCode
multiImpAssociation.AssociationEnd(p => p.Product).HasMapping("Code", "ProductCode");
//第二个关联端 Property 此关联端在关联表PropertyTakingValue中的映射为Property的主键Code映射为PropertyCode
multiImpAssociation.AssociationEnd(p => p.Property).HasMapping("Code", "PropertyCode");
//第三个关联端 PropertyValue 此关联端在关联表PropertyTakingValue中的映射为PropertyValue的主键Code映射为PropertyValueCode
multiImpAssociation.AssociationEnd(p => p.PropertyValue).HasMapping("Code", "PropertyValueCode");
//使用与显式多方关联相同的存储表
multiImpAssociation.ToTable("PropertyTakingValue");
//配置关联引用 多方关联的左端是确定的 即关联型上的Product 右端无需配置
//注意此处使用的配置方法 指定的泛型参数为关联型类型
productEntity.AssociationReference<PropertyTakingValueImp>("PropertyValues", true)
//配置取值器 即从对象中取值的方法 此处即为从关联型转换为List<Tuple<Property, PropertyValue>>
.HasValueGetter(new DelegateValueGetter<Product, List<PropertyTakingValueImp>>(p =>
{
if (p.PropertyValues == null || p.PropertyValues.Count == 0)
return null;
return p.PropertyValues.Select(q => new PropertyTakingValueImp
{
Product = p,
ProductCode = p.Code,
Property = q.Item1,
PropertyCode = q.Item1.Code,
PropertyValue = q.Item2,
PropertyValueCode = q.Item2.Code
}).ToList();
}))
//配置设值器 即为对象设置值 此处即为从关联型转换为List<Tuple<Property, PropertyValue>>
.HasValueSetter<PropertyTakingValueImp>((p, impValue) =>
{
if (impValue != null)
{
if (p.PropertyValues == null)
p.PropertyValues = new List<Tuple<Property, PropertyValue>>();
//检查是否为自己的关联 以及去重
if (p.Code == impValue.ProductCode
&& !p.PropertyValues.Any(q =>
q.Item1.Code == impValue.PropertyCode && q.Item2.Code == impValue.PropertyValueCode))
p.PropertyValues.Add(
new Tuple<Property, PropertyValue>(impValue.Property, impValue.PropertyValue));
}
}, eValueSettingMode.Appending)
.HasLeftEnd("Product");
}
}
/// <summary>
///显式化的隐式属性取值
/// </summary>
public class PropertyTakingValueImp
{
/// <summary>
///产品
/// </summary>
private Product _product;
/// <summary>
///产品编码
/// </summary>
private string _productCode;
/// <summary>
///属性
/// </summary>
private Property _property;
/// <summary>
///属性编码
/// </summary>
private string _propertyCode;
/// <summary>
///属性取值
/// </summary>
private PropertyValue _propertyValue;
/// <summary>
///属性取值编码
/// </summary>
private string _propertyValueCode;
/// <summary>
///产品编码
/// </summary>
public string ProductCode
{
get => _productCode;
set => _productCode = value;
}
/// <summary>
///产品
/// </summary>
public Product Product
{
get => _product;
set => _product = value;
}
/// <summary>
///属性编码
/// </summary>
public string PropertyCode
{
get => _propertyCode;
set => _propertyCode = value;
}
/// <summary>
///属性
/// </summary>
public Property Property
{
get => _property;
set => _property = value;
}
/// <summary>
///属性取值编码
/// </summary>
public string PropertyValueCode
{
get => _propertyValueCode;
set => _propertyValueCode = value;
}
/// <summary>
///属性取值
/// </summary>
public PropertyValue PropertyValue
{
get => _propertyValue;
set => _propertyValue = value;
}
}