我的目标是以async
的方式使用Entity Framework存储过程和函数,但是没有内置的支持。默认的T4代码仅生成同步方法。
经过大量搜索和反复试验,我决定通过调用Model.Context.tt
和/或ExecuteStoreCommandAsync
来修改ExecuteStoreQueryAsync
以生成适当的代码。
不同于ExecuteFunction
只是想要函数名称而不考虑模式,ExecuteStoreCommandAsync
也需要在过程名称之前加上模式(例如[MySchema].[MyProcedure]
)。
类EdmFunction
显然具有一个Schema
属性,但对于我所有的函数来说都是空的。如果我将EDMX
作为文本打开,则可以清楚地看到以下内容:
<Function Name="MyProcedure" Schema="MySchema">
问题是-如何在TT中为存储过程/函数访问正确的架构?
我正在NET Framework 4.7项目中使用EF 6.2。
答案 0 :(得分:2)
EF6元数据系统非常复杂,可能是由于尝试涵盖了太多场景-数据库优先,代码优先和模型优先。它们具有单独的元数据,这些元数据组织在所谓的数据空间中-存储模型,对象模型和概念模型以及它们之间的映射。
这里的问题是标准EF6 T4生成器使用概念模型。这是因为ExecuteFunction
和CreateQuery
与EntityCommand
(实体SQL)一起使用,这些实体后来被转换为“存储”命令(原始SQL)。 ExecuteStoreCommand[Async]
和ExecuteStoreQuery[Async]
直接与“存储”命令(原始SQL)一起使用。
因此,您需要访问“商店”模型。请注意,“概念”模型和“商店”模型都包含EdmFunction
对象,但它们的名称不同,参数名称,类型等也不同。而且由于Schema
仅对“商店”(数据库)有意义),这就是为什么您总是从概念模型中获得null
。
这里是如何从商店模式加载和获取EdmFunction
的信息。标准EF6 T4模板包含一个名为EF6.Utility.CS.ttinclude
的文件,其中包含许多代码生成使用的帮助程序。其中一个是名为EdmMetadataLoader
的类,其方法CreateEdmItemCollection
被标准模板用来从EDMX加载概念模型。可以将其用作提取所需方法的基础,如下所示(将其添加到代码帮助器部分中的Context.tt的末尾-最后关闭#>
之前):>
private static StoreItemCollection CreateStoreItemCollection(string sourcePath, IDynamicHost host, System.Collections.IList errors)
{
var root = XElement.Load(host.ResolvePath(sourcePath), LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
var schemaElement = root.Elements()
.Where(e => e.Name.LocalName == "Runtime")
.Elements()
.Where(e => e.Name.LocalName == "StorageModels")
.Elements()
.Where(e => e.Name.LocalName == "Schema")
.FirstOrDefault() ?? root;
if (schemaElement != null)
{
using (var reader = schemaElement.CreateReader())
{
IList<EdmSchemaError> schemaErrors;
var itemCollection = StoreItemCollection.Create(new[] { reader }, null, null, out schemaErrors);
foreach (var error in schemaErrors)
{
errors.Add(
new CompilerError(
error.SchemaLocation ?? sourcePath,
error.Line,
error.Column,
error.ErrorCode.ToString(CultureInfo.InvariantCulture),
error.Message)
{
IsWarning = error.Severity == EdmSchemaErrorSeverity.Warning
});
}
return itemCollection ?? new StoreItemCollection();
}
}
return new StoreItemCollection();
}
然后找到该行
var itemCollection = loader.CreateEdmItemCollection(inputFile);
并在其后插入以下行
var storeItemCollection = CreateStoreItemCollection(inputFile, textTransform.Host, textTransform.Errors);
现在您可以替换标准
foreach (var edmFunction in container.FunctionImports)
{
WriteFunctionImport(typeMapper, codeStringGenerator, edmFunction, modelNamespace, includeMergeOption: false);
}
使用
var functions = storeItemCollection
.GetItems<EdmFunction>()
.Where(f => !f.IsFromProviderManifest)
.ToList();
foreach (var edmFunction in functions)
{
#>
// [<#=edmFunction.Schema ?? ""#>].[<#=edmFunction.Name#>]
<#
}
主体仅输出带有每个数据库函数导入的[Schema]。[Name]的注释,以证明正确的edmFunction.Schema
属性(问题的目标)。将其替换为实际的代码生成。
如果您同时需要函数的概念(代码)和存储(db)定义,则可以类似的方式创建StorageMappingItemCollection
(唯一的区别是它需要传递EdmItemCollection
和{ {1}}(除了xml阅读器之外)已经有了,例如(StoreItemCollection
是您必须创建和实现的方法):
CreateStorageMappingItemCollection
然后使用
var storageMappingItemCollection = CreateStorageMappingItemCollection(
(EdmItemCollection)itemCollection, storeItemCollection,
inputFile, textTransform.Host, textTransform.Errors);
获得具有两个var functionImports = storageMappingItemCollection
.GetItems<EntityContainerMapping>()
.SelectMany(m => m.FunctionImportMappings)
.ToList();
类型属性的FunctionImportMapping对象的列表:FunctionImport(概念模型)和TargetFunction(存储模型)。
更新,您确实需要使用上述“映射”方法。 EdmFunction
提供了用于定义C#方法的必要信息(名称,参数,返回类型),而FunctionImport
提供了调用db函数/过程所需的信息。
所以辅助方法如下:
TargetFunction
和示例用法:
private static StorageMappingItemCollection CreateStorageMappingItemCollection(EdmItemCollection edmItemCollection, StoreItemCollection storeItemCollection, string sourcePath, IDynamicHost host, System.Collections.IList errors)
{
var root = XElement.Load(host.ResolvePath(sourcePath), LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
var schemaElement = root.Elements()
.Where(e => e.Name.LocalName == "Runtime")
.Elements()
.Where(e => e.Name.LocalName == "Mappings")
.Elements()
.Where(e => e.Name.LocalName == "Mapping")
.FirstOrDefault() ?? root;
if (schemaElement != null)
{
using (var reader = schemaElement.CreateReader())
{
IList<EdmSchemaError> schemaErrors;
var itemCollection = StorageMappingItemCollection.Create(edmItemCollection, storeItemCollection, new[] { reader }, null, out schemaErrors);
foreach (var error in schemaErrors)
{
errors.Add(
new CompilerError(
error.SchemaLocation ?? sourcePath,
error.Line,
error.Column,
error.ErrorCode.ToString(CultureInfo.InvariantCulture),
error.Message)
{
IsWarning = error.Severity == EdmSchemaErrorSeverity.Warning
});
}
if (itemCollection != null) return itemCollection;
}
}
return new StorageMappingItemCollection(edmItemCollection, storeItemCollection);
}