DDD中的EFCore
EFCore在DDD中的使用
在DDD中,我们对聚合根的操作都会通过仓储去获取聚合实例。
因为聚合根中可能会含有实体属性,值对象属性,并且,在DDD中,我们所设计的领域模型都是充血模型。所以,在对聚合根的持久化中,最方便的还是Mangodb这种KEY-VALUE存储的NOSQL。
不过,关系型数据库通过EF也能方便的解决复杂模型的数据库映射。
本文使用EFCore,部分API不适用于EF;本文不谈DDD。
以下引出几个知识点:
- backing field
 - releation
 - lazy load
 - data binding
 - navigation property
 - converter
 
让我们开始吧
我们首先定义一个复杂关系的 对象模型;
大致上描述下这个BookEntity根实体类的几个定义:
- 拥有只读的属性 
Name - 拥有两个对象属性
Author和Catalog - 枚举
EnumBookType类型属性Type - 拥有两个私有的列表字段 
_chapters和_keyWords 
简单映射
class BookEntity{
    private BookEntity(string name){
        Name = name;
    }
    public string Name { get; }
    public string BookCoverImage { get; private set; }
    public EnumBookType Type { get; private set; }
    //...
}
class BookEntityTypeConfiguration : IEntityTypeConfiguration<BookEntity>{
    public void Configure(EntityTypeBuilder<BookEntity> builder){
        builder.Property<string>("Id").HasColumnName("_id_")
            .HasValueGenerator<StringGuidValueGenerator>();
        builder.Property(x => x.Name);
        builder.Property(x => x.Type)
                .HasConversion<string>(k => k.ToString(), v => Enum.Parse<EnumBookType>(v));
    }
}
在上述代码中,我们定义了一个简单对象类及它的配置项。
BookEntity中未定义Id主键,我们通过 阴影属性 的方式指定了一个主键,并将它映射到db的_id_列;- EF中默认绑定 有 setter 方法的 public getter 属性,而我们的
Name没有setter方法,我们必须通过在配置中显示调用Property()将其加入到绑定中。 - EF中可以通过构造函数将字段绑定到实体上。
 - 可以能过调用 
HasConversion()方法显示指定使用的转换方法。比如将IDictionary<string,string>保存为string 
那么我们如何根据 主键 查询呢?
EF 为我们提供了静态方法EF.Property()
var entity = ctx.Set<BookEntity>().FirstOrDefault(x=>EF.Property<string>(x,"Id") == "1");
关系与固有类型
在官方文档中,关系主要使用以下几种方法来配置的。
HasOne()HasMany()WithOne()WithMany()
而 OwnsType (固有类型)是新近推出的API。
OwnsOne()OwnsMany()
虽然都会创建导航属性,但是从定义和使用上来看
,还是有很大区别的。
经过测试,导航属性不能通过构造函数绑定,所以以下配置中,均使用 private setter 。
(如果有读者发现错误,欢迎指正。)
下面我们就对两种API进行配置。
OwnsType的配置
从使用上的角度上来看,OwnsType像其名字一样,强调的是A拥有B,这个属性是这个类固有的,没有懒加载的配置。
扩展我们之前写义的实体类。
class BookEntity{
    //...略
    public AuthorInfo Author { get; private set; }
    private List<KeyWordInfo> _keyWords = new List<KeyWordInfo>();
    public IEnumerable<KeyWordInfo> KeyWords => _keyWords;
}
class AuthorInfo
{
    public AuthorInfo(string name){
        Name = name;
    }
    public string Name { get; }
}
class KeyWordInfo
{
    public KeyWordInfo(string word){
        Word = word;
    }
    public string Word { get; }
}
扩展配置类
class BookEntityTypeConfiguration : IEntityTypeConfiguration<BookEntity>{
    builder.OwnsOne(x => x.Author, b => {
        b.Property(v => v.Name).HasColumnName("AuthorName");
    });
    builder.OwnsMany(x => x.KeyWords, b =>
    {
        b.ToTable("BookKeyWords");
        b.Property<int>("Id").HasColumnName("_id_");
        b.HasKey("Id");
        b.Property(x => x.Word);
        b.HasForeignKey("BookId");
    });
    builder.Metadata.FindNavigation(nameof(BookEntity.KeyWords))
        .SetPropertyAccessMode(PropertyAccessMode.Field);
}
默认情况下,OwnsOne()会与实体映射在同一张表,OwnsMany()没有做具体测试。
这里我们对导航属性KeyWords进行了配置,因为它是只读的,所以我们将它配置为绑定为字段,这个私有字段叫做backing field(支持字段??),在EF中默认有以下4种格式,当然这是支持自定义的:
- _< camel-cased property name >
 - _< property name >
 - m_< camel-cased property name >
 - m_< property name >
 
