在领域驱动设计(DDD)中,有一个非常重要的概念:“强类型Id”。使用强类型Id来做标识属性的类型会比用int、Guid等通用类型能带来更多的好处。比如有一个根据根据Id删除用户的方法的签名如下:

void RemoveById(long id);

我们从方法的参数看不出来id代表什么含义,因此如果我们错误地把货物的id传递给这个方法,那么也是可以的。这样用long等通用类型来表示标识属性会让参数等的业务属性弱化。

而如果我们自定义一个UserId类型,如下:

class UserId

{

public long Value{get;init;}

public UserId(long value)

{

    this.Value=value;

}

}

这样User类的定义中Id属性的类型就从long变成了UserId类型,如下:

class User

{

   public UserId Id{get;}

   public string Name{get;set;}

}

对应的RemoveById方法的签名也变成了:

void RemoveById(UserId id);

这样不仅能一看就看出来id参数代表的业务含义,也能避免“把货物Id的值传递给用户Id参数”这样的问题。

在.NET 6及之前,Entity Framework Core(简称EF Core)中很难优美地实现强类型Id。在.NET7中,EF Core中提供了对强类型Id的支持,具体用法请参考EF Core官方文档中“Value generation for DDD guarded types”这部分内容。

尽管EF Core已经内置了对强类型Id的支持,但是它需要程序员编写非常多的代码。比如一个比较完善的强类型Id类的代码就要编写如下30多行代码:

public readonly struct PersonId

{

            public Guid Value { get; }

            public PersonId(Guid value)

            {

                        Value = value;

            }

            public override string ToString()

            {

                        return Convert.ToString(Value);

            }

            public override int GetHashCode()

            {

                        return Value.GetHashCode();

            }

            public override bool Equals(object obj)

            {

                        if (obj is PersonId)

                        {

                                    PersonId objId = (PersonId)obj;

                                    return Value == objId.Value;

                        }

                        return base.Equals(obj);

            }

            public static bool operator ==(PersonId c1, PersonId c2)

            {

                        return c1.Equals(c2);

            }

            public static bool operator !=(PersonId c1, PersonId c2)

            {

                        return !c1.Equals(c2);

            }

}

还要编写一个ValueConverter类以及配置自定义的ValueGenerator……需要编写的代码的复杂程度让想使用强类型Id的开发者望而却步。

正因为这一点,所以连微软的文档中都​警告到"强类型Id会增加代码的复杂性,请谨慎使用"。幸好,这个世界有我!

为了解决这个问题,我基于.NET的SourceGenerator技术编写了一个开源项目,这个开源项目会在编译时自动生成相关的代码,开发人员只要在实体类上标注一个[HasStronglyTypedId]即可。

项目地址:https://github.com/yangzhongke/LessCode.EFCore.StronglyTypedId

下面我用一个把所有代码都写到一个控制台项目中的例子来演示它的用法,多项目分层等更复杂的用法请见项目文档以及项目中的Examples文件夹中的内容。

注意:这个项目可能会随着升级而用法有所变化,具体用法请以最新官方文档为准。

用法:

1、 新建一个.NET7控制台项目,然后依次安装如下这些Nuget包:LessCode.EFCore、LessCode.EFCore.StronglyTypedIdCommons、LessCode.EFCore.StronglyTypedIdGenerator。当然我们的项目要使用SQLServer以及使用EF core的migration,所以还要安装如下的Nuget包:Microsoft.EntityFrameworkCore.SqlServer、Microsoft.EntityFrameworkCore.Tools。

2、 项目中新建一个实体类型Person

[HasStronglyTypedId]

class Person

{

            public PersonId Id { get; set; }

            public string Name { get; set; }

}

我们注意到Person上标注的[HasStronglyTypedId(typeof(Guid))],它代表这个类启用强类型Id,编译器在编译的时候自动生成一个名字叫PersonId的类,所以我们就声明了一个名字叫Id、类型为PersonId的属性来表示实体的标识。

PersonId在数据库中保存的默认是long类型,如果想保存为Guid类型,就可以写成[HasStronglyTypedId(typeof(Guid))]。

编译一下项目,如果能够编译成功,我们反编译生成的dll,就能看到dll中自动生成了PersonId、PersonIdValueConverter两个类。

3、 编写DbContext,代码如下:

using LessCode.EFCore;

class TestDbContext:DbContext

{

            public DbSet<Person> Persons { get; set; }

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

            {

                        optionsBuilder.UseSqlServer(自己的连接字符串);

            }

            protected override void OnModelCreating(ModelBuilder modelBuilder)

            {

                        base.OnModelCreating(modelBuilder);

                        modelBuilder.ConfigureStronglyTypedId();

            }

            protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)

            {

                        base.ConfigureConventions(configurationBuilder);

                        configurationBuilder.ConfigureStronglyTypedIdConventions(this);

            }

}

4、 进行数据库的迁移等操作,这部分属于EF Core的标准操作,我不再介绍。对EF Core的用法不熟悉的朋友,请到哔哩哔哩、youtube等平台搜索“杨中科 .NET Core教程”。

5、 编写代码进行测试

using TestDbContext ctx = new TestDbContext();

Person p1 = new Person();

p1.Name = "yzk";

ctx.Persons.Add(p1);

ctx.SaveChanges();

PersonId pId1 = p1.Id;

Console.WriteLine(pId1);

Person? p2 = FindById(new PersonId(1));

Console.WriteLine(p2.Name); 

Person? FindById(PersonId pid)

