Какой файл T4 используется для создания EDMX из базы данных через обновление модели из базы данных?

При работе с моделью EF4 (edmx) нам часто приходится обрабатывать «Обновить модель из базы данных». Обычно нам нужно просто удалить таблицу (таблицы) и позволить им полностью восстановиться из базы данных.

Проблема в том, что у нас есть несколько рекурсивных отношений/свойств. По умолчанию процесс «обновить модель из базы данных» создает свойство с именем объекта, а затем добавляет 1, 2, 3 и т. д. для каждой дополнительной связи. Итак, если у меня есть таблица «компаний», где она указывает на себя несколько раз (например, материнская компания и компания dba), в настоящее время edmx приводит к Company1 и Company2. Мне нужно контролировать их имена .... не вручную.

Если бы я мог найти файл T4 (или способ перехвата и контроля) генерации самого файла edmx, я мог бы решить эту проблему.


person Randy    schedule 29.02.2012    source источник


Ответы (2)


Просто наткнулся на этот вопрос, когда искал что-то еще, поэтому я ожидаю, что вы решили его самостоятельно. Некоторое время назад у меня была точно такая же проблема, как и у вас. Я обошел это с помощью шаблона EDMX.tt «prewash» T4, который переименовал эти свойства в файле EDMX. Единственная проблема заключается в том, чтобы не забыть запустить его после сохранения изменений дизайнера EDM (а также убедиться, что файл EDMX извлечен и доступен для редактирования!)

Я думаю, что это еще одна функция, на которую, возможно, стоит обратить внимание в более поздних версиях EF. Наличие свойств навигации с именами Address1, Address2 и т. д. бесполезно.

Основное вдохновение для извлечения файла EDMX в память и его разбора пришло отсюда: http://www.codeproject.com/KB/library/EdmxParsing.aspx

Немного длинного кода и в VB для загрузки, но вот:

<#@ template language="VB" debug="false" hostspecific="true"#>
<#@ import namespace="<xmlns=\"http://schemas.microsoft.com/ado/2008/09/edm\">" #>
<#@ import namespace="<xmlns:edmx=\"http://schemas.microsoft.com/ado/2008/10/edmx\">" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Xml.Linq" #>
'EDMX pre wash template
'Last run:<#= GetDate() #>
<#
  Main()
