不需要iis的网站开发,青海省住房和城乡建设厅的官方网站,庆元县建设局网站,你的网站尚未进行备案使用 Source Generator 代替 T4 动态生成代码Intro在 Source Generator 出现之前有一些重复性的代码#xff0c;我会使用 T4 去生成#xff0c;这样就可以一定程度上避免复制粘贴和可维护性也会更好一些。在了解了一些 Source Generator 之后#xff0c;就想尝试把现在项目里… 使用 Source Generator 代替 T4 动态生成代码Intro在 Source Generator 出现之前有一些重复性的代码我会使用 T4 去生成这样就可以一定程度上避免复制粘贴和可维护性也会更好一些。在了解了一些 Source Generator 之后就想尝试把现在项目里的一些 T4 换成 Source Generator 来实现大部分场景应该都是没有问题的可以直接用 Source Generator 替换而且 Source Generator 可以根据编译信息动态的去生成更加的智能和自动化。接着来看一下我是如何使用 Source Generator 来代替 T4 生成代码的吧Before首先来看一下修改之前的项目情况项目结构是这样的原来在 Business 项目里有一个 T4 模板定义如下# template debugfalse hostSpecifictrue languageC# #
# output extension.generated.cs encodingutf-8 #
# Assembly NameSystem.Core #
# import namespaceSystem #
# import namespaceSystem.Collections #
#string[] types {BlockType,BlockEntity,OperationLog,Reservation,ReservationPlace,ReservationPeriod,SystemSettings,Notice,DisabledPeriod};
#
using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{
# foreach (var item in types){
#public partial interface IBLL# item #: IEFRepositoryReservationDbContext, # item #{}public partial class BLL# item # : EFRepositoryReservationDbContext, # item #, IBLL# item #{public BLL# item #(ReservationDbContext dbContext) : base(dbContext){}}
# }
#
}
模板比较简单动态生成的代码如下using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{public partial interface IBLLBlockType: IEFRepositoryReservationDbContext, BlockType{}public partial class BLLBlockType : EFRepositoryReservationDbContext, BlockType, IBLLBlockType{public BLLBlockType(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLBlockEntity: IEFRepositoryReservationDbContext, BlockEntity{}public partial class BLLBlockEntity : EFRepositoryReservationDbContext, BlockEntity, IBLLBlockEntity{public BLLBlockEntity(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLOperationLog: IEFRepositoryReservationDbContext, OperationLog{}public partial class BLLOperationLog : EFRepositoryReservationDbContext, OperationLog, IBLLOperationLog{public BLLOperationLog(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLReservation: IEFRepositoryReservationDbContext, Reservation{}public partial class BLLReservation : EFRepositoryReservationDbContext, Reservation, IBLLReservation{public BLLReservation(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLReservationPlace: IEFRepositoryReservationDbContext, ReservationPlace{}public partial class BLLReservationPlace : EFRepositoryReservationDbContext, ReservationPlace, IBLLReservationPlace{public BLLReservationPlace(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLReservationPeriod: IEFRepositoryReservationDbContext, ReservationPeriod{}public partial class BLLReservationPeriod : EFRepositoryReservationDbContext, ReservationPeriod, IBLLReservationPeriod{public BLLReservationPeriod(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLSystemSettings: IEFRepositoryReservationDbContext, SystemSettings{}public partial class BLLSystemSettings : EFRepositoryReservationDbContext, SystemSettings, IBLLSystemSettings{public BLLSystemSettings(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLNotice: IEFRepositoryReservationDbContext, Notice{}public partial class BLLNotice : EFRepositoryReservationDbContext, Notice, IBLLNotice{public BLLNotice(ReservationDbContext dbContext) : base(dbContext){}}public partial interface IBLLDisabledPeriod: IEFRepositoryReservationDbContext, DisabledPeriod{}public partial class BLLDisabledPeriod : EFRepositoryReservationDbContext, DisabledPeriod, IBLLDisabledPeriod{public BLLDisabledPeriod(ReservationDbContext dbContext) : base(dbContext){}}
}
我是在开发时动态生成的听大师说也可以改成在编译的时候进行生成不过我没去尝试过有兴趣的可以了解一下 https://docs.microsoft.com/en-us/visualstudio/modeling/run-time-text-generation-with-t4-text-templatesAfter使用 Source Generator 分成了两步第一步还是比较手动的保留了上面的 types 数组第二步则是自动的根据编译的信息动态的获取 types 数组首先我们要确定哪个项目是要动态生成代码的项目哪个项目是要写 Source Generator 的项目原来我们用 T4 生成代码的项目(Business)就是我们要动态生成代码的项目也就是这个项目应该是引用 Source Generator 的项目那我们 Source Generator 应该要放在哪个项目里呢理论上来说要生成代码的项目哪一个都是可以的新建一个项目也是可以的Business 直接依赖于 Database 项目所以我选择了 Database 项目来实现 Source GeneratorUpdate1首先我们需要配置 Source Generator 环境首先为我们要写 Generator 的项目增加对 Microsoft.CodeAnalysis.CSharp 的引用PackageReference IncludeMicrosoft.CodeAnalysis.CSharp Version3.9.0 /
因为 Source Generator 有外部依赖所以需要声明依赖项和上一篇文章类似在项目文件中增加下面的配置PropertyGroupGetTargetPathDependsOn;GetDependencyTargetPaths/GetTargetPathDependsOn
/PropertyGroup
ItemGroupPackageReference IncludeMicrosoft.CodeAnalysis.CSharp Version3.9.0 /
/ItemGroup
ItemGroupPackageReference IncludeWeihanLi.EntityFramework Version2.0.0-preview-* GeneratePathPropertytrue /
/ItemGroup
Target NameGetDependencyTargetPathsItemGroupTargetPathWithTargetPlatformMoniker Include$(PKGWeihanLi_EntityFramework)\lib\netstandard2.1\WeihanLi.EntityFramework.dll IncludeRuntimeDependencyfalse //ItemGroup
/Target
然后要动态生成代码的项目也需要配置一下只需要修改项目文件原来的 T4 模板可以删掉了可以参考下面的配置PropertyGroupEmitCompilerGeneratedFilestrue/EmitCompilerGeneratedFiles
/PropertyGroup
ItemGroupProjectReference Include..\OpenReservation.Database\OpenReservation.Database.csprojOutputItemTypeAnalyzer /
/ItemGroup
在 ProjectReference 中声明 OutputItemTypeAnalyzer 以使用 Generator 的功能通过配置 EmitCompilerGeneratedFiles 以生成动态代码帮助我们调试之后就开始写我们的 Generator 了最终代码如下[Generator]
public class ServiceGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context){}public void Execute(GeneratorExecutionContext context){var types new[]{BlockType,BlockEntity,OperationLog,Reservation,ReservationPlace,ReservationPeriod,SystemSettings,Notice,DisabledPeriod};var codeBuilder new StringBuilder();codeBuilder.AppendLine(
using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{);foreach (var item in types){codeBuilder.AppendLine($public partial interface IBLL{item}: IEFRepositoryReservationDbContext, {item}{{}}public partial class BLL{item} : EFRepositoryReservationDbContext, {item}, IBLL{item}{{public BLL{item}(ReservationDbContext dbContext) : base(dbContext){{}}}});}codeBuilder.AppendLine(});var codeText codeBuilder.ToString();context.AddSource(nameof(ServiceGenerator), codeText);}
}
此时我们的 Generator 已经可以工作了生成的代码和上面的完全一样而且生成的代码可以不需要保存在代码库里了编译的时候会动态生成已经完全可以取代 T4 了详细修改可以参考这个 Commithttps://github.com/OpenReservation/ReservationServer/commit/8a723ba652a10fb393e90bf70923631f58294da8Update2接着上面的修改虽然已经代替了 T4但是似乎并不能够体现出 Source Generator 的优势啊于是就想再改一版利用编译信息自动的获取上面的 types 数组因为 types 不是随便写的是 model 的名字所以从编译信息中获取理论上来说是可以做到的于是有了第二版的实现实现代码如下[Generator]
public class ServiceGenerator : ISourceGenerator
{public void Initialize(GeneratorInitializationContext context){// Debugger.Launch();}public void Execute(GeneratorExecutionContext context){// 从编译信息中获取 DbSet 类型var dbContextType context.Compilation.GetTypeByMetadataName(typeof(DbSet).FullName);// 从编译信息中获取 ReservationDbContext 类型var reservationDbContextType context.Compilation.GetTypeByMetadataName(typeof(ReservationDbContext).FullName);// 获取 ReservationDbContext 中的 DbSet 属性var propertySymbols reservationDbContextType.GetMembers().OfTypeIMethodSymbol().Where(x x.IsVirtual x.MethodKind MethodKind.PropertyGet x.ReturnType is INamedTypeSymbol{IsGenericType: true,IsUnboundGenericType: false,} typeSymbol ReferenceEquals(typeSymbol.ConstructedFrom.ContainingAssembly, dbContextType.ContainingAssembly)).ToArray();// 获取属性的返回值var propertyReturnType propertySymbols.Select(r ((INamedTypeSymbol)r.ReturnType)).ToArray();// 获取属性泛型类型参数并获取泛型类型参数的名称var modelTypeNames propertyReturnType.Select(t t.TypeArguments).SelectMany(x x).Select(x x.Name).ToArray();var codeBuilder new StringBuilder();codeBuilder.AppendLine(
using OpenReservation.Database;
using OpenReservation.Models;
using WeihanLi.EntityFramework;namespace OpenReservation.Business
{);foreach (var item in modelTypeNames){codeBuilder.AppendLine($
public partial interface IBLL{item}: IEFRepositoryReservationDbContext, {item}{{}}public partial class BLL{item} : EFRepositoryReservationDbContext, {item}, IBLL{item}
{{public BLL{item}(ReservationDbContext dbContext) : base(dbContext){{}}
}});}codeBuilder.AppendLine(});var codeText codeBuilder.ToString();// 添加要动态生成的代码context.AddSource(nameof(ServiceGenerator), codeText);}
}
除了上面 Generator 的修改之外还需要增加 EFCore 依赖项这也是目前使用 SourceGenerator 的一个痛点我的 EF 扩展 WeihanLi.EntityFramework 已经依赖了 EFCore 但还是需要再声明一下声明方式和前面类似 ItemGroupPackageReference IncludeWeihanLi.EntityFramework Version2.0.0-preview-* GeneratePathPropertytrue /PackageReference IncludeMicrosoft.EntityFrameworkCore Version5.0.5 GeneratePathPropertytrue //ItemGroupTarget NameGetDependencyTargetPathsItemGroupTargetPathWithTargetPlatformMoniker Include$(PKGWeihanLi_EntityFramework)\lib\netstandard2.1\WeihanLi.EntityFramework.dll IncludeRuntimeDependencyfalse /TargetPathWithTargetPlatformMoniker Include$(PKGMicrosoft_EntityFrameworkCore)\lib\netstandard2.1\Microsoft.EntityFrameworkCore.dll IncludeRuntimeDependencyfalse //ItemGroup/Target
这样我们就可以通过 Source Generator 动态的自动生成 service 代码了以后新加表只需要在 ReservationDbContext 中加入新的表就可以了编译器也会自动生成新加表的服务类不需要再手动配置 types 数组了舒服~~More通过上面的示例再次戳到了痛点希望后面的版本更新中能够有所优化也希望 VS 能够提供更有力的支持。以上就是所有内容了希望能够对你有所帮助上面的示例代码可以从 https://github.com/OpenReservation/ReservationServer 进行获取ReferencesC# 强大的新特性 Source Generatorhttps://docs.microsoft.com/en-us/visualstudio/modeling/design-time-code-generation-by-using-t4-text-templates?viewvs-2019https://docs.microsoft.com/en-us/visualstudio/modeling/run-time-text-generation-with-t4-text-templates?viewvs-2019https://github.com/OpenReservation/ReservationServer/tree/9d2e0987d12143d297d4233bc37c06785bfa0cff/OpenReservation.Businesshttps://github.com/OpenReservation/ReservationServer/commit/8a723ba652a10fb393e90bf70923631f58294da8https://github.com/OpenReservation/ReservationServer/blob/dev/OpenReservation.Database/ServiceGenerator.cshttps://github.com/OpenReservation/ReservationServer