七星海 发表于 2023-11-20 11:07:34

EF Core预编译模型Compiled Model

前言

最近还在和 npgsql 与 EF Core 斗争,由于 EF Core 暂时还不支持 AOT,因此在 AOT 应用程序中使用 EF Core 时,会提示问题:

听这个意思,似乎使用 Compiled Model 可以解决问题,于是就又研究了一下 EF Core 的这个功能。
在 EF Core 中,模型根据实体类和配置构建,默认情况下,每次创建一个新的 DbContext 实例时,EF Core 都会构建模型。对于需要频繁创建 DbContext 实例的应用程序,这可能会导致性能问题。
Entity Framework Core(EF Core)的预编译模型(Compiled Model)对应提供了一种优化,在 EF Core 6 preview 5 中首次增加了这个功能,可以让设计人员预编译模型,避免在后续执行查询时动态生成模型。
预编译模型的优势


[*]性能提升:通过预编译模型,可以减少应用程序启动时的开销,特别是对于大型模型。
此处的启动时间,指 DbContext 的首次启动时间,由于延迟查询的机制,一般 DbContext 并不会在新建对象时完成启动,而是在首次执行插入或者查询时完成这个过程。
参考下图(来自参考 1):

显然,随着模型的规模增大,启动时间线性增长;但是使用预编译模型后,启动时间和模型大小基本无关,保持在一个极低的水平。

[*]一致性:确保每个 DbContext 实例使用相同的模型配置。
使用预编译模型


[*]生成编译模型:
使用 EF Core 命令行工具,命令:
dotnet ef dbcontext optimize这将生成 DbContext 的预编译模型。我只有一个 POCO 类,生成了 3 个文件,类名称就是文件名称。

public partial class DataContextModel : RuntimeModel
{
    static DataContextModel()
    {
      var model = new DataContextModel();
      model.Initialize();
      model.Customize();
      _instance = model;
    }

    private static DataContextModel _instance;
    public static IModel Instance => _instance;

    partial void Initialize();

    partial void Customize();
}public partial class DataContextModel
{
    partial void Initialize()
    {
      var deviceDatum = DeviceDatumEntityType.Create(this);

      DeviceDatumEntityType.CreateAnnotations(deviceDatum);

      AddAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
      AddAnnotation("ProductVersion", "8.0.0-rc.2.23480.1");
      AddAnnotation("Relational:MaxIdentifierLength", 63);
      AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel());
    }

    private IRelationalModel CreateRelationalModel()
    {
          // 这里面非常多描述类型的代码,节约篇幅我就不写全了。
      var relationalModel = new RelationalModel(this);

      var deviceDatum = FindEntityType("AspireSample.DeviceDatum")!;

      var defaultTableMappings = new List<TableMappingBase<ColumnMappingBase>>();
      deviceDatum.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings);
      
          ....
          
      return relationalModel.MakeReadOnly();
    }
}internal partial class DeviceDatumEntityType
{
    public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null)
    {
      var runtimeEntityType = model.AddEntityType(
            "AspireSample.DeviceDatum",
            typeof(DeviceDatum),
            baseEntityType);

      var id = runtimeEntityType.AddProperty(
            "Id",
            typeof(string),
            propertyInfo: typeof(DeviceDatum).GetProperty("Id", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly),
            fieldInfo: typeof(DeviceDatum).GetField("<Id>k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly),
            afterSaveBehavior: PropertySaveBehavior.Throw);
      id.TypeMapping = StringTypeMapping.Default.Clone(
            comparer: new ValueComparer<string>(
                (string v1, string v2) => v1 == v2,
                (string v) => v.GetHashCode(),
                (string v) => v),
            keyComparer: new ValueComparer<string>(
                (string v1, string v2) => v1 == v2,
                (string v) => v.GetHashCode(),
                (string v) => v),
            providerValueComparer: new ValueComparer<string>(
                (string v1, string v2) => v1 == v2,
                (string v) => v.GetHashCode(),
                (string v) => v),
            mappingInfo: new RelationalTypeMappingInfo(
                dbType: System.Data.DbType.String));
               
      ......

      var key = runtimeEntityType.AddKey(
            new[] { id });
      runtimeEntityType.SetPrimaryKey(key);

      return runtimeEntityType;
    }

    public static void CreateAnnotations(RuntimeEntityType runtimeEntityType)
    {
      runtimeEntityType.AddAnnotation("Relational:FunctionName", null);
      runtimeEntityType.AddAnnotation("Relational:Schema", null);
      runtimeEntityType.AddAnnotation("Relational:SqlQuery", null);
      runtimeEntityType.AddAnnotation("Relational:TableName", "devicedata");
      runtimeEntityType.AddAnnotation("Relational:ViewName", null);
      runtimeEntityType.AddAnnotation("Relational:ViewSchema", null);

      Customize(runtimeEntityType);
    }

    static partial void Customize(RuntimeEntityType runtimeEntityType);
}可以看到,优化工具帮我们生成了非常多的代码,尤其是与类型描述相关的代码,因此,如果我们修改模型,那么必须重新执行一遍对应的生成指令。

[*]修改 DbContext:
修改你的 DbContext 类,让它使用这个预编译模型。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
      // 指定编译模型的使用
      optionsBuilder.UseModel(CompiledModels.MyCompiledModel.Instance);
    }
}权衡利弊

核心优点:

[*]提升启动速度,对实体类型较多的 DbContext 尤其显著。
缺点:

[*]不支持全局查询过滤、Lazy loading proxies、Change tracking proxies 和自定义 IModelCacheKeyFactory 。
[*]每次修改模型都必须重新生成优化代码。
不支持的东西很多,每次修改模型还需要重新生成就非常麻烦,因此,如果不是真的启动速度已经非常慢了不建议使用。
后记

我在使用 EF Core 的 Compiled Model 之后依然提示相同的错误,后来发现错误是从 Reflection 相关类爆出的,而不是 EF Core 的相关类。所以错误里说的 Compiled Model 和 EF Core 的 Compiled Model 概念不同,应该指 AOT 不支持反射中动态加载,需要提前编译。现在 EF Core 还没完全准备好,因此,重申一下,EF Core 8 暂时不支持 AOT。
参考


[*]Announcing Entity Framework Core 6.0 Preview 5: Compiled Models - .NET Blog (microsoft.com)
[*]Advanced Performance Topics | Microsoft Learn

来源:https://www.cnblogs.com/podolski/archive/2023/11/20/17843466.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: EF Core预编译模型Compiled Model