Skip to main content

多对多隐式关联搜索

场景

我们在网站上发表文章时,通常要给文章设置一个分类,这里我们假定一篇文章只能归入一个分类,但可以从原来的分类移入另一个。 直观地讲,设置分类就是调用Article类的Category访问器给关联引用赋值,这样就需要先载入目标分类.但在项目实践中,为了提升性能我们更希望采用设置分类ID的方式,因为大多数情况下分类ID是已知的,这样可以减少一次载入分类实例的据库查询操作。 但这种方式存在风险,因为模型无法自洽: 既然Article引用了Category,那么在任何情况下都应该能导航到文章所属的分类,但采用上述方式后却无法导航到目标分类。 怎样才能解决这种性能与模型自洽之间的矛盾呢? (答案分两种情况讨论: 1.对于旧对象,直接使用延迟加载机制即可: 2.对于新对象,应使用ObjectSet的Create方法。对于第二种情况,强调三点: 1.Create方法有两个重载:2.使用此方法可以配置专属的构造器,3如果应用层不使用Create方法就无法激活按需加载机制,导致模型无法自洽,为防止这种情况,可以将构造函数设置成私有,定义静态的构造方法,传入仓储)

http://indexmp4.suiyiyun.cn/img/20240114184720.png

解决方案

实体模型

/// <summary>
///产品ID
/// </summary>
public class Product
{
/// <summary>
///所属的分类
/// </summary>
private List<Category> _categories;

/// <summary>
///产品Code
/// </summary>
private string _code;

/// <summary>
///产品名称
/// </summary>
private string _name;

/// <summary>
///产品Code
/// </summary>
public string Code
{
get => _code;
set => _code = value;
}

/// <summary>
///产品名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}

/// <summary>
///所属的分类
/// </summary>
public List<Category> Categories
{
get => _categories;
set => _categories = value;
}
}
 /// <summary>
///产品分类
/// </summary>
public class Category
{
/// <summary>
///产品分类ID
/// </summary>
private int _categoryId;

/// <summary>
///产品分类名称
/// </summary>
private string _name;

/// <summary>
///分类下的产品
/// </summary>
private List<Product> _products;

/// <summary>
///产品分类ID
/// </summary>
public int CategoryId
{
get => _categoryId;
set => _categoryId = value;
}

/// <summary>
///产品分类名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}

/// <summary>
///分类下的产品
/// </summary>
public List<Product> Products
{
get => _products;
set => _products = value;
}
}

模型配置

  /// <summary>
///显式化的产品分类隐式关联型
/// </summary>
public class ProductCategory
{
/// <summary>
///分类
/// </summary>
private Category _category;

/// <summary>
///分类ID
/// </summary>
private int _categoryId;

/// <summary>
///分类名称
/// </summary>
private string _categoryName;

/// <summary>
///产品
/// </summary>
private Product _product;

/// <summary>
///产品Code
/// </summary>
private string _productCode;

/// <summary>
///产品Code
/// </summary>
public string ProductCode
{
get => _productCode;
set => _productCode = value;
}

/// <summary>
///分类ID
/// </summary>
public int CategoryId
{
get => _categoryId;
set => _categoryId = value;
}

/// <summary>
///分类名称
/// </summary>
public string CategoryName
{
get => _categoryName;
set => _categoryName = value;
}

/// <summary>
///产品
/// </summary>
public Product Product
{
get => _product;
set => _product = value;
}

/// <summary>
///分类
/// </summary>
public Category Category
{
get => _category;
set => _category = value;
}
}
 /// <summary>
///隐式多对多关联搜索配置
/// </summary>
public class ImplicitMultiSearchConfiguration : 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 categoryEntity = modelBuilder.Entity<Category>();
//配置主键
categoryEntity.HasKeyAttribute(p => p.CategoryId).HasKeyIsSelfIncreased(true);

//配置显式化的隐式多对多关联型
var implicitMultiAssociation = modelBuilder.Association<ProductCategory>();
//配置产品关联端 在关联表中映射为主键Code->字段ProductCode
implicitMultiAssociation.AssociationEnd(p => p.Product).HasMapping("Code", "ProductCode");
//配置分类关联端 在关联表中映射为主键CategoryId->字段CategoryId
implicitMultiAssociation.AssociationEnd(p => p.Category).HasMapping("CategoryId", "CategoryId");
//多对多 独立关联表
implicitMultiAssociation.ToTable("ProductCategory");

//如果在概念建模阶段就注意到需要此种查询 域类已将此关联设置为显示关联时 类内会直接定义关联引用为List<ProductCategory> 则此处不需要做此转换
//此下的配置为领域模型未将关联引用显式化时的配置方式
//配置关联引用 注意此处使用的配置方法 指定的泛型参数为关联型类型
productEntity.AssociationReference<ProductCategory>("Categories", true)
//配置取值器 即从对象中取值的方法 此处即为从关联型转换为List<Category>
.HasValueGetter(new DelegateValueGetter<Product, List<ProductCategory>>(p =>
{
if (p.Categories == null || p.Categories.Count == 0)
return null;
return p.Categories.Select(q => new ProductCategory
{
Category = q,
CategoryId = q.CategoryId,
CategoryName = q.Name,
Product = p,
ProductCode = p.Code
}).ToList();
}))
//配置设值器 即为对象设置值 此处即为从关联型转换为List<Category>
.HasValueSetter<ProductCategory>((p, impValue) =>
{
if (impValue != null)
{
if (p.Categories == null)
p.Categories = new List<Category>();
//检查是否为自己的关联 以及去重
if (p.Code == impValue.ProductCode
&& p.Categories.All(q => q.CategoryId != impValue.CategoryId))
p.Categories.Add(impValue.Category);
}
}, eValueSettingMode.Appending)
//左端为自己 产品Product 右端分类Category
.HasLeftEnd("Product").HasRightEnd("Category");

//配置关联引用 注意此处使用的配置方法 指定的泛型参数为关联型类型
categoryEntity.AssociationReference<ProductCategory>("Products", true)
//配置取值器 即从对象中取值的方法 此处即为从关联型转换为List<Product>
.HasValueGetter(new DelegateValueGetter<Category, List<ProductCategory>>(p =>
{
if (p.Products == null || p.Products.Count == 0)
return null;
return p.Products.Select(q => new ProductCategory
{
Category = p,
CategoryId = p.CategoryId,
CategoryName = p.Name,
Product = q,
ProductCode = q.Code
}).ToList();
}))
//配置设值器 即为对象设置值 此处即为从关联型转换为List<Product>
.HasValueSetter<ProductCategory>((p, impValue) =>
{
if (impValue != null)
{
if (p.Products == null)
p.Products = new List<Product>();
//检查是否为自己的关联 以及去重
if (p.CategoryId == impValue.CategoryId
&& p.Products.All(q => q.Code != impValue.ProductCode))
p.Products.Add(impValue.Product);
}
}, eValueSettingMode.Appending)
//左端为自己 分类Category 右端产品Product
.HasLeftEnd("Category").HasRightEnd("Product");
}
}