Какъв 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 (afaik). Благодаря ти @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