使用T4生成代码时,实体框架架构在EdmFunction中始终为空

时间:2019-02-24 12:51:13

标签: entity-framework t4

我的目标是以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。

1 个答案:

答案 0 :(得分:2)

EF6元数据系统非常复杂,可能是由于尝试涵盖了太多场景-数据库优先,代码优先和模型优先。它们具有单独的元数据,这些元数据组织在所谓的数据空间中-存储模型,对象模型和概念模型以及它们之间的映射。

这里的问题是标准EF6 T4生成器使用概念模型。这是因为ExecuteFunctionCreateQueryEntityCommand(实体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);    
}
相关问题