按需自动加载
场景
我们在网站上发表文章时,通常要给文章设置一个分类,这里我们假定一篇文章只能归入一个分类,但可以从原来的分类移入另一个。 直观地讲,设置分类就是调用Article类的Category访问器给关联引用赋值,这样就需要先载入目标分类.但在项目实践中,为了提升性能我们更希望采用设置分类ID的方式,因为大多数情况下分类ID是已知的,这样可以减少一次载入分类实例的据库查询操作。 但这种方式存在风险,因为模型无法自洽: 既然Article引用了Category,那么在任何情况下都应该能导航到文章所属的分类,但采用上述方式后却无法导航到目标分类。 怎样才能解决这种性能与模型自洽之间的矛盾呢? (答案分两种情况讨论: 1.对于旧对象,直接使用延迟加载机制即可: 2.对于新对象,应使用ObjectSet的Create方法。对于第二种情况,强调三点: 1.Create方法有两个重载:2.使用此方法可以配置专属的构造器,3如果应用层不使用Create方法就无法激活按需加载机制,导致模型无法自洽,为防止这种情况,可以将构造函数设置成私有,定义静态的构造方法,传入仓储)
解决方案
实体模型
/// <summary>
///文章
/// </summary>
public class Article
{
/// <summary>
///文章ID
/// </summary>
private readonly int _articleId;
/// <summary>
///发布日期
/// </summary>
private readonly DateTime _publishedTime;
/// <summary>
///作者
/// </summary>
private string _author;
/// <summary>
///文章分类
/// </summary>
private Category _category;
/// <summary>
///文章分类ID
/// </summary>
private int _categoryId;
/// <summary>
///内容
/// </summary>
private string _content;
/// <summary>
///标题
/// </summary>
private string _title;
/// <summary>
///初始化文章
/// </summary>
protected Article(int categoryId)
{
_articleId = (int)Math.Abs(DateTime.Now.Ticks / 1000000000000);
_publishedTime = DateTime.Now;
_categoryId = categoryId;
}
/// <summary>
///反持久化初始化文章
/// </summary>
/// <param name="articleId">文章ID</param>
/// <param name="publishedTime">发布时间</param>
protected Article(int articleId, DateTime publishedTime)
{
_articleId = articleId;
_publishedTime = publishedTime;
}
/// <summary>
///标题
/// </summary>
public string Title
{
get => _title;
set => _title = value;
}
/// <summary>
///作者
/// </summary>
public string Author
{
get => _author;
set => _author = value;
}
/// <summary>
///内容
/// </summary>
public string Content
{
get => _content;
set => _content = value;
}
/// <summary>
///文章分类ID
/// </summary>
public int CategoryId
{
get => _categoryId;
protected internal set => _categoryId = value;
}
/// <summary>
///文章分类
/// </summary>
public virtual Category Category
{
get => _category;
protected internal set => _category = value;
}
/// <summary>
///文章ID
/// </summary>
public int ArticleId => _articleId;
/// <summary>
///发布日期
/// </summary>
public DateTime PublishedTime => _publishedTime;
/// <summary>
///创建文章
/// </summary>
/// <param name="repository">分类仓储</param>
/// <param name="categoryId">分类ID</param>
/// <returns></returns>
public static Article Create(ICategoryRepository repository, int categoryId)
{
return new Article(categoryId)
{
_category = repository.GetCategory(categoryId)
};
}
}
/// <summary>
///文章分类
/// </summary>
public class Category
{
/// <summary>
///文章分类ID
/// </summary>
private int _categoryId;
/// <summary>
///文章分类名称
/// </summary>
private string _name;
/// <summary>
///文章分类ID
/// </summary>
public int CategoryId
{
get => _categoryId;
set => _categoryId = value;
}
/// <summary>
///文章分类名称
/// </summary>
public string Name
{
get => _name;
set => _name = value;
}
}
/// <summary>
///文章分类仓储
/// </summary>
public interface ICategoryRepository
{
/// <summary>
///根据文章分类ID获取文章分类
/// </summary>
/// <param name="id">文章分类I</param>
/// <returns></returns>
Category GetCategory(int id);
}
模型配置
/// <summary>
///按需加载配置
/// </summary>
public class DemandLoadingConfiguration : 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 articleEntity = modelBuilder.Entity<Article>();
//配置主键
articleEntity.HasKeyAttribute(p => p.ArticleId).HasKeyIsSelfIncreased(false);
//使用定义的非公开构造函数进行反持久化 即可不需要为ArticleId PublishedTime设置访问器 因为在初始化对象时已经通过构造函数赋值
articleEntity.HasConstructor(typeof(Article).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic,
null, new[] { typeof(int), typeof(DateTime) }, null))
// 指示ArticleId PublishedTime已经在此构造函数内赋值 此处需要与反持久化构造函数参数顺序一致
.Map(p => p.ArticleId).Map(p => p.PublishedTime).End();
//为文章配置用于对象集(ObjectSet)的Create方法的实例构造函数
articleEntity.HasNewInstanceConstructor(typeof(Article).GetConstructor(
BindingFlags.Instance | BindingFlags.NonPublic,
null, new[] { typeof(int) }, null));
//配置文章分类实体型
var category = modelBuilder.Entity<Category>();
//配置主键
category.HasKeyAttribute(p => p.CategoryId).HasKeyIsSelfIncreased(true);
//配置文章和分类间的关联型
var articleCategoryAssociation = modelBuilder.Association<Article, Category>();
//配置关联端 End1一端为Article 映射为ArticleId->ArticleId
articleCategoryAssociation.AssociationEnd(p => p.End1).HasMapping("ArticleId", "ArticleId");
//End2一端为Category 映射为CategoryId->CategoryId
articleCategoryAssociation.AssociationEnd(p => p.End2).HasMapping("CategoryId", "CategoryId");
//关联表为Article
articleCategoryAssociation.ToTable("Article");
//配置关联引用 左端Article 即End1 右端Category 即End2 启用延迟加载 注意此处的Category需要为virtual
articleEntity.AssociationReference(p => p.Category).HasLeftEnd("End1").HasRightEnd("End2")
.HasEnableLazyLoading(true);
}
}