实体解析

实体解析是工具的最重要的功能之一,它可以解析用户的实体,提取出实体的属性和关系。本篇文档将详细说明实体解析相关的内容,你将了解

  • 使用Roslyn解析实体类
  • 使用EntityFrameworkCore.Design解析DbContext和实体
  • 解析结果结构:EntityInfo

使用Roslyn解析实体类

实体解析使用了Roslyn来解析C#代码中的实体类。通过分析代码中的类定义、属性和方法,工具可以提取出实体的结构信息。

主要的解析逻辑在EntityParseHelper中实现.

使用EntityFrameworkCore.Design解析DbContext和实体

这种方案在实现上稍微有些复杂,但理论上能够获取关于数据库上下文和实体的各种信息,以增加后续代码生成的智能化。

具体步骤:

  1. 使用Roslyn加载目标项目的程序集,如EntityFramework
  2. 先找到abstract的继承DbContext的基类,如ContextBase
  3. 从程序集中获取所有继承自基类如ContextBase的非abstract的数据库上下文。
  4. 创建上下文实例,获取DbContextModel属性获取实体模型,返回一个字典。
  5. 使用时,根据实体获取到对应的上下文,然后查询字典,获取Model属性,从而获取实体的相关信息。

涉及到的实现类

  • DbContextAnalysisHelper.cs,使用Roslyn解析目标程序集,获取DbContext信息
  • DbContextAnalyzer.cs,创建DbContext实例,提供FrozenDictionary<string, IModel>类型的字典
  • DbContextParseHelper,根据字典来获取对应实体的信息,提供EntityInfo

这里的核心是如何创建目标程序集的DbContext实例,我们既没有通过启用Host,从而获取依赖注入的服务,也没有使用IDesignTimeDbContextFactory(目标程序可能不提供,提供会影响ef工具行为),而是直接使用Activator.CreateInstance来创建实例。

这里的核心在于如何构建DbContext所需要的参数DbContextOptionsBuilder。可以直接使用工具中的Sqlite相关程序集,来构建该参数, 我们的目的是获取实体模型信息,并不需要使用跟目标程序一致的数据库提供程序。你会看到以下代码:

// use tool's sqlite assembly
 var sqliteAssembly = Assembly.Load("Microsoft.EntityFrameworkCore.Sqlite");
 var sqliteExtensionsType = sqliteAssembly.GetType(
     "Microsoft.EntityFrameworkCore.SqliteDbContextOptionsBuilderExtensions"
 );
 if (sqliteExtensionsType != null)
 {
     var useSqliteMethod = sqliteExtensionsType.GetMethod(
         "UseSqlite",
         [optionsBuilderType, typeof(string), typeof(Action<object>)]
     );
     useSqliteMethod?.Invoke(null, [optionsBuilder, "DataSource=temp", null]);
 }
 var options = optionsBuilder.Options;
 dbContextInstance = Activator.CreateInstance(contextType, options) as DbContext;

解析说明

使用Roslyn进行语义和语法解析,是对源代码内容进行解析,能够获取到用户代码的原始表达。

使用EntityFrameworkCore.Design解析DbContext和实体,本质上是对编译后的程序集进行反射,获取到编译后的实体信息,但一些原始的代码信息可能无法获取,如:

public UserType UserType { get; set; } = UserType.Normal;

其中的= UserType.Normal;无法通过反射获取到。

编译会去除一些内容,如注释,条件编译等信息。

通过结合RoslynEntityFrameworkCore.Design,我们可以获取到更全面的实体信息。

EntityInfo

EntityInfo是实体解析后的数据结构,它包含了实体的各类信息,如以下所示:

public string? ModuleName { get; set; }
 /// <summary>
 /// the db context name
 /// </summary>
 public string? DbContextName { get; set; }
 public string? DbContextSpaceName { get; set; }
 /// <summary>
 /// 类名
 /// </summary>
 [MaxLength(100)]
 public required string Name { get; set; }
 /// <summary>
 /// 命名空间
 /// </summary>
 [MaxLength(100)]
 public required string NamespaceName { get; set; }
 /// <summary>
 /// 程序集名称
 /// </summary>
 [MaxLength(100)]
 public string? AssemblyName { get; set; }
 /// <summary>
 /// 类注释
 /// </summary>
 [MaxLength(300)]
 public string? Comment { get; set; }
 /// <summary>
 /// 类注释
 /// </summary>
 [MaxLength(100)]
 public string? Summary { get; set; }
 public List<PropertyInfo> PropertyInfos { get; set; } = [];
 /// <summary>
 /// 导航属性
 /// </summary>
 public List<EntityNavigation> Navigations { get; set; } = [];

其中PropertyInfo是实体包含的属性信息,EntityNavigation是实体的导航属性信息。

PropertyInfo

PropertyInfo是实体属性的详细信息,主要提供代码生成时的各类无数据。

/// <summary>
/// 类型
/// </summary>
[MaxLength(100)]
public required string Type { get; set; }
/// <summary>
/// 名称
/// </summary>
[MaxLength(100)]
public required string Name { get; set; }
[MaxLength(100)]
public string? DisplayName { get; set; }
/// <summary>
/// 是否是数组
/// </summary>
public bool IsList { get; set; }
public bool IsPublic { get; set; } = true;
public bool IsForeignKey { get; set; }
/// <summary>
/// 是否为导航属性
/// </summary>
public bool IsNavigation { get; set; }
public bool IsJsonIgnore { get; set; }
/// <summary>
/// 导航属性类名称
/// </summary>
[MaxLength(100)]
public string? NavigationName { get; set; }
public bool IsComplexType { get; set; }
/// <summary>
/// 导航属性的对应关系
/// </summary>
public bool? HasMany { get; set; }
public bool IsEnum { get; set; }
/// <summary>
/// 是否包括set方法
/// </summary>
public bool HasSet { get; set; } = true;
[MaxLength(100)]
public string? AttributeText { get; set; }
/// <summary>
/// xml comment
/// </summary>
[MaxLength(500)]
public string? CommentXml { get; set; }
/// <summary>
/// comment summary
/// </summary>
[MaxLength(200)]
public string? CommentSummary { get; set; }
/// <summary>
/// 是否必须
/// </summary>
public bool IsRequired { get; set; }
/// <summary>
/// 可空?
/// </summary>
public bool IsNullable { get; set; }
public int? MinLength { get; set; }
public int? MaxLength { get; set; }
public bool IsDecimal { get; set; }
/// <summary>
/// 默认值
/// </summary>
[MaxLength(100)]
public string DefaultValue { get; set; } = string.Empty;
public bool IsShadow { get; set; }
public bool IsIndex { get; set; }

EntityNavigation

EntityNavigation是实体的导航属性信息,主要用于描述实体之间的关系。

public required string ForeignKey { get; set; }
 public required string Name { get; set; }
 public required string Type { get; set; }
 public bool IsCollection { get; set; }
 public string? Summary { get; set; }
 public bool IsRequired { get; set; }
 public bool IsUnique { get; set; }
 public bool IsSkipNavigation { get; set; }
 public bool IsOwnership { get; set; }
 public List<PropertyInfo> ForeignKeyProperties { get; set; } = [];
 public EntityInfo? EntityInfo { get; set; }