The open source project I developed makes it easier for EFCore in .NET7 to use strongly typed Id

In domain-driven design (DDD), there is a very important concept: "strongly typed Id". Using strong type Id as the type of identification attribute will bring more benefits than using general types such as int and Guid. For example, the signature of a method for deleting users based on Id is as follows:

void RemoveById(long id);

We can't see what the id means from the parameters of the method, so if we pass the id of the goods to this method by mistake, it's okay. In this way, using general types such as long to represent identification attributes will weaken business attributes such as parameters.

And if we customize a UserId type, as follows:

class UserId

{

public long Value{get;init;}

public UserId(long value)

{

    this.Value=value;

}

}

In this way, the type of the Id attribute in the definition of the User class changes from long to UserId type, as follows:

class User

{

   public UserId Id{get;}

   public string Name{get;set;}

}

The signature of the corresponding RemoveById method also becomes:

void RemoveById(UserId id);

In this way, not only can you see the business meaning represented by the id parameter at a glance, but also avoid the problem of "passing the value of the goods Id to the user Id parameter".

Before .NET 6, Entity Framework Core (EF Core for short) was difficult to implement strong type Id gracefully. In .NET7, EF Core provides support for strongly typed Id. For specific usage, please refer to "Value generation for DDD guarded types" in the official EF Core documentation.

Although EF Core has built-in support for strongly typed Id s, it requires programmers to write a lot of code. For example, a relatively complete code of the strongly typed Id class needs to write more than 30 lines of code as follows:

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);

            }

}

Also write a ValueConverter class and configure a custom ValueGenerator... The complexity of the code that needs to be written discourages developers who want to use strongly typed Id.

Because of this, even Microsoft's documentation warns that "strongly typed Id will increase the complexity of the code, please use it with caution". Fortunately, this world has me!

 

In order to solve this problem, I wrote an open source project based on .NET's SourceGenerator technology. This open source project will automatically generate related codes when compiling. Developers only need to mark a [HasStronglyTypedId] on the entity class.

project address: https://github.com/yangzhongke/LessCode.EFCore.StronglyTypedId

 

Below I use an example of writing all the code into a console project to demonstrate its usage. For more complex usages such as multi-project layering, please refer to the project documentation and the contents of the Examples folder in the project.

Note: The usage of this project may change with the upgrade, please refer to the latest official document for specific usage.

usage:

1. Create a new .NET7 console project, and then install the following Nuget packages in sequence: LessCode.EFCore, LessCode.EFCore.StronglyTypedIdCommons, LessCode.EFCore.StronglyTypedIdGenerator. Of course, our project needs to use SQLServer and the migration of EF core, so we need to install the following Nuget packages: Microsoft.EntityFrameworkCore.SqlServer, Microsoft.EntityFrameworkCore.Tools.

2. Create a new entity type Person in the project

[HasStronglyTypedId]

class Person

{

            public PersonId Id { get; set; }

            public string Name { get; set; }

}

 

We noticed [HasStronglyTypedId(typeof(Guid))] marked on Person, which means that this class enables strong type Id, and the compiler automatically generates a class named PersonId when compiling, so we declare a class named Id , an attribute of type PersonId to represent the identity of the entity.

The default PersonId saved in the database is long type. If you want to save it as Guid type, you can write it as [HasStronglyTypedId(typeof(Guid))].

Compile the project. If the compilation is successful, we decompile the generated dll, and we can see that two classes, PersonId and PersonIdValueConverter, are automatically generated in the dll.

 

3. Write DbContext, the code is as follows:

using LessCode.EFCore;

class TestDbContext:DbContext

{

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

            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

            {

                        optionsBuilder.UseSqlServer(own connection string);

            }

 

            protected override void OnModelCreating(ModelBuilder modelBuilder)

            {

                        base.OnModelCreating(modelBuilder);

                        modelBuilder.ConfigureStronglyTypedId();

            }

 

            protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)

            {

                        base.ConfigureConventions(configurationBuilder);

                        configurationBuilder.ConfigureStronglyTypedIdConventions(this);

            }

}

 

4. Perform database migration and other operations. This part belongs to the standard operation of EF Core, and I will not introduce it again. Friends who are not familiar with the usage of EF Core, please go to Bilibili, youtube and other platforms to search for "Yang Zhongke .NET Core Tutorial".

5. Write code to test

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);

}

Strongly typed Id allows us to better implement DDD in EFCore. This open source project allows developers to complete the use of strongly typed Id by marking a line [HasStronglyTypedId] on the entity class. Hope it can help you, welcome to share it with your technical community.

 

Transfer to https://www.cnblogs.com/rupeng/p/16934316.html

Tags: efcore

Posted by devioustree on Tue, 29 Nov 2022 13:57:05 +0530