只读属性示例
场景
Article类用于描述一篇文章,它有5个属性: 标识符 、标题、作者、内容、发布时间。
其中,“标识符”和“发布时间”两个属性比较特殊,它们的值是由系统生成的。 首先看标识符。既然是标签符,那么显然它最重要的一个特征就是唯一性,也就是说,不同文章的标识应该是不一样的。为了达成这种唯一性,通常会在系统中建立一个标识符管理中心,每次发布一篇文章时都要到管理中心申清一个标识符,管理中心则采取一定的措施保证每次领发的标识符是不一样的。在程序实现层面,我们可以在构造函数中执行标识符申领:
public Article
{
_articleld = IdentifierCenter.Apply;
}
实践中通常采用数据库的ID生成制作为标识符管理中心,这时我们会将申请工作延迟到持久化阶段,即将文章插入到文章表后获取数据库为这条记录生成的标识值,这是实践上简便起见采用的妥协方案。但如果在不适于使用数据库作为标识符管理中心的场景下一一比如分布式数据库一一我们就需要专门建立一个标识符管理中心,在这种情况下我们推荐采用上述构造函数方案。再来看发布时间。我们汶里先明,"发布时间”的含义是文章上传至系统的时间。根据这一定义,显然生成发布时间的最佳实战是在构造函数中获取系统的当前时间:
public Article{
_publishedTime = DateTime.Now
}
这两个属性在文章的生命周期中只能生成一次,而且生成后就不能改变,类似于我们的身份证号码。因此,它们的属性访问器是只读的:
//标识Id
public int Articleld{
get
{
return _articleld;
}
}
//“发布时间”属性的访问顺
public DateTime PublishedTime{
get {
return _publishedTime;
}
}
这样问题就来了在反持久化的时候,如何给这两个属性赋值呢?种方案是利用反射分别给 artide和 publishedTime两字段赋值,但反射的性能较低,另一种 方案是硬着头皮给这两个属性增加写访问顺,但这会破坏面向对象的封装性原则,对于立志践行面向对象或领域驱动理念的开发团队来说,这是很难接受的。你有更好的方案吗?
解决方案
实体模型
/// <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 string _content;
/// <summary>
/// 标题
/// </summary>
private string _title;
/// <summary>
/// 初始化文章
/// </summary>
public Article()
{
_articleId = IdentifierCenter.Apply();
_publishedTime = DateTime.Now;
}
/// <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 ArticleId => _articleId;
/// <summary>
/// 发布日期
/// </summary>
public DateTime PublishedTime => _publishedTime;
}
/// <summary>
/// 标识中心
/// </summary>
public static class IdentifierCenter
{
/// <summary>
/// 使用Ticks毫秒数作为ID
/// </summary>
/// <returns></returns>
public static int Apply()
{
return (int)Math.Abs(DateTime.Now.Ticks / 1000000000000);
}
}
模型配置
/// <summary>
/// 只读属性配置
/// </summary>
public class ReadOnlyConfiguration : 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();
}
}