Благодаря Джеймсу Клоузу, это действительно работает.
Это шаблон 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);
}
#>