#>
<#+
  '----------------------------------------------------------------------------------------------------------
  ' Main
  '----------------------------------------------------------------------------------------------------------
  ''' 
  '''  Parses the EDMX file and renames all navigation properties which are not collections and do not
  '''  reference types by primary key with a their FK name, e.g. navigation property for DefaultAddress_FK is
  '''  renamed to DefaultAddress
  ''' 
  Public Sub Main()

    Dim strPath As String = System.IO.Path.GetDirectoryName(Host.TemplateFile) & "\MyDataModel.edmx"
    Dim edmx As XElement = XElement.Load(strPath)
    Dim itemCol As EdmItemCollection = ReadEdmItemCollection(edmx)
    Dim entity As EntityType
    Dim entityTo As EntityType
    Dim navigationProperties As IEnumerable(Of NavigationProperty)
    Dim navigationProperty As NavigationProperty
    Dim updatableProperty As XElement
    Dim assType As AssociationType
    Dim rc As ReferentialConstraint
    Dim strPropertyName As String
    Dim bModifyProperty As Boolean = False

    For Each entity In itemCol.GetItems(Of EntityType)().OrderBy(Function(e) e.Name)

      navigationProperties = From n In entity.NavigationProperties
                             Where n.DeclaringType Is entity AndAlso
                                   n.ToEndMember.RelationshipMultiplicity  RelationshipMultiplicity.Many

      If navigationProperties.Any() Then
        For Each navigationProperty In navigationProperties
          bModifyProperty = False
          ' Get the association for this navigation property
          assType = (From ass As AssociationType In itemCol.GetItems(Of AssociationType)() _
                     Where ass.AssociationEndMembers IsNot Nothing _
                        AndAlso ass.Name = navigationProperty.RelationshipType.Name _
                     Select ass).AsQueryable().FirstOrDefault()
          If (assType IsNot Nothing) Then

            rc = assType.ReferentialConstraints.FirstOrDefault()
            If (rc IsNot Nothing AndAlso rc.ToProperties.Any) Then
              strPropertyName = rc.ToProperties.First.Name
              ' Make sure the FK is not also a PK on the entity referenced
              entityTo = (From e In itemCol.GetItems(Of EntityType)() Where e.Name = rc.ToRole.Name).FirstOrDefault()
              If (entityTo IsNot Nothing AndAlso
                  Not (From km In entityTo.KeyMembers() Where km.Name = strPropertyName).Any) Then
                ' Get the new name of the property - this uses a little extension
                ' method I wrote to Trim characters at the end of a string matching a regex
                strPropertyName = strPropertyName.TrimEnd("_FK[0-9]{0,1}", options:=0)
                ' Ensure there are no already existant properties with that name on the entity
                If (Not (From p In entity.Properties Where p IsNot navigationProperty AndAlso p.Name = strPropertyName).Any) Then
                  bModifyProperty = True
                End If
              End If

              If (bModifyProperty) Then
                updatableProperty = (From n In (From e In edmx...
                                                Where e.@Name = entity.Name).
                                     Where n.@Name = navigationProperty.Name).FirstOrDefault
                If (updatableProperty IsNot Nothing AndAlso updatableProperty.@Name  strPropertyName) Then
#>'Renaming navigation property on <#= entity.Name #> from <#= updatableProperty.@Name #> to <#= strPropertyName #> in EDMX file
<#+
                  updatableProperty.@Name = strPropertyName
                End If
              End If
            End If

          End If
        Next
      End If

    Next entity

    edmx.Save(strPath)

  End Sub

  '----------------------------------------------------------------------------------------------------------
  ' ReadEdmItemCollection
  '----------------------------------------------------------------------------------------------------------
  ''' 
  '''  Code to parse the EDMX xml document and return the managed EdmItemCollection class
  ''' 
  ''' Taken from here: http://www.codeproject.com/KB/library/EdmxParsing.aspx 
  Public Shared Function ReadEdmItemCollection(edmx As XElement) As EdmItemCollection

    Dim csdlNodes As IEnumerable(Of XElement) = edmx....First.Elements
    Dim readers As IEnumerable(Of XMLReader) = From c As XElement In csdlNodes Select c.CreateReader()
    Return New EdmItemCollection(readers)

  End Function
#>

person James Close    schedule 10.07.2012
comment
Эй, Джеймс, пока я не нашел ответа. Я в основном отказался от этой функции в свете необходимости развивать проект в других областях. Спасибо за предложенный ответ! 1+ за предоставление ответа. Я собираюсь проверить решение со своей стороны, и если оно сработает или значительно приблизит меня, я приму его как ответ. Еще раз спасибо! - person Randy; 20.07.2012
comment
для тех, кто пытается использовать этот код, добавьте <#@ include file="EF6.Utility.VB.ttinclude"#> вверху, чтобы разрешить кучу ссылок. И имейте в виду, что некоторые символы могут быть вырезаны из вставленного кода (может быть ТАКИМ способом отображения кода). Например, edmx....First.Elements должно быть edmx...<edmx:ConceptualModels>.First.Elements (афаик). Спасибо @JamesClose - person Ivan Ferrer Villa; 18.09.2015

Благодаря Джеймсу Клоузу это действительно работает.

Это шаблон C # T4 (он выглядит как шаблон James VB), который переписывает навигацию edmx и простые свойства, а затем исправляет сопоставления и ассоциации:

<#@ template  debug="true" hostSpecific="true" #>
<#@ assembly name="System.Text.RegularExpressions"#>
<#@ import namespace="System.Text.RegularExpressions" #>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#/*CodeGenerationCommon.ttinclude contains TypeMapper and EdmMetadataLoader from Model.tt, moved it from there to avoid duplication*/#>
<#@ include file="CodeGenerationCommon.ttinclude" #>
<#@ output extension=".txt" #>
Edmx fixer template
Started at: <#= DateTime.Now #>
<#
    const string inputFile = @"Model.edmx";
    var textTransform = DynamicTextTransformation.Create(this);
    var edmx = XElement.Load(textTransform.Host.ResolvePath(inputFile), LoadOptions.SetBaseUri | LoadOptions.SetLineInfo);
    var code = new CodeGenerationTools(this);
    var ef = new MetadataTools(this);
    var typeMapper = new TypeMapper(code, ef, textTransform.Errors);
    var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);
    var navigationProperties = typeMapper.GetItemsToGenerate<EntityType>(itemCollection).SelectMany(item => typeMapper.GetNavigationProperties(item));
    Fix(navigationProperties, edmx);
    edmx.Save(textTransform.Host.ResolvePath(inputFile));
#>
Finished at: <#= DateTime.Now #>
<#+ 
    public void Fix(IEnumerable<NavigationProperty> navigationProperties, XElement edmx)
    {
        foreach(var navigationProperty in navigationProperties)
        {
            if((navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many && navigationProperty.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many) || 
                (navigationProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many && navigationProperty.FromEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many))
            {
                continue;
            }
            var fk = navigationProperty.GetDependentProperties().FirstOrDefault();
            if(fk == null)
            {
                var mirrorFk = navigationProperties.FirstOrDefault(item => !item.Equals(navigationProperty) && item.RelationshipType.Name == navigationProperty.RelationshipType.Name).GetDependentProperties().First();
                RewriteNavigationProperty(navigationProperty, mirrorFk.Name, edmx, true);
                continue;
            }
            RewriteNavigationProperty(navigationProperty, fk.Name, edmx, false);
        }
    }

    public void RewriteNavigationProperty(NavigationProperty navigationProperty, string fkName, XElement edmx, bool isCollection)
    {
        var entity = edmx
            .Descendants()
            .Where(item => item.Name.LocalName == "ConceptualModels")
            .Descendants()
            .First(item => item.Name.LocalName == "EntityType" && item.Attribute("Name").Value == navigationProperty.DeclaringType.Name);
        var element = entity
            .Elements()
            .First(item => item.Name.LocalName == "NavigationProperty" && item.Attribute("Relationship").Value == navigationProperty.RelationshipType.ToString());
        var trimId = new Regex(@"(.*)(ID|Id|id)$").Match(fkName).Groups[1].Value;
        var trimDigits = new Regex(@"(.*)(\d*)$").Match(navigationProperty.Name).Groups[1].Value;
        var suffix = string.IsNullOrEmpty(trimDigits) ? navigationProperty.Name : trimDigits;
        var prefix = string.IsNullOrEmpty(trimId) ? fkName : trimId;
        if(string.IsNullOrEmpty(trimId) && !isCollection)
        {
            FixFk(edmx, entity, fkName, navigationProperty);
        }
        element.SetAttributeValue("Name", isCollection ? prefix + suffix : prefix);
    }

    public void FixFk(XElement edmx, XElement entity, string fkName, NavigationProperty navigationProperty)
    {
        var newFkName = fkName + "Id";
        var fk = entity
            .Elements()
            .First(item => item.Name.LocalName == "Property" && item.Attribute("Name").Value == fkName);
        fk.SetAttributeValue("Name", newFkName);
        var association = edmx
            .Descendants()
            .Where(item => item.Name.LocalName == "ConceptualModels")
            .Descendants()
            .FirstOrDefault(item => item.Name.LocalName == "Association" && item.Attribute("Name").Value == navigationProperty.RelationshipType.Name)
            .Descendants()
            .FirstOrDefault(item => item.Name.LocalName == "Dependent" && item.Attribute("Role").Value == navigationProperty.DeclaringType.Name)
            .Elements()
            .First(item => item.Name.LocalName == "PropertyRef");
        association.SetAttributeValue("Name", newFkName);
        var mapping = edmx
            .Descendants()
            .Where(item => item.Name.LocalName == "Mappings")
            .Descendants()
            .FirstOrDefault(item => item.Name.LocalName == "EntityTypeMapping" && item.Attribute("TypeName").Value == navigationProperty.DeclaringType.FullName)
            .Descendants()
            .First(item => item.Name.LocalName == "ScalarProperty" && item.Attribute("Name").Value == fkName);
        mapping.SetAttributeValue("Name", newFkName);
    }
#>
person bitval    schedule 08.08.2014