在 EF Core 7 中实现强类型 ID

本文主要介绍 DDD 中的强类型 ID 的概念,及其在 EF 7 中的实现,以及使用 LessCode.EFCore.StronglyTypedId 这种更简易的上手方式。

背景

在杨中科老师 B 站的.Net Core 视频教程[1]其中 DDD 部分讲到了强类型 ID(Strongly-typed-id)的概念,也叫受保护的密钥(guarded keys)当时在 .NET 中的 DDD 实现是个悬而未决的问题,之后我也一直在寻找相关的实现方案。

非常高兴 .NET 7 的更新带来的 EF Core 7.0 的新增功能中,就包含了改进的值生成[2]这一部分,在自动生成关键属性的值方面进行了两项重大改进。

下面我们通过几个例子来了解这部分的内容,以及如何更简便的实现强类型。

强类型 ID

强类型 ID(Strongly-typed-id),又称之为受保护的键(guarded keys),它是领域驱动设计(DDD) 中的一项不可或缺的功能。

简单的来说,就是比如两个实体都是 int、long 或是 Guid 等类型的键值 ID,那么这就意味着它们 ID 就有可能在编码时被我们分配错误。再者一个函数如果同时传这两个 ID 作为参数,顺序传入错误,就意味着执行的结果出现问题。

在 DDD 的概念中,可以将实体的 ID 包装到另一种特定的类型中来避免。比如将 User 的 int 型 Id 包装为 UserId 类型,只用来它来表示 User 实体的 Id:

// 包装前
public class User
{public int Id { get; set; }
}// 以下是包装后
public class User
{public UserId Id { get; set; }
}

其优点非常明显:

•代码自解释,不需要多余的注释就可以看明白,提高程序的可读性•利用编译器提前避免不经意的编码错误,提高程序的安全性

当然上面的代码并不是具体实现的全部,需要其他更多的额外编码工作。也就是说其增加了代码的复杂性。DDD 中更多的是规范性设计,是为了预防缺陷的发生,让代码也变的更易懂了。具体是否要使用某一条规范,我们可以根据项目的具体情况进行权衡。

缺陷也总会有解决方案,集体的智慧是无穷,已经有很多技术大牛提供了更简便的方案,我们只需要站在巨人的肩膀上体验强类型 ID 带来的优点和便捷就可以了,文章也会介绍如何更简易的实现。

EF 中的使用演示

我们首次创建一个未使用强类型 ID 的 Demo,之后用不同方法实现强类型 ID 进行比较。项目都选择 .NET 7,数据库这里使用的是 MySql 。MySQL 中对 EF Core 7.0 的支持需要用到组件 Pomelo.EntityFrameworkCore.MySql ,当前需要其 alpha 版本。

1. 未使用强类型 ID

创建一个用于生成作者表的 Author 实体:

internal class Author
{public long Id { get; set; }public string Name { get; set; }public string Description { get; set; }
}

接下来创建一个用于生成图书表的 Book 实体:

internal class Book
{public Guid Id { get; set; }public string BookName { get; set; }public Author? Author { get; set; }public long AuthorId { get; set; }
}

然后创建对应的 DbContext

internal class TestDbContext : DbContext
{public DbSet<Book> Books { get; set; }public DbSet<Author> Authors { get; set; }protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){string connStr = "Server=localhost;database=test;uid=root;pwd=root;";var serverVersion = new MySqlServerVersion(new Version(8, 0, 27));optionsBuilder.UseMySql(connStr, serverVersion);optionsBuilder.LogTo(Console.WriteLine);}}

进行数据库迁移,我们可以发现其创建的数据库表情况如下:

58a7962688de275579f15eeac79f461b.png
数据库

然后在 Program.cs 中编写下列测试添加和查询的代码:

using ordinary;
using System;
using System.Text.Json;
using System.Text.Json.Serialization;TestDbContext ctx = new TestDbContext();var zack = new Author
{Name = "zack",Description = "mvp"
};ctx.Authors.Add(zack);ctx.SaveChanges();ctx.Books.Add(new Book {Author= zack,BookName = "ddd .net",
});ctx.SaveChanges();var list1 = ctx.Authors.ToArray();
var list2 = ctx.Books.ToArray();Console.WriteLine("\n\n--------------------- Author Table Info  -------------------------");Console.WriteLine(JsonSerializer.Serialize(list1));Console.WriteLine("\n\n--------------------- Book Table Info  -------------------------");Console.WriteLine(JsonSerializer.Serialize(list2));