{

    using TestDbContext ctx = new TestDbContext();

    return ctx.Persons.SingleOrDefault(p => p.Id == pid);

}

强类型Id让我们能够更好的在EFCore中实现DDD,我开源的这个项目能够让开发者只要在实体类上标注一行[HasStronglyTypedId]就可以完成强类型Id的使用。希望它能够帮到你,欢迎把它分享到你所在的技术社区。

我开发的开源项目,让.NET7中的EFCore更轻松地使用强类型Id的更多相关文章

  1. 使用GitHub进行协同项目开发和开源项目贡献

    本教程致力于摆脱git命令行快速的学习使用GitHub. 此次是GitHub课程的第三次课程,也是最后一次课程.推荐进行按照次序查看本次教程.上篇文章:程序员,一起玩转GitHub版本控制,超简单入门 ...

  2. 《云阅》一个仿网易云音乐UI,使用Gank.Io及豆瓣Api开发的开源项目

    CloudReader 一款基于网易云音乐UI,使用GankIo及豆瓣api开发的符合Google Material Desgin阅读类的开源项目.项目采取的是Retrofit + RxJava + ...

  3. iOS开发之开源项目链接

    1. Coding iOS 客户端 Coding官方客户端. 笔者强烈推荐的值得学习的完整APP.GitHub - Coding/Coding-iOS: Coding iOS 客户端源代码 2. OS ...

  4. 流媒体开发之开源项目live555---live555 server 编译 包括更改帧率大小

    由于要测试8148解码器的性能,需要搭建不同帧率25fps - >30fps,宏块大小defualt 100 000 -> 200 000不同大小的h264码流,所以就需要编译改动的liv ...

  5. 流媒体开发之开源项目live555---更改server端的帧率大小和码率大小

    -----------------------------qq:1327706646 010101010101010110010101010101010101010author:midu 010101 ...

  6. 一款属于自己的笔记本【vue+gin+elementUI前后端分离开发部署开源项目】

    前言 我为什么要写一个个人的云笔记? (⊙o⊙)-额额额

  7. 一劳永逸:域名支持通配符,ASP.NET Core中配置CORS更轻松

    ASP.NET Core 内置了对 CORS 的支持,使用很简单,只需先在 Startup 的 ConfigureServices() 中添加 CORS 策略: public void Configu ...

  8. .NET平台开源项目速览(4).NET文档生成工具ADB及使用

    很久以前就使用ADB这个工具来生成项目的帮助文档.功能强大,在学习一些开源项目的过程中,官方没有提供CHM帮助文档,所以为了快速的了解项目结构和注释.就生成文档来自己看,非常好用.这也是一个学习方法吧 ...

  9. 直接拿来用!最火的Android开源项目(完结篇)

    直接拿来用!最火的Android开源项目(完结篇) 2014-01-06 19:59 4785人阅读 评论(1) 收藏 举报 分类: android 高手进阶教程(100) 摘要:截至目前,在GitH ...

  10. 直接拿来用!最火的Android开源项目(完结篇)(转)

    摘要:截至目前,在GitHub“最受欢迎的开源项目”系列文章中我们已介绍了40个Android开源项目,对于如此众多的项目,你是Mark.和码友分享经验还是慨叹“活到老要学到老”?今天我们将继续介绍另 ...

随机推荐

  1. Mybatis框架搭建

    Mybatis框架搭建 思路: 搭建环境 导入Mybatis 编写代码 测试 一.搭建环境 创建数据库 /* Navicat Premium Data Transfer​ Source Server ...

  2. ProxySQL(4):多层配置系统

    文章转载自:https://www.cnblogs.com/f-ck-need-u/p/9280793.html ProxySQL中的库 使用ProxySQL的Admin管理接口连上ProxySQL, ...

  3. SNI 路由和多协议端口的 TCP

    文章转载自:https://mp.weixin.qq.com/s/nMMN7hAJK6SFn1V1YyxvHA 下面是一个简单的示例配置 - 使用最新支持的 YAML 文件格式,将请求路由到一个数据库 ...

  4. 天翼云上新增IP备案具体操作步骤

    0.点击右上角的备案,进入到备案中心 1.已备案信息管理 点击左侧的已备案信息管理,右侧出现的页面中找到已备案网站信息,网站负责人后面的操作里有5个图标,点击第三个(变更接入),提交订单,进入到下一步 ...

  5. MySQL集群搭建(5)-MHA高可用架构

    1 概述 1.1 MHA 简介 MHA - Master High Availability 是由 Perl 实现的一款高可用程序,出现故障时,MHA 以最小的停机时间(通常10-30秒)执行 mas ...

  6. MySQL数据表更新模板

    -- ---------------------------- -- 新增表 -- ---------------------------- CREATE TABLE `biz_circle_lead ...

  7. 16. 综合使用tail、forward、copy和stdout

    通过一个例子进行阶段总结. 本示例使用到如下插件:in_tail, out_copy, out_stdout, out_forward, in_forward. 本示例包含两个节点: node_for ...

  8. Ubuntu20.04和Docker环境下安装Redash中文版

    创建Ubunt20.04虚拟机,请参考:https://www.linuxidc.com/Linux/2020-03/162547.htm 一.安装基础环境: # 1.更换APT国内源 sudo se ...

  9. vscode展示子文件夹

    取消勾选设置-功能-compact Folders

  10. 华为设备配置ssh-client命令

    ssh client first-time enable 开启首次认证功能不对ssh服务器的RSA公钥进行有效性验证 stelnet 10.1.1.2 登陆R2 sys 进入到R2的系统视图 disp ...