问题
When working with an EF4 (edmx) model, we frequently need to process an "Update Model From Database". Commonly, we need to just delete table(s) and let them fully regenerate from the database.
The issue at hand is that we have multiple recursive relationships/properties. By default, the "update Model From Database" process creates the property with the object's name and then adds a 1, 2, 3, etc. for each additional relationship. So if I have a table of "companies" where it points to itself multiple times (like parent company and dba company), currently the edmx results in Company1 and Company2. I need to control the naming of them....not manually.
If i could find the T4 file (or a way to intercept and control) the generation of the edmx file itself, i could fix this problem.
回答1:
Just stumbled on this question whilst looking for something else, so I expect you have solved it yourself. A while back I had the exact same issue as you however. The way I got round it was by using an EDMX.tt "prewash" T4 template, which re-named those properties in the EDMX file. The only wrinkle is remembering to run it after saving EDM designer changes (and also ensuring the EDMX file is checked out and editable!)
I think this is another feature that may need to be looked at in later versions of EF. Having navigation properties named Address1, Address2, etc. is not helpful.
The basic inspiration about pulling the EDMX file into memory and parsing it came from here: http://www.codeproject.com/KB/library/EdmxParsing.aspx
Bit of a long lump of code and in VB to boot but here you are:
<#@ 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:
Thanks to James Close, this really works.
This is C# T4 template(it looks like James VB template) that rewrites edmx navigation & simple properties and then fixes mappings and associations:
<#@ 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);
}
#>
来源:https://stackoverflow.com/questions/9501431/what-t4-file-is-used-to-generate-an-edmx-from-database-via-update-model-from-da