Какой файл T4 используется для генерации EDMX из базы данных через «Обновить модель из базы данных»? - PullRequest
1 голос
/ 29 февраля 2012

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

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

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

Ответы [ 2 ]

4 голосов
/ 10 июля 2012

Просто наткнулся на этот вопрос, пока искал что-то еще, поэтому я ожидаю, что вы решили его самостоятельно.Некоторое время назад у меня была точно такая же проблема, как и у вас.Я обошел его, используя шаблон «предварительной стирки» EDMX.tt 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
#>

2 голосов
/ 08 августа 2014

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

Это шаблон 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);
    }
#>
...