其执行结果如下:

a02a6d81af6e3318ed6468c019f5cb2e.png
执行结果

2. 基础实现

接下来我们按照官网的说明对以上的代码进行改造,实现基本的强类型 ID。

我们按照说明先定义类型,对两个类进行改造。

internal class Book
{public BookId Id { get; set; }public string BookName { get; set; }public Author? Author { get; set; }public AuthorId AuthorId { get; set; }
}public readonly struct BookId
{public BookId(Guid value) => Value = value;public Guid Value { get; }
}
internal class Author
{public AuthorId Id { get; set; }public string Name { get; set; }public string Description { get; set; }}public readonly struct AuthorId
{public AuthorId(long value) => Value = value;public long Value { get; }
}

此时直接迁移肯定是会报错的:

The property 'Author.Id' could not be mapped because it is of type 'AuthorId', which is not a supported primitive type or a valid entity type. Either explicitly map this property, or ignore it using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
67303010c5e650766233507a2dab7beb.png
迁移报错

强类型 ID 在数据库里面的表示还是原始的类型,我们还需要在 DbContext 中通过为类型定义值转换器来实现转换:

protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{configurationBuilder.Properties<AuthorId>().HaveConversion<AuthorIdConverter>();configurationBuilder.Properties<BookId>().HaveConversion<BookIdConverter>();
}private class AuthorIdConverter : ValueConverter<AuthorId, long>
{public AuthorIdConverter(): base(v => v.Value, v => new(v)){}
}private class BookIdConverter : ValueConverter<BookId, Guid>
{public BookIdConverter(): base(v => v.Value, v => new(v)){}
}

接着还没结束,我们还需要 DbContext.OnModelCreating 中配置值转换的,否则迁移后你会发现 Author 的主键自增没有了,运行后的数据库 Guid 还全变成 0 了。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{modelBuilder.Entity<Author>().Property(author => author.Id).ValueGeneratedOnAdd();modelBuilder.Entity<Book>().Property(book => book.Id).ValueGeneratedOnAdd();
}

3. 使用 LessCode.EFCore.StronglyTypedId 简化

通过上一小节我们看到,虽然支持了强类型 ID ,但是要实现起来需要自行配置的东西还是非常多得,用的越多,额外代码的工作量也随之增长。虽然是在自己代码里 Ctrl CV 但是多执行几次也说不定会一个疏忽而出错。

因为在 GitHub Follow 了杨中科老师,所以在几天前发现了我们这位宝藏大男孩提供的新工具 LessCode.EFCore.StronglyTypedId,开源地址:https://github.com/yangzhongke/LessCode.EFCore.StronglyTypedId,这个项目基于 source generator 技术,可以帮你生成额外的代码,四舍五入约等于杨老师帮你把多余的代码写了。

根据说明文档开始新的改造,首先安装说需要的 Nuget 包,因为演示的 Demo 没有分层,是一把梭哈的,直接安装全部的包就可以了。分层的项目可以前往仓库查看分层的使用文档即可。

Install-Package LessCode.EFCore
Install-Package LessCode.EFCore.StronglyTypedIdGenerator

在改造上,只需要通过标识声明这个类存在一个强类型 ID 即可,默认标识类型是 long ,对于 Author 类,只需要直接添加 [HasStronglyTypedId] 即可:

[HasStronglyTypedId]
internal class Author
{public AuthorId Id { get; set; }public string Name { get; set; }public string Description { get; set; }
}

对 Book 类使用的 Guid 类型 ID,可以使用 HasStronglyTypedId 的构造函数来制定标识类型:

[HasStronglyTypedId(typeof(Guid))]
internal class Book
{public BookId Id { get; set; }public string BookName { get; set; }public Author? Author { get; set; }public AuthorId AuthorId { get; set; }
}

对于 DbContext 的修改,只需要做简单的配置即可,无需根据强类型 ID 的使用情况自行进行繁杂的转换和配置,这些将由 LessCode.EFCore 根据 [HasStronglyTypedId] 的标识进行处理。

