Схема Entity Framework всегда пуста в EdmFunction при генерации кода с использованием T4 - PullRequest
0 голосов
/ 24 февраля 2019

Моя цель состояла в том, чтобы использовать хранимые процедуры и функции Entity Framework async, но встроенная поддержка отсутствует.Код T4 по умолчанию генерирует только методы синхронизации.

После долгих поисков, проб и ошибок я согласился с изменением моего Model.Context.tt для генерации соответствующего кода путем вызова ExecuteStoreCommandAsync и / или ExecuteStoreQueryAsync.

В отличие от ExecuteFunction, которыйпросто хочет имя функции независимо от схемы, ExecuteStoreCommandAsync также требуется схема с префиксом к имени процедуры (например, [MySchema].[MyProcedure]).

Класс EdmFunction, очевидно, имеет Schema свойство, но оно пусто для всех моих функций.Если я открою EDMX как текст, я отчетливо вижу что-то вроде:

<Function Name="MyProcedure" Schema="MySchema">

Вопрос в том, как мне получить доступ к правильной схеме в TT для моей хранимой процедуры / функции?

Я использую EF 6.2 в проекте NET Framework 4.7.

1 Ответ

0 голосов
/ 15 июля 2019

Система метаданных EF6 довольно сложна, вероятно, из-за попытки охватить слишком много сценариев - сначала база данных, сначала код, а потом модель.У них есть отдельные метаданные, организованные в так называемые пространства данных - модель хранилища, объектная модель и концептуальная модель, а также сопоставления между ними.

Проблема в том, что стандартный генератор EF6 T4 использует концептуальную модель.Это потому, что ExecuteFunction и CreateQuery работают с EntityCommand s (Entity SQL), которые впоследствии преобразуются в команды "store" (необработанный SQL).В то время как ExecuteStoreCommand[Async] и ExecuteStoreQuery[Async] работают напрямую с командами "store" (сырой SQL).

Так что вам нужен доступ к модели "store".Обратите внимание, что как «концептуальная», так и «магазинная» модели содержат EdmFunction объекты, но их имена различны, так же как и имена параметров, типы и т. Д. А поскольку Schema имеет смысл только для «store» (базы данных), поэтомувы всегда получаете 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#>]
<#
}

Тело просто выводит комментарий с [Схема]. [Имя] каждого импорта функции db, чтобы доказать правильное свойство edmFunction.Schema (цель вопроса).Замените его фактической генерацией кода.


В случае, если вам нужны как концептуальные (код), так и определения хранения (дБ) функции, вы можете создать StorageMappingItemCollection аналогичным образом (единственное отличиеявляется то, что он требует передачи EdmItemCollection и StoreItemCollection, которые у вас уже есть в дополнение к xml reader), например (CreateStorageMappingItemCollection - метод, который вы должны создать и реализовать):

var storageMappingItemCollection = CreateStorageMappingItemCollection(
    (EdmItemCollection)itemCollection, storeItemCollection,
    inputFile, textTransform.Host, textTransform.Errors);

изатем используйте

var functionImports = storageMappingItemCollection
    .GetItems<EntityContainerMapping>()
    .SelectMany(m => m.FunctionImportMappings)
    .ToList();

, чтобы получить список FunctionImportMapping объектов, имеющих два свойства типа EdmFunction: FunctionImport (концептуальная модель) и TargetFunction (модель хранилища).

Обновление Вам действительно нужно использовать вышеупомянутый подход "отображения".FunctionImport предоставляет необходимую информацию для определения метода C # (имя, аргументы, тип возвращаемого значения), тогда как TargetFunction предоставляет информацию, необходимую для вызова функции / процедуры db.

Таким образом, вспомогательный метод выглядит следующим образом:

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);    
}

и пример использования:

var storageMappingItemCollection = CreateStorageMappingItemCollection(
    (EdmItemCollection)itemCollection, storeItemCollection,
    inputFile, textTransform.Host, textTransform.Errors);

var functionImports = storageMappingItemCollection
    .GetItems<EntityContainerMapping>()
    .SelectMany(m => m.FunctionImportMappings)
    .ToList();

foreach (var item in functionImports)
{
#>
    // <#=item.FunctionImport.Name#> => [<#=item.TargetFunction.Schema ?? ""#>].[<#=item.TargetFunction.Name#>]
<#
}
...