那什么是
backing field???
ReleationShip 配置
如HasOne()这种关系API,更适合于A与B之前的关系,比如 1-* (一对多)的关系、1-1(一对一)的关系等等,所以这种配置必须在不同表中。
class BookEntity{
    //...略
    private IList<BookChapterEntity> _chapters = new List<BookChapterEntity>();
    public IEnumerable<BookChapterEntity> Chapters => _chapters;
}
class BookEntityTypeConfiguration : IEntityTypeConfiguration<BookEntity>
{
    public void Configure(EntityTypeBuilder<BookEntity> builder)
    {
        //...略
        builder.HasMany(x => x.Chapters).WithOne().HasForeignKey("BookId");
        builder.Metadata.FindNavigation(nameof(BookEntity.Chapters))
            .SetPropertyAccessMode(PropertyAccessMode.Field);
    }
}
class BookChapterEntityTypeConfiguration : IEntityTypeConfiguration<BookChapterEntity>
{
    public void Configure(EntityTypeBuilder<BookChapterEntity> builder)
    {
        builder.Property(x => x.Title);
        builder.Property(x => x.Index);
        builder.Property<string>("Id");
    }
}
从配置上来看,我们的两个实体都是分开配置的,而从实体类角度上看,这里是两个类体的关系,我们配置的是 1-的关系。
使用HasOne()等ReleationShip* Api配置的属性,默认是不加载的,我们可以通过配置进行立即加载或延迟加载。
我们可以查看官方文档查看懒加载的方式。
https://docs.microsoft.com/en-us/ef/core/querying/related-data
backing field (支持字段)
我们都知道C#中有这样子的写法。
class Foo{
    public string Name{get;set;}
}
写完整了是这样的。
class Foo{
    private string _name;
    public string Name{
        get{return _name;}
        set{_name = value;}
    }
}
而在其它语言中,可能是这样的。
class Foo{
    private string _name;
    public string GetName(){
        return _name;
    }
    public void SetName(value){
        _name = value;
    }
}
我认为以上的_name就是一个backing field; 以字面意思解释就是属性底层的字段。
查询过滤器 Query Filter
我们公司的业务设计上,数据不能真删,通过一个 IsDeleted 字段进行控制。这样在有必要的情况下,我们可以将数据进行还原。
class BookEntityTypeConfiguration : IEntityTypeConfiguration<BookEntity>
{
    public void Configure(EntityTypeBuilder<BookEntity> builder)
    {
        //...略
        builder.Property<bool>("IsDeleted");
        builder.HasQueryFilter(x=>!EF.Property<bool>(x,"IsDeleted"));
    }
}
我们通过HasQueryFilter()配置一个全局过滤器。
什么?你说又要查询的时候要查询IsDeleted == true的数据??
var allBooks = ctx.Set<BookEntity>()
    .IgnoreQueryFilters()
    .ToList();
//通过 IgnoreQueryFilters 忽视掉全局过滤器;
更多的查看官方文档
结尾总结
在我们项目切换到DDD模式下开发的时候,使用关系型数据库作为仓储的实现真是头疼。还好,我们有EF,但是如果对EF的API和映射不熟悉的话,会导致出现因技术原因修改领域模型的情况,而这种情况是我们应该避免的。
如发现文中有误,欢迎指正。
DDD中的EFCore的更多相关文章
- EFCore:关于DDD中值对象(Owns)无法更新数值
		
最近使用DDD+EFCore时,使用EFCore提供的OwnsOne或者OwnsMany关联值对象保存数据,没想到遇到一个很奇怪的问题:值对象中的值竟然无法被EFCore保存!也没有抛出任何异常!我瞬 ...
 - 初探领域驱动设计(2)Repository在DDD中的应用
		
概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值类型和领域服务,也稍微讲到了DDD中的分层结构.但这只能算是一个很简单的介绍,并且我们在上篇的末尾还留下了一些问题,其中大家讨论比较多的, ...
 - DDD~Unity在DDD中的使用
		
