什么T4文件用于通过“从数据库更新模型”从数据库生成EDMX?

时间:2012-02-29 15:00:47

标签: entity-framework-4 t4 edmx

使用EF4(edmx)模型时,我们经常需要处理“从数据库更新模型”。通常,我们需要删除表,并让它们从数据库中完全重新生成。

手头的问题是我们有多个递归关系/属性。默认情况下,“从数据库更新模型”过程使用对象的名称创建属性,然后为每个附加关系添加1,2,3等。因此,如果我有一个“公司”表,它多次指向自己(如母公司和dba公司),目前edmx的结果是Company1和Company2。我需要控制它们的命名....而不是手动。

如果我能找到T4文件(或拦截和控制的方法)生成edmx文件本身,我可以解决这个问题。

2 个答案:

答案 0 :(得分:4)

在寻找其他东西时偶然发现了这个问题,所以我希望你自己解决了这个问题。不久之后,我遇到了与你完全相同的问题。我绕过它的方法是使用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
#>

答案 1 :(得分:2)

感谢James Close,这确实有用。

这是重写edmx导航和放大器的C#T4模板(看起来像James VB模板)。简单的属性,然后修复映射和关联:

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