Skip to main content

私有引用示例

场景

我们再来学习一个电商相关的领域概念,SKU,即最小库存单位在仓储营理中,需要对品类(如上衣、裤子)作进一步地细分,细分的依据是该品类的属性,可能是一个属性,也可能是多个属性组合。例如服装行业通常采用“颜色”与“尺码”组合,如果一款衣服有两种颜色、三种尺码,两者组合即可细分出6类,这6个细分类就是6个SKU,作为细分依据的属性“颜色”、“尺码”则称为库存属性(有时也称为销售属性) 。

Sku介绍

SKU,全称为Stock Keeping Unit,即最小存货单位,是库存进出计量的基本单元,可以以件、盒、托盘等为单位。例如:XL码、白色、021型的T恤每件是一个SKU,S码、白色、021型的T恤每件则是另一个SKU。因此,同种商品如有多种款式、大小、颜色等,便需要有多个SKU。在使用时要根据不同业态,不同管理模式来处理。

之所以称作“最小”库存单位,是因为这一细分层级是库存营理的最小粒度,同属一个SKU的产品肯定还可以找出差别,但库存管理不关注,管理员登记产品数量、价格和存储位置时只精确到SKU这一层级 从上面的概念分析可以看出,SKU不是独立存在的,它依赖于产品库存属性的取值。因此我们不允许外部直接添加、删除SKU,必须封装"生成SKU”的触发机制(通过SetPropertyValue RemovePropety、RemovePropertyValue三个方法): 如果设置的属性是库存属性,那么就会触发生成SKU; 如果移除库存属性的值,就会同步销毁相应的SKU。 这意味着,在反持久化时无法将从数据库读取到的关联实例写入Product对象。那么,在这种场景下还能否使用持久化框架呢?

http://indexmp4.suiyiyun.cn/img/image-20231031201235482.png

解决方案

实体模型

/// <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");
}
}