回到目录 上一讲介绍了DDD中的领域层,并提到下次要讲Unity,所以这篇文章当然就要介绍它了,呵呵,Unity是Microsoft.Practices中的一部分,主要实现了依赖注入的功能,或者叫它控 ...
 - Repository在DDD中的应用
		
Repository在DDD中的应用2014-10-09 08:55 by Jesse Liu, 98 阅读, 0 评论, 收藏, 编辑 概述 上一篇我们算是粗略的介绍了一下DDD,我们提到了实体.值 ...
 - DDD中的分层架构
		
DDD中的分层架构很好的应用了关注点分离原则Separation of Concerns(SOC),每一层做好自己的事情,减少交叉 表现层 表现层提供用来完成任务的用户界面,如webform wpf ...
 - 对DDD中领域服务的理解
		
CZ 能不能清晰具体区分service和实体的区别 网上有人用DCI来解决 不知道对不对 STST 我复习下DDD中的服务的概念了参与讨论啊CZ 这个我也看过 但是太过于笼统 STST STST 复习 ...
 - DDD中的聚合和UML中的聚合以及组合的关系
		
UML:聚合关系:成员对象是整体的一部分,但是成员对象可以脱离整体对象独立存在.如汽车(Car)与引擎(Engine).轮胎(Wheel).车灯(Light)之间的关系为聚合关系,引擎.轮胎.车灯可以 ...
 - DDD中的值对象如何用NHibernate进行映射
		
原文:DDD中的值对象如何用NHibernate进行映射 <component/>是NHibernate中一个有趣的特性,即是用来映射DDD(Data-Display-Debuger)概念 ...
 - Java实现DDD中UnitOfWork
		
Java实现DDD中UnitOfWork 背景 Maintains a list of objects affected by a business transaction and coordinat ...
 
随机推荐
- 【项目总结】扯一扯电商网站前端css的整体架构设计(1)
			
最近半忙不忙的写了一个外包网站,网站主要功能是艺术品竞拍和艺术衍生品的销售.工程已经完成了80%左右,现在前后端代码量已经50W行左右,我主要负责的是前端设计和前端布局.下面就先放一个网站的设计图吧, ...
 - 微信开发之c#下获取jssdk的access_token
			
获取access_token是调用微信JS接口的基础,只有得到了它才能得到我们需要的jsapi_ticket并生成签名,然后注入配置信息config. 微信官方文档我就不多做介绍,反正我是踩了不少坑. ...
 - 【maven】---pom.xml-dependencies
			
前言 Maven在做项目开发的时候,需要用到多个jar包,这个时候,maven提供的中央仓库,就会根据依赖项,自动download需要的jar包,自动引用到项目中. 内容 具体依赖配置,集中在pom. ...
 - 解决winform datagridview的ClearSelection无效问题
			
因为把方法放在了界面的构造方法里,此时datagridview还没绘制出来,所以ClearSelection方法无效,放在control或form的load方法里就没问题了 参考:https://ww ...
 - zTree API中刷新树没效果
			
想刷新树,但是根据API来的refresh无效 ---------------------------------------------------------------------------- ...
 - SQL语句insert into 不存在则插入,存在则修改
			
一 测试表的创建 -- ---------------------------- -- Table structure for User -- ---------------------------- ...
 - Python导入模块Import和from+Import区别
			
在我们使用python的时候会发现使用Import可以导入模块,from+Import也可以,那么他们之间有什么区别,该用哪一种呢?让我们来看看 1.首先在demo.py中创建一个变量a,定义一个函数 ...
 - Flink学习笔记:Flink Runtime
			
本文为<Flink大数据项目实战>学习笔记,想通过视频系统学习Flink这个最火爆的大数据计算框架的同学,推荐学习课程: Flink大数据项目实战:http://t.cn/EJtKhaz ...
 - python 常用模块大全
			
1.getpass 模块 一般用于获取用户输入的密码 import getpass pwd = getpass.getpass('input your pass') print(pwd) print ...
 - 2019.4.3 HTML&CSS 理论部分
			
HTML 块标签 1.独占一行,不和其他标签待在一行; 2.能设置宽高 常见的块标签:h1-h6,p,div,table,hr,br,ul,ol, 行标签 1.可以和其他行标签待在一行 2.不能设置宽 ...