前言
最近還在和 npgsql
與 EF Core
斗爭(zhēng),由于 EF Core
暫時(shí)還不支持 AOT,因此在 AOT 應(yīng)用程序中使用 EF Core 時(shí),會(huì)提示問(wèn)題:
聽(tīng)這個(gè)意思,似乎使用 Compiled Model
可以解決問(wèn)題,于是就又研究了一下 EF Core 的這個(gè)功能。
在 EF Core 中,模型根據(jù)實(shí)體類和配置構(gòu)建,默認(rèn)情況下,每次創(chuàng)建一個(gè)新的 DbContext
實(shí)例時(shí),EF Core 都會(huì)構(gòu)建模型。對(duì)于需要頻繁創(chuàng)建 DbContext
實(shí)例的應(yīng)用程序,這可能會(huì)導(dǎo)致性能問(wèn)題。
Entity Framework Core(EF Core)的預(yù)編譯模型(Compiled Model)對(duì)應(yīng)提供了一種優(yōu)化,在 EF Core 6 preview 5 中首次增加了這個(gè)功能,可以讓設(shè)計(jì)人員預(yù)編譯模型,避免在后續(xù)執(zhí)行查詢時(shí)動(dòng)態(tài)生成模型。
預(yù)編譯模型的優(yōu)勢(shì)
- 性能提升:通過(guò)預(yù)編譯模型,可以減少應(yīng)用程序啟動(dòng)時(shí)的開(kāi)銷,特別是對(duì)于大型模型。
此處的啟動(dòng)時(shí)間,指
DbContext
的首次啟動(dòng)時(shí)間,由于延遲查詢的機(jī)制,一般 DbContext 并不會(huì)在新建對(duì)象時(shí)完成啟動(dòng),而是在首次執(zhí)行插入或者查詢時(shí)完成這個(gè)過(guò)程。
參考下圖(來(lái)自參考 1):
顯然,隨著模型的規(guī)模增大,啟動(dòng)時(shí)間線性增長(zhǎng);但是使用預(yù)編譯模型后,啟動(dòng)時(shí)間和模型大小基本無(wú)關(guān),保持在一個(gè)極低的水平。
-
一致性:確保每個(gè)
DbContext
實(shí)例使用相同的模型配置。
使用預(yù)編譯模型
-
生成編譯模型:
使用 EF Core 命令行工具,命令:
dotnet ef dbcontext optimize
這將生成 DbContext
的預(yù)編譯模型。我只有一個(gè) POCO 類,生成了 3 個(gè)文件,類名稱就是文件名稱。
[DbContext(typeof(DataContext))]
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()
{
// 這里面非常多描述類型的代碼,節(jié)約篇幅我就不寫全了。
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);
}
可以看到,優(yōu)化工具幫我們生成了非常多的代碼,尤其是與類型描述相關(guān)的代碼,因此,如果我們修改模型,那么必須重新執(zhí)行一遍對(duì)應(yīng)的生成指令。
-
修改 DbContext:
修改你的DbContext
類,讓它使用這個(gè)預(yù)編譯模型。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
// 指定編譯模型的使用
optionsBuilder.UseModel(CompiledModels.MyCompiledModel.Instance);
}
}
權(quán)衡利弊
核心優(yōu)點(diǎn):
- 提升啟動(dòng)速度,對(duì)實(shí)體類型較多的
DbContext
尤其顯著。
缺點(diǎn):
- 不支持全局查詢過(guò)濾、
Lazy loading proxies
、Change tracking proxies
和自定義IModelCacheKeyFactory
。 - 每次修改模型都必須重新生成優(yōu)化代碼。
不支持的東西很多,每次修改模型還需要重新生成就非常麻煩,因此,如果不是真的啟動(dòng)速度已經(jīng)非常慢了不建議使用。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-746865.html
后記
我在使用 EF Core 的 Compiled Model 之后依然提示相同的錯(cuò)誤,后來(lái)發(fā)現(xiàn)錯(cuò)誤是從 Reflection 相關(guān)類爆出的,而不是 EF Core 的相關(guān)類。所以錯(cuò)誤里說(shuō)的 Compiled Model 和 EF Core 的 Compiled Model 概念不同,應(yīng)該指 AOT 不支持反射中動(dòng)態(tài)加載,需要提前編譯。現(xiàn)在 EF Core 還沒(méi)完全準(zhǔn)備好,因此,重申一下,EF Core 8 暫時(shí)不支持 AOT。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-746865.html
參考
- Announcing Entity Framework Core 6.0 Preview 5: Compiled Models - .NET Blog (microsoft.com)
- Advanced Performance Topics | Microsoft Learn
到了這里,關(guān)于EF Core預(yù)編譯模型Compiled Model的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!