protected override void OnModelCreating(ModelBuilder modelBuilder)
{base.OnModelCreating(modelBuilder);modelBuilder.ConfigureStronglyTypedId();
}protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{base.ConfigureConventions(configurationBuilder);configurationBuilder.ConfigureStronglyTypedIdConventions(this);
}

如此这般,可谓简便了不少。俗话说的好(我说的):轮子用的好,程序下班早。赶快去试起来吧!

最后

更多 LessCode.EFCore.StronglyTypedId 的介绍可前往: https://github.com/yangzhongke/LessCode.EFCore.StronglyTypedId。

文章相关 Demo 地址:https://github.com/sangyuxiaowu/StronglyTypedId

References

[1] .Net Core 视频教程: https://www.bilibili.com/video/BV1pK41137He/
[2] 改进的值生成: https://learn.microsoft.com/zh-cn/ef/core/what-is-new/ef-core-7.0/whatsnew#improved-value-generation

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/280961.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何快速打造一款高清又极速的短视频APP?

2019独角兽企业重金招聘Python工程师标准>>> 整个短视频的市场规模一直在增长&#xff0c;网络数据显示2018年已经突破100亿大关&#xff0c;在2019年预测将超过200亿。纵观行业&#xff0c;在生活资讯、美食、搞笑、游戏、美妆等领域&#xff0c;短视频流量巨大但竞…

Django03: django加入APP

