I am attempting to have table names automatically renamed to strip off a leading prefix in EF4. I know it can be done in the GUI, however, my company created the DB schema
I do not know about any build in feature for stripping table prefixes but you can open .edmx file as XML (use Open With in VS) and use simple replacing to replace prefix with nothing.
The simplest solution we came up with was to make a new console application that did the low level work of renaming everything in the edmx file; the application can be added to the tools menu of Visual Studio (add external tool) with 'arguments' = $(ItemPath), 'initial directory' = $(ItemDir), 'prompt for arguments' = true and 'use output window' = true, then can be executed with the EDMX file selected. In our case, we needed to strip underscores and transform the names to camel case (interpreting the underscore as a word separator), in addition to stripping the prefix.
I'll omit the rest of the code (like the Transform.Standard method, error handling and the stripping of additional parameters) because it's too badly written to share and it's too late at night for refactoring :P; but it's easy enough to interpret the rest of the arguments after the first as strings to be stripped from names and so on - this main code is only about the modifications needed on the EDMX file. In case you use this as an External Tool from VS, you can specify the rest of the strings after $(ItemPath) in 'arguments'.
Please note this wasn't tested extensively, and there may be additional information in other EDMX files that we failed to account for (but I doubt it); also, I got part of the code from somewhere else on the net but failed to write down where exactly, so sorry! Credit should have been given accordingly. Also, naturally this would have been much better as an extension for VS2010, but I simply did not have the time to do it - if you do link it somewhere and I'll use that instead ;)
if (_args.Count < 1) return;
string file = _args.First();
if (!File.Exists(file))
{
wait("Could not find specified file.");
return;
}
if (Path.GetExtension(file) != ".edmx")
{
wait("This works only on EDMX files.");
return;
}
//processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(file, ".bak"));
File.Copy(file, Path.ChangeExtension(file, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(file);
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2008/09/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2008/10/edmx";
XElement csdl = xdoc.Descendants(XName.Get("Schema", CSDLNamespace)).First();
XElement msl = xdoc.Descendants(XName.Get("Mapping", MSLNamespace)).First();
XElement designerDiagram = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).First();
//modifications for renaming everything, not just table names:
string[] CSDLpaths = new string[]
{
"EntityContainer/EntitySet.Name",
"EntityContainer/EntitySet.EntityType",
"EntityContainer/AssociationSet/End.EntitySet",
"EntityType.Name",
"EntityType/Key/PropertyRef/Name",
"EntityType/Property.Name",
"EntityType/NavigationProperty.Name",
"Association/End.Type",
"Association//PropertyRef.Name",
};
#region CSDL2
Console.WriteLine("Modifying CSDL...");
Console.WriteLine(" - modifying entity sets...");
foreach (var entitySet in csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("EntitySet", CSDLNamespace)))
{
entitySet.Attribute("Name").Value = Transform.Standard(entitySet.Attribute("Name").Value);
entitySet.Attribute("EntityType").Value = Transform.Standard(entitySet.Attribute("EntityType").Value);
}
Console.WriteLine(" - modifying association sets...");
foreach (var associationSet in csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("AssociationSet", CSDLNamespace)))
{
foreach (var end in associationSet.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("EntitySet").Value = Transform.Standard(end.Attribute("EntitySet").Value);
}
}
Console.WriteLine(" - modifying entity types...");
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = Transform.Standard(entityType.Attribute("Name").Value);
foreach (var key in entityType.Elements(XName.Get("Key", CSDLNamespace)))
{
foreach (var propertyRef in key.Elements(XName.Get("PropertyRef", CSDLNamespace)))
{
propertyRef.Attribute("Name").Value = Transform.Standard(propertyRef.Attribute("Name").Value);
}
}
foreach (var property in entityType.Elements(XName.Get("Property", CSDLNamespace)))
{
property.Attribute("Name").Value = Transform.Standard(property.Attribute("Name").Value);
}
foreach (var navigationProperty in entityType.Elements(XName.Get("NavigationProperty", CSDLNamespace)))
{
navigationProperty.Attribute("Name").Value = Transform.Standard(navigationProperty.Attribute("Name").Value);
}
}
Console.WriteLine(" - modifying associations...");
foreach (var association in csdl.Elements(XName.Get("Association", CSDLNamespace)))
{
foreach (var end in association.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("Type").Value = Transform.Standard(end.Attribute("Type").Value);
}
foreach (var propref in association.Descendants(XName.Get("PropertyRef", CSDLNamespace)))
{
//propertyrefs are contained in constraints
propref.Attribute("Name").Value = Transform.Standard(propref.Attribute("Name").Value);
}
}
#endregion
#region MSL2
Console.WriteLine("Modifying MSL...");
Console.WriteLine(" - modifying entity set mappings...");
foreach (var entitySetMapping in msl.Element(XName.Get("EntityContainerMapping", MSLNamespace)).Elements(XName.Get("EntitySetMapping", MSLNamespace)))
{
entitySetMapping.Attribute("Name").Value = Transform.Standard(entitySetMapping.Attribute("Name").Value);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = Transform.Standard(entityTypeMapping.Attribute("TypeName").Value);
foreach
(var scalarProperty in
(entityTypeMapping.Element(XName.Get("MappingFragment", MSLNamespace))).Elements(XName.Get("ScalarProperty", MSLNamespace))
)
{
scalarProperty.Attribute("Name").Value = Transform.Standard(scalarProperty.Attribute("Name").Value);
}
}
}
Console.WriteLine(" - modifying association set mappings...");
foreach (var associationSetMapping in msl.Element(XName.Get("EntityContainerMapping", MSLNamespace)).Elements(XName.Get("AssociationSetMapping", MSLNamespace)))
{
foreach (var endProperty in associationSetMapping.Elements(XName.Get("EndProperty", MSLNamespace)))
{
foreach (var scalarProperty in endProperty.Elements(XName.Get("ScalarProperty", MSLNamespace)))
{
scalarProperty.Attribute("Name").Value = Transform.Standard(scalarProperty.Attribute("Name").Value);
}
}
}
#endregion
#region Designer
Console.WriteLine("Modifying designer content...");
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = Transform.Standard(item.Attribute("EntityType").Value);
}
#endregion
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(args[0], Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
Edit: adding the Transform class used by the code above. Also, please note that this works for Entity Framework 4.0 - later versions might have slightly different EDMX structure (I'm not sure) so the code might need to be modified to account for that.
public class Transform
{
public static string Standard(string initial, IEnumerable<string> eliminations = null)
{
Regex re = new Regex(@"(\w+)(\W*?)$", RegexOptions.Compiled);
Regex camelSplit = new Regex(@"(?<!^)(?=[A-Z])", RegexOptions.Compiled);
return re.Replace(initial, new MatchEvaluator((Match m) =>
{
string name = m.Groups[1].Value;
var parts = name.Split('_').AsEnumerable();
if (parts.Count() == 1 && IsMixedCase(name))
{
string result = string.Concat(camelSplit.Split(name).Except(eliminations, StringComparer.CurrentCultureIgnoreCase));
return result + m.Groups[2];
}
else
{
parts = parts.Select(s => CultureInfo.CurrentCulture.TextInfo.ToTitleCase(s.ToLower()));
parts = parts.Except(eliminations, StringComparer.CurrentCultureIgnoreCase);
return string.Concat(parts) + m.Groups[2];
}
}));
}
public static bool IsMixedCase(string name)
{
int lower = 0, total = 0;
for (int i = 0; i < name.Length; i++)
{
if (char.IsLower(name, i)) lower++;
if (char.IsLetter(name, i)) total++;
}
return lower != 0 && lower != total;
}
}
Now you could write a little bit more code in the Main method that could take the arguments (except the first one which will be the name of the file) and pass them onward to the eliminations
parameter; these could be strings that need to be erased from the names, in my case the prefixes. You can then specify these strings from the visual studio tools interface when invoking the tool. Or I suppose you can just hardcode them if you don't care that much about reusing the tool :)