关联端级联操作
场景
有时候,我们不仅要跟踪关联实例的创建与销毁,还需要跟踪关联端的创建与销毁。 我们以产品与SKU之间的关联为例。这个关联类似于汽车与车轮之间的关系,所不同的是,SKU是专属于产品的 (组合关系),一个产品的SKU是由产品自己生成的,也就是说Sku实例是在Product对象内部创建的。当我们为产品设置属性时如果该属性是库存属性就会触发这个内部创建过程。也就是说,程序员无法显式通知持久化框架“有一个对象被创建出来了”或者“有一个对象应当被销毁”。那么,持久化框架在跟踪到一个关联被创建或销毁时,我们如何让框架知道该关联所指向的对象也是新建或待销毁的呢?
Sku介绍
SKU,全称为Stock Keeping Unit,即最小存货单位,是库存进出计量的基本单元,可以以件、盒、托盘等为单位。例如:XL码、白色、021型的T恤每件是一个SKU,S码、白色、021型的T恤每件则是另一个SKU。因此,同种商品如有多种款式、大小、颜色等,便需要有多个SKU。在使用时要根据不同业态,不同管理模式来处理。
解决方案
实体模型
/// <summary>
///产品
/// </summary>
public class Product
{
/// <summary>
///产品编号
/// </summary>
private string _code;
/// <summary>
///产品名称
/// </summary>
private string _name;
/// <summary>
///属性取值
/// </summary>
private List<PropertyTakingValue> _propertyTakingValues;
/// <summary>
///SKU
/// </summary>
private List<Sku> _skus;
/// <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;
protected internal set => _propertyTakingValues = value;
}
/// <summary>
///SKU
/// </summary>
public List<Sku> Skus
{
get => _skus;
protected internal set => _skus = value;
}
/// <summary>
///设置一 个新的属性取值
/// </summary>
/// <param name="property">属性</param>
/// <param name="value">值</param>
/// <param name="photoUrl">图片</param>
public void SetProperty(Property property, PropertyValue value, string photoUrl)
{
if (_propertyTakingValues == null)
_propertyTakingValues = new List<PropertyTakingValue>();
//查找属性取值
var takingValue = _propertyTakingValues.FirstOrDefault(p =>
p.PropertyCode == property.Code && p.PropertyValueCode == value.Code);
//没有
if (takingValue == null)
{
//添加属性取值
takingValue = new PropertyTakingValue
{
ProductCode = _code,
Product = this,
PropertyCode = property.Code,
Property = property,
PropertyValueCode = value.Code,
PropertyValue = value,
PropertyPhotoUrl = photoUrl
};
_propertyTakingValues.Add(takingValue);
//创建一个对应的SKU
if (_skus == null)
_skus = new List<Sku>();
_skus.Add(new Sku { Code = $"{_code}_{takingValue.Property.Code}_{takingValue.PropertyValue.Code}" });
}
else
{
//有 修改
takingValue.PropertyCode = property.Code;
takingValue.Property = property;
takingValue.PropertyCode = value.Code;
takingValue.PropertyValue = value;
takingValue.PropertyPhotoUrl = photoUrl;
}
}
/// <summary>
///移除某个属性的指定值
/// </summary>
/// <param name="propertyCode">属性Code</param>
/// <param name="valueCode">值Code</param>
public void RemoveProperty(string propertyCode, string valueCode)
{
if (_propertyTakingValues == null)
_propertyTakingValues = new List<PropertyTakingValue>();
_propertyTakingValues.RemoveAll(p => p.PropertyCode == propertyCode && p.PropertyValueCode == valueCode);
if (_skus == null)
_skus = new List<Sku>();
_skus.RemoveAll(p => p.Code == $"{_code}_{propertyCode}_{valueCode}");
}
/// <summary>
///获取某个属性所有的值
/// </summary>
/// <param name="propertyCode">属性Code</param>
/// <returns></returns>
public PropertyValue[] GetPropertyValue(string propertyCode)
{
if (_propertyTakingValues == null)
_propertyTakingValues = new List<PropertyTakingValue>();
return _propertyTakingValues.Where(p => p.PropertyCode == propertyCode).Select(p => p.PropertyValue)
.ToArray();
}
}
/// <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;
}
}
/// <summary>
///SKU
/// </summary>
public class Sku
{
/// <summary>
///数量
/// </summary>
private int _amount;
/// <summary>
///SKU编码 规则为 商品Code_{属性名1_属性值 ... 属性名N_属性值}
/// </summary>
private string _code;
/// <summary>
///仓储位置
/// </summary>
private string _locate;
/// <summary>
///单价
/// </summary>
private int _price;
/// <summary>
///产品代码
/// </summary>
private string _productCode;
/// <summary>
///命名空间内部构造函数
/// </summary>
internal Sku()
{
}
/// <summary>
///SKU编码 规则为 商品Code_属性名Code_属性值Code
/// </summary>
public string Code
{
get => _code;
protected internal set => _code = value;
}
/// <summary>
///数量
/// </summary>
public int Amount
{
get => _amount;
set => _amount = value;
}
/// <summary>
///仓储位置
/// </summary>
public string Locate
{
get => _locate;
set => _locate = value;
}
/// <summary>
///单价
/// </summary>
public int Price
{
get => _price;
set => _price = value;
}
/// <summary>
///产品代码
/// </summary>
public string ProductCode
{
get => _productCode;
protected internal set => _productCode = value;
}
}
模型配置
public class PrivateReferenceConfiguration : 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);
//将SKU配置为实体型
var skuEntity = modelBuilder.Entity<Sku>();
//配置主键
skuEntity.HasKeyAttribute(p => p.Code).HasKeyIsSelfIncreased(false);
//配置反持久化构造函数
//配置一个反持久化构造函数
skuEntity.HasConstructor(typeof(Sku).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null, Type.EmptyTypes, null)).End();
//配置多方关联的关联型
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");
//配置产品和SKU的关联型
var productAssSku = modelBuilder.Association<Product, Sku>();
//产品这一端 在映射表Sku里是主键Code映射为ProductCode
productAssSku.AssociationEnd(p => p.End1).HasMapping("Code", "ProductCode");
//Sku这一端在映射表Sku里是主键Code映射为Code
productAssSku.AssociationEnd(p => p.End2).HasMapping("Code", "Code")
//注意此处将SKU这一端配置为HasDefaultAsNew(true) 即默认将此端的新对象附加至上下文 对应了场景十一中的如何对内部关联对象管理问题
.HasDefaultAsNew(true);
//设置关联表
productAssSku.ToTable("Sku");
//配置关联引用 多方关联的左端是确定的 即关联型上的Product 右端无需配置
//注意此处的关联引用PropertyTakingValues的Set访问器是protected internal的 所以无法在应用层访问 只能通过SetProperty 和 RemoveProperty进行 添加 和 移除 即对应了场景九的私有引用问题
productEntity.AssociationReference(p => p.PropertyTakingValues).HasLeftEnd("Product");
//注意此处的关联引用Skus的Set访问器是protected internal的 所以无法在应用层访问 只能通过SetProperty 和 RemoveProperty进行 添加 和 移除 即对应了场景九的私有引用问题
productEntity.AssociationReference(p => p.Skus).HasLeftEnd("End1").HasRightEnd("End2");
}
}