使用命令在已有project创建 1.创建 在manage.py同级运行命令 python manage.py startapp app01 2.django中加入app 在settings.py里的INSTALLED_APPS加入app01.apps.App01Config, INSTALLED_APPS [django.contrib.admin,django.contrib.auth,django.contrib.contenttype…

如何将Windows 10帐户还原为本地帐户(在Windows Store劫持它之后)

If your Windows 10 user account is currently a Microsoft account (by your choice or because you got, one way or another, roped into it) it’s easy to revert it back to a local account if you know where to look. Read on as we show you how. 如果您的Windows 1…

【译】Dapr 是一个“10倍好”平台 !?

译者注在正式阅读本文之前&#xff0c;我们有必要先了解下什么是“10 倍好”。10 倍好理论最早出自彼得蒂尔的《从 0 到 1》&#xff0c;他说一个新创企业&#xff0c;要想获得快速成长&#xff0c;其提供的解决方案要比现有方案好 10 倍以上&#xff0c;这个好 10 倍&#xff…

1. ReactJS基础(开发环境搭建)

本文主要介绍通过React官方提供的create-react-app脚手架进行开发环境的搭建。 1.安装node环境(安装过程这里不做介绍&#xff0c;可参考其他博文) 在cmd中输入node -v 如果可以看到相应版本号&#xff0c;说明node环境安装成功 2.npm全局安装create-react-app脚手架 3.cmd命令…

“云计算+DevOps”的正确打开方式

以我们的经验看&#xff0c;技术和工具是很重要&#xff0c;但是技术和工具本身却不能产生价值&#xff0c;而将DevOps和云计算结合却可以。事实上&#xff0c;云计算的特性决定了&#xff0c;云计算和DevOps势必如影随形&#xff0c;而云计算与DevOps的结合也正在为企业用户提…

微服务和分布式系统中的授权解决方案

本文是 《精读 Mastering ABP Framework》 2.3 探索横切关注点 - 使用授权和权限系统 一节的扩充内容&#xff0c;重点探讨了授权在分布式和微服务系统中遇到的挑战&#xff0c;以及 ABP Framework 中采用的解决方案。认证 & 授权• 认证&#xff08;Authentication&#x…

如何从命令行浏览和连接到无线网络

() We are always on the lookout for geeky ways to impress our friends, and recently we came across a way to connect to our wireless network from the command prompt, so today we’ll show you how to do it as well. 我们一直在寻找令人印象深刻的方式来打动我们的…

html 基础之canvas 和 localStorage

1&#xff0c;建立一个canvas 画布&#xff1a; 1 <!DOCTYPE html>2 <html lang"en">3 <head>4 <meta charset"UTF-8">5 <meta name"viewport" content"widthdevice-width, initial-scale1.0">…

国产数据助力金融行业维护信息安全

金融信息系统作为国家关键信息基础设施&#xff0c;直接关系到国家经济、社会的正常运行。长期以来&#xff0c;我国金融信息化依赖进口设备和系统&#xff0c;金融行业尤其是银行业被IBM、HP、甲骨文等外商捆绑较深&#xff0c;金融行业信息化设备的软硬件系统被外商垄断。这等…

etcd v3 集群——简单配置

2019独角兽企业重金招聘Python工程师标准>>> 一、etcd v3安装&#xff1a; tar -axf etcd-v3.2.0-linux-amd64.tar.gz -C /usr/local/src/chmod ax /usr/local/src/etcd-v3.2.0-linux-amd64/etcd*cp -a /usr/local/src/etcd-v3.2.0-linux-amd64/etcd* /usr/local/bi…

windows变量延迟_Windows 10的2018年10月更新可能推迟到11月(这就是原因)

windows变量延迟Microsoft stopped offering Windows 10’s October 2018 Update on October 6, as it was deleting some people’s files. Now, another ugly data loss bug has reared its head, and it won’t be fixed until November. 微软于10月6日停止提供Windows 10的…

根据MediatR的Contract Messages自动生成Minimal WebApi接口

大家好&#xff0c;我是失业在家&#xff0c;正在找工作的博主Jerry。今天给大家介绍一个能大大减少ASP.Net Minimal WebApi编码量的方法。我们一般会把微服务的VO和DTO封装成消息类&#xff0c;并作为WebApi的Request和Response参数进行网络传递。如果使用MediatR&#xff0c;…

bupt summer training for 16 #8 ——字符串处理

https://vjudge.net/contest/175596#overview A.设第i次出现的位置左右端点分别为Li&#xff0c;Ri 初始化L0 0&#xff0c;则有ans sum{ (L[i] - L[i-1]) * (n 1 - Ri) } 1 #include <cstdio>2 #include <cstring>3 #include <iostream>4 #include <a…

程序员必须知道的HTML常用代码有哪些?

HTML即超文本标记语言&#xff0c;是目前应用最为广泛的语言之一&#xff0c;是组成一个网页的主要语言。在现今这个HTML5华丽丽地占领了整个互联网的时候&#xff0c;如果想要通过网页抓住浏览者的眼球光靠因循守旧是不行的&#xff0c;程序猿们需要掌握一些必须知道的HTML常用…

公用ip地址查询_是什么使您无法更改公用IP地址并在Internet上造成严重破坏?

公用ip地址查询What exactly is preventing you (or anyone else) from changing their IP address and causing all sorts of headaches for ISPs and other Internet users? 到底是什么在阻止您(或其他任何人)更改其IP地址并导致ISP和其他Internet用户感到头疼&#xff1f; …

Vim的新一代补全插件:coc.nvim

coc.nvim可以同时在nvim和vim8.1上使用。 安装 参考官方&#xff1a;Install coc.nvim 推荐使用vim-plug插件管理器&#xff0c;在vimrc中添加&#xff1a; Plug neoclide/coc.nvim, {do: { -> coc#util#install()}} 然后输入命令:PlugInstall 等待插件下载&#xff0c;再等…

C++STL——概述

一、相关介绍 STL 标准模板库在编写代码的过程中有一些程序经常会被用到&#xff0c;而且需求特别稳定&#xff0c;所以C中把这些常用的模板做了统一的规范&#xff0c;慢慢的就形成了STL提供三种类型的组件: 容器、迭代器和算法&#xff0c;它们都支持泛型程序设计标准容器 顺…

固态硬盘可靠性_您可以通过使用较少的总容量来提高硬盘的可靠性吗?

固态硬盘可靠性Your computer has a massive hard drive that you significantly underuse. Would decreasing the size of the primary partition actually increase the lifespan of the drive? 您的计算机具有大量未充分使用的巨大硬盘驱动器。 减小主分区的大小是否会真正…

接收上传的multi-file的文件(四)

构建工程 为例创建一个springmvc工程你需要spring-boot-starter-thymeleaf和 spring-boot-starter-web的起步依赖。为例能够上传文件在服务器&#xff0c;你需要在web.xml中加入标签做相关的配置&#xff0c;但在sringboot 工程中&#xff0c;它已经为你自动做了&#xff0c;所…