天天看點

我開發的開源項目,讓.NET7中的EFCore更輕松地使用強類型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會增加代碼的複雜性,請謹慎使用"。幸好,這個世界有我!

我開發的開源項目,讓.NET7中的EFCore更輕松地使用強類型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兩個類。

我開發的開源項目,讓.NET7中的EFCore更輕松地使用強類型Id

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的使用。希望它能夠幫到你,歡迎把它分享到你所在的技術社群。

繼續閱讀