The database I\'m working against has table names such as \"table_name\". That\'s fine, but I\'d like to generate classes in the format \"TableName\" to work with in C#, Pas
Update: For use with EF6, see additional answer on this page.
Thanks to Alex's answer, I've now extended this code to a full working solution that solves this problem. Since it's taken me most of the day, I'm posting here to help others facing the same challenge. This includes a complete class for manipulating the edmx file (no points for pretty code here) which passes fairly verbose unit tests on varying input strings:
A few examples:
some_class > SomeClass
_some_class_ > SomeClass
some_1_said > Some1Said
I had some additional issues to deal with:
Firstly optionally not replacing underscores while still changing the string to pascal case (due to column names like "test_string" and "teststring", which both resolved to "TestString" otherwise, causing collisions).
Secondly, my code here takes an input parameter that modifies the object context class name that is generated.
Lastly, I wasn't initially sure how to get the designer file to update after modifiying the edmx, so I've included exact steps. I would recommend creating a pre build step in Visual Studio, which saves some of the remaining effort in updating from a modified database schema.
Updating an EDMX File to Reflect Database Changes
Double click the edmx file to show the design surface.
Right click the design surface and select "update model from database".
Select "yes include sensitive information in connection string".
Uncheck "save entity connection settings in App Config" (we already have these).
Select the appropriate database. In Add screen select Tables, Views etc.
Leave pluralise and foreign key options as checked. If creating the edmx file from fresh, you are given the option to enter a model name. You can change or leave this.
Click finish.
Run the code below on the edmx file.
Depending on the state of the application, you may see errors here until the next step.
Right click the edmx file that you are updating, and select "Run custom tool" in Visual Studio. This will cuse the designer.cs (C#) file to be updated.
Run build to check there are no compiler errors.
Run tests to ensure application is functioning correctly.
Any application issues following this should be expected according to the application changes that have been made.
Replacing an edmx file in it's entirety.
Delete the edmx file, taking the designer file with it.
Right click the entities folder
From the create file dialogue, select ADO.NET Entity Data Model. Name it accroding to the class name you would like for your object context(IMPORTANT). This value is referenced in the connection string, so take a look at your app config in case of issues.
In choose model contents, select generate from database.
Follow above instructions from step 3.
using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace EdmxStringFormatter
{
public class Program
{
static void Main(string[] args)
{
if (args.Length < 1) return;
string filePath = args[0];
string entityName = null;
// Optionally do not replace underscores which
// helps with naming collisions with siimilarly named
// columns on some database tables.
bool replaceUnderscores = true;
// Allow for the replacement of the object context class name, which is useful
// where multiple databases have edmx files.
bool doEntityNameReplace = false;
if (args.Length > 1)
{
entityName = args[1];
doEntityNameReplace = true;
}
if (args.Length > 2)
{
replaceUnderscores = args[2] != "0";
}
if (!File.Exists(filePath))
{
StopWithMessage("Could not find specified file.");
return;
}
if (Path.GetExtension(filePath) != ".edmx")
{
StopWithMessage("This works only on EDMX files.");
return;
}
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
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";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
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:
#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 = FormatString(entitySet.Attribute("Name").Value, replaceUnderscores);
entitySet.Attribute("EntityType").Value = FormatString(entitySet.Attribute("EntityType").Value, replaceUnderscores);
}
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 = FormatString(end.Attribute("EntitySet").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying entity types...");
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = FormatString(entityType.Attribute("Name").Value, replaceUnderscores);
foreach (var key in entityType.Elements(XName.Get("Key", CSDLNamespace)))
{
foreach (var propertyRef in key.Elements(XName.Get("PropertyRef", CSDLNamespace)))
{
propertyRef.Attribute("Name").Value = FormatString(propertyRef.Attribute("Name").Value, replaceUnderscores);
}
}
foreach (var property in entityType.Elements(XName.Get("Property", CSDLNamespace)))
{
property.Attribute("Name").Value = FormatString(property.Attribute("Name").Value, replaceUnderscores);
}
foreach (var navigationProperty in entityType.Elements(XName.Get("NavigationProperty", CSDLNamespace)))
{
navigationProperty.Attribute("Name").Value = FormatString(navigationProperty.Attribute("Name").Value, replaceUnderscores);
}
}
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 = FormatString(end.Attribute("Type").Value, replaceUnderscores);
}
foreach (var propref in association.Descendants(XName.Get("PropertyRef", CSDLNamespace)))
{
//propertyrefs are contained in constraints
propref.Attribute("Name").Value = FormatString(propref.Attribute("Name").Value, replaceUnderscores);
}
}
#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 = FormatString(entitySetMapping.Attribute("Name").Value, replaceUnderscores);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = FormatString(entityTypeMapping.Attribute("TypeName").Value, replaceUnderscores);
foreach
(var scalarProperty in
(entityTypeMapping.Element(XName.Get("MappingFragment", MSLNamespace))).Elements(XName.Get("ScalarProperty", MSLNamespace))
)
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value, replaceUnderscores);
}
}
}
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 = FormatString(scalarProperty.Attribute("Name").Value, replaceUnderscores);
}
}
}
#endregion
#region Designer
Console.WriteLine("Modifying designer content...");
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = FormatString(item.Attribute("EntityType").Value, replaceUnderscores);
}
#endregion
// Optionally replace the entity name in case the default of "Entity" is not
// sufficient for your needs.
if (doEntityNameReplace)
{
Console.WriteLine("Modifying entity name refs...");
// CSDL
xdoc.Descendants(XName.Get("EntityContainer", CSDLNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
xdoc.Descendants(XName.Get("EntityContainerMapping", CSNameSpace)).First().Attribute("CdmEntityContainer").Value = entityName;
}
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
/// <summary>
/// Formats the string to pascal case, additionally checking for a period
/// in the string (in which case it skips past the period, which indicates
/// the use of namespace in a string.
/// </summary>
/// <param name="str"></param>
/// <param name="replaceUnderscores"></param>
/// <returns></returns>
private static string FormatString(string str, bool replaceUnderscores = true)
{
char[] chars = str.ToCharArray();
var sb = new StringBuilder();
bool previousCharWasUpper = false;
bool lastOperationWasToLower = false;
int startPos = 0;
if (str.Contains("."))
{
if (str.IndexOf(".") < (str.Length - 1))
{
startPos = str.IndexOf(".") + 1;
}
sb.Append(str.Substring(0, startPos));
}
for (int i = startPos; i < chars.Length; i++)
{
char character = chars[i];
if (Char.IsLetter(character))
{
if (Char.IsLower(character))
{
bool toUpper = false;
if (i > 0)
{
// Look at the previous char to see if not a letter
if (!Char.IsLetter(chars[i - 1]))
{
toUpper = true;
}
}
if (i == 0 || toUpper)
{
character = Char.ToUpper(character);
lastOperationWasToLower = false;
}
}
else // IsUpper = true
{
if (previousCharWasUpper || lastOperationWasToLower)
{
character = Char.ToLower(character);
lastOperationWasToLower = true;
}
}
previousCharWasUpper = Char.IsUpper(character);
sb.Append(character);
}
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}
}
return sb.ToString();
}
private static void StopWithMessage(string str)
{
Console.WriteLine(str);
Console.ReadLine();
throw new InvalidOperationException("Cannot continue.");
}
}
}
Adapting to Visual Studio 2013 & EF6
The code namespaces need a little tweak in order to make it work with EF6:
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2009/11/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
plus you need to take care of designerDiagram(in my case, it wasn't found, so just replaced First() with FirstOrDefault() and added simple null check).
some_class > SomeClass
didn't work for me, instead I got:
some_class > Someclass
To fix it I moved
previousCharWasUpper = false;
a few lines up. It's near the end of the old code block.
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}
Changed to:
else
{
previousCharWasUpper = false;
if (Char.IsDigit(character))
{
sb.Append(character);
lastOperationWasToLower = false;
}
else if(!replaceUnderscores)
{
if(character == '_')
{
sb.Append(character);
}
}
}
This is the full C# .edmx modification code for use in an Entity Framework 6 context (original answer was for EF4).
(Additional answer as opposed to edit due to character limit per answer)
Thank you Chris for your input. I have recently revisited this as a project using this tool has been upgraded to EF6. The full code for use with EF6 is copied below.
Note that this program code now operates on two files - the .edmx and the .edmx.diagram file. Visual Studio 2013 splits the diagram out into a separate file, and this needs editing otherwise the table / entity representations will not show up on the .edmx designer surface. The file path for the designer file is accepted as the second argument.
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
namespace EdmxStringFormatter
{
public class Program
{
static void Main(string[] args)
{
if (args.Length < 1) return;
string filePath = args[0];
string designerFilePath = null;
string entityName = null;
// Optionally do not replace underscores which
// helps with naming collisions with siimilarly named
// columns on some database tables.
bool replaceUnderscores = true;
// Allow for the replacement of the object context class name, which is useful
// where multiple databases have edmx files.
bool doEntityNameReplace = false;
if (args.Length > 1)
{
designerFilePath = args[1];
}
if (args.Length > 2)
{
entityName = args[2];
doEntityNameReplace = true;
}
if (args.Length > 3)
{
replaceUnderscores = args[3] != "0";
}
if (!File.Exists(filePath))
{
StopWithMessage("Could not find specified file.");
return;
}
if (Path.GetExtension(filePath) != ".edmx")
{
StopWithMessage("This works only on EDMX files.");
return;
}
TransformEdmx(filePath, replaceUnderscores, doEntityNameReplace, entityName);
TransformEdmxDiagram(designerFilePath, replaceUnderscores);
}
private static void TransformEdmx(string filePath, bool replaceUnderscores, bool doEntityNameReplace, string entityName)
{
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
//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";
//const string CSNameSpace = "http://schemas.microsoft.com/ado/2008/09/mapping/cs";
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2009/11/edm";
const string MSLNamespace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
const string CSNameSpace = "http://schemas.microsoft.com/ado/2009/11/mapping/cs";
XElement csdl = xdoc.Descendants(XName.Get("Schema", CSDLNamespace)).First();
XElement msl = xdoc.Descendants(XName.Get("Mapping", MSLNamespace)).First();
//modifications for renaming everything, not just table names:
#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 = FormatString(entitySet.Attribute("Name").Value, replaceUnderscores);
entitySet.Attribute("EntityType").Value = FormatString(entitySet.Attribute("EntityType").Value,
replaceUnderscores);
}
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 = FormatString(end.Attribute("EntitySet").Value, replaceUnderscores);
}
}
Console.WriteLine(" - modifying entity types...");
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = FormatString(entityType.Attribute("Name").Value, replaceUnderscores);
foreach (var key in entityType.Elements(XName.Get("Key", CSDLNamespace)))
{
foreach (var propertyRef in key.Elements(XName.Get("PropertyRef", CSDLNamespace)))
{
propertyRef.Attribute("Name").Value = FormatString(propertyRef.Attribute("Name").Value,
replaceUnderscores);
}
}
foreach (var property in entityType.Elements(XName.Get("Property", CSDLNamespace)))
{
property.Attribute("Name").Value = FormatString(property.Attribute("Name").Value, replaceUnderscores);
}
foreach (var navigationProperty in entityType.Elements(XName.Get("NavigationProperty", CSDLNamespace)))
{
navigationProperty.Attribute("Name").Value = FormatString(navigationProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
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 = FormatString(end.Attribute("Type").Value, replaceUnderscores);
}
foreach (var propref in association.Descendants(XName.Get("PropertyRef", CSDLNamespace)))
{
//propertyrefs are contained in constraints
propref.Attribute("Name").Value = FormatString(propref.Attribute("Name").Value, replaceUnderscores);
}
}
#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 = FormatString(entitySetMapping.Attribute("Name").Value,
replaceUnderscores);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = FormatString(entityTypeMapping.Attribute("TypeName").Value,
replaceUnderscores);
foreach
(var scalarProperty in
(entityTypeMapping.Element(XName.Get("MappingFragment", MSLNamespace))).Elements(
XName.Get("ScalarProperty", MSLNamespace))
)
{
scalarProperty.Attribute("Name").Value = FormatString(scalarProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
}
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 = FormatString(scalarProperty.Attribute("Name").Value,
replaceUnderscores);
}
}
}
#endregion
// Optionally replace the entity name in case the default of "Entity" is not
// sufficient for your needs.
if (doEntityNameReplace)
{
Console.WriteLine("Modifying entity name refs...");
// CSDL
xdoc.Descendants(XName.Get("EntityContainer", CSDLNamespace)).First().Attribute("Name").Value = entityName;
// Diagram
var diagramDescendants = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).FirstOrDefault();
if (diagramDescendants != null)
{
diagramDescendants.Attribute("Name").Value = entityName;
}
// Diagram
xdoc.Descendants(XName.Get("EntityContainerMapping", CSNameSpace)).First().Attribute("CdmEntityContainer").Value
= entityName;
}
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
private static void TransformEdmxDiagram(string filePath, bool replaceUnderscores)
{
// Processing:
Console.WriteLine("Creating backup: " + Path.ChangeExtension(filePath, ".bak"));
File.Copy(filePath, Path.ChangeExtension(filePath, ".bak"), true);
Console.WriteLine("Reading target document...");
XDocument xdoc = XDocument.Load(filePath);
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2009/11/edmx";
XElement designerDiagram = xdoc.Descendants(XName.Get("Diagram", DiagramNamespace)).FirstOrDefault();
#region Designer
Console.WriteLine("Modifying designer content...");
if (designerDiagram != null)
{
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = FormatString(item.Attribute("EntityType").Value, replaceUnderscores);
}
}
#endregion
Console.WriteLine("Writing result...");
using (XmlTextWriter writer = new XmlTextWriter(filePath, Encoding.Default))
{
writer.Formatting = Formatting.Indented;
xdoc.WriteTo(writer);
}
}
/// <summary>
/// Formats the string to pascal case, additionally checking for a period
/// in the string (in which case it skips past the period, which indicates
/// the use of namespace in a string.
/// </summary>
/// <param name="str"></param>
/// <param name="replaceUnderscores"></param>
/// <returns></returns>
private static string FormatString(string str, bool replaceUnderscores = true)
{
char[] chars = str.ToCharArray();
var sb = new StringBuilder();
bool previousCharWasUpper = false;
bool lastOperationWasToLower = false;
int startPos = 0;
if (str.Contains("."))
{
if (str.IndexOf(".") < (str.Length - 1))
{
startPos = str.IndexOf(".") + 1;
}
sb.Append(str.Substring(0, startPos));
}
for (int i = startPos; i < chars.Length; i++)
{
char character = chars[i];
if (Char.IsLetter(character))
{
if (Char.IsLower(character))
{
bool toUpper = false;
if (i > 0)
{
// Look at the previous char to see if not a letter
if (!Char.IsLetter(chars[i - 1]))
{
toUpper = true;
}
}
if (i == 0 || toUpper)
{
character = Char.ToUpper(character);
lastOperationWasToLower = false;
}
}
else // IsUpper = true
{
if (previousCharWasUpper || lastOperationWasToLower)
{
character = Char.ToLower(character);
lastOperationWasToLower = true;
}
}
previousCharWasUpper = Char.IsUpper(character);
sb.Append(character);
}
else
{
if (Char.IsDigit(character))
{
sb.Append(character);
previousCharWasUpper = false;
lastOperationWasToLower = false;
}
else if (!replaceUnderscores)
{
if (character == '_')
{
sb.Append(character);
}
}
}
}
return sb.ToString();
}
private static void StopWithMessage(string str)
{
Console.WriteLine(str);
Console.ReadLine();
throw new InvalidOperationException("Cannot continue.");
}
}
}