热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

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

在领域驱动设计(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

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的使用。希望它能够帮到你,欢迎把它分享到你所在的技术社区。

 

转 https://www.cnblogs.com/rupeng/p/16934316.html



推荐阅读
  • C++ 开发实战:实用技巧与经验分享
    C++ 开发实战:实用技巧与经验分享 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • iOS 设备唯一标识获取的高效解决方案与实践
    在iOS 7中,苹果公司再次禁止了对MAC地址的访问,使得开发者无法直接获取设备的物理地址。为了在开发过程中实现设备的唯一标识,苹果推荐使用Keychain服务来存储和管理唯一的标识符。此外,还可以结合其他技术手段,如UUID和广告标识符(IDFA),以确保设备的唯一性和安全性。这些方法不仅能够满足应用的需求,还能保护用户的隐私。 ... [详细]
  • 在开发过程中,我最初也依赖于功能全面但操作繁琐的集成开发环境(IDE),如Borland Delphi 和 Microsoft Visual Studio。然而,随着对高效开发的追求,我逐渐转向了更加轻量级和灵活的工具组合。通过 CLIfe,我构建了一个高度定制化的开发环境,不仅提高了代码编写效率,还简化了项目管理流程。这一配置结合了多种强大的命令行工具和插件,使我在日常开发中能够更加得心应手。 ... [详细]
  • 第六章:枚举类型与switch结构的应用分析
    第六章深入探讨了枚举类型与 `switch` 结构在编程中的应用。枚举类型(`enum`)是一种将一组相关常量组织在一起的数据类型,广泛存在于多种编程语言中。例如,在 Cocoa 框架中,处理文本对齐时常用 `NSTextAlignment` 枚举来表示不同的对齐方式。通过结合 `switch` 结构,可以更清晰、高效地实现基于枚举值的逻辑分支,提高代码的可读性和维护性。 ... [详细]
  • 在使用 SQL Server 时,连接故障是用户最常见的问题之一。通常,连接 SQL Server 的方法有两种:一种是通过 SQL Server 自带的客户端工具,例如 SQL Server Management Studio;另一种是通过第三方应用程序或开发工具进行连接。本文将详细分析导致连接故障的常见原因,并提供相应的解决策略,帮助用户有效排除连接问题。 ... [详细]
  • 当在 IntelliJ IDEA 中遇到“Sources not found for: XXX”问题时,可以通过命令行执行 `mvn dependency:resolve -Dclassifier=sources` 来解决源代码下载失败的问题。此方法能够有效确保项目依赖的源代码被正确获取,从而便于调试和代码阅读。更多详细信息可参考相关技术文档或官方指南。 ... [详细]
  • 在Kohana 3框架中,实现最优的即时消息显示方法是许多开发者关注的问题。本文将探讨如何高效、优雅地展示flash消息,包括最佳实践和技术细节,以提升用户体验和代码可维护性。 ... [详细]
  • 本文探讨了资源访问的学习路径与方法,旨在帮助学习者更高效地获取和利用各类资源。通过分析不同资源的特点和应用场景,提出了多种实用的学习策略和技术手段,为学习者提供了系统的指导和建议。 ... [详细]
  • 并发编程入门:初探多任务处理技术
    并发编程入门:探索多任务处理技术并发编程是指在单个处理器上高效地管理多个任务的执行过程。其核心在于通过合理分配和协调任务,提高系统的整体性能。主要应用场景包括:1) 将复杂任务分解为多个子任务,并分配给不同的线程,实现并行处理;2) 通过同步机制确保线程间协调一致,避免资源竞争和数据不一致问题。此外,理解并发编程还涉及锁机制、线程池和异步编程等关键技术。 ... [详细]
  • WebStorm 是一款强大的集成开发环境,支持多种现代 Web 开发技术,包括 Node.js、CoffeeScript、TypeScript、Dart、Jade、Sass、LESS 和 Stylus。它为开发者提供了丰富的功能和工具,帮助高效构建和调试复杂的 Node.js 应用程序。 ... [详细]
  • 安装Windows 10必须使用U盘吗?如果不使用U盘,还有哪些方法可以安装Windows 10?
    安装Windows 10必须使用U盘吗?如果不使用U盘,还有哪些方法可以安装Windows 10? ... [详细]
  • 如何高效地安装并配置 PostgreSQL 数据库系统?本文将详细介绍从下载到安装、配置环境变量、初始化数据库、以及优化性能的全过程,帮助读者快速掌握 PostgreSQL 的核心操作与最佳实践。文章还涵盖了常见问题的解决方案,确保用户在部署过程中能够顺利解决遇到的各种挑战。 ... [详细]
  • a16z深入解析:代币设计的常见误区、优化策略及未来趋势分析
    a16z深入解析:代币设计的常见误区、优化策略及未来趋势分析 ... [详细]
author-avatar
紫猫1_696_267
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有