If we’ll skip exact details, we can say, that internal behavior of whole modeling and mapping is based on views (attention, we’re not talking about views in i.e. SQL databases!). Mapping is compiled into bidirectional views. These views express entities in form of tables (one direction – so called query views) and tables in form of entities (other direction – so called update views). Among others these views satisfy entity = query_view(update_view(entity)). As you might expect, generating this views isn’t simple. More important, these views are generated in runtime. So if you have big model with a lot of associations and entities, the generating can take significant time. For my test, overgrown sort of
, model with 400+ entities and 300+ associations it takes 20 seconds.
Luckily there’s a solution. These views can be generated using EdmGen or EdmGen2 (or your own tool). For this case I’ll use EdmGen2, because the work is slightly more comfortable, because you can work directly with EMDX file.
OK, so create model of your favorite database and create pre-build action in Visual Studio and add:
cd "$(ProjectDir)"
"$(SolutionDir)EdmGen2.exe" /ViewGen cs "$(ProjectDir)BigModel.edmx"
First row presets working directory to current project’s directory, because EdmGen2 doesn’t allows you to specify the name of output file (but you can change this, because sources are available). Next we’ll simply call EmdGen2 with /ViewGen parameter and specify language and path to EDMX file. After first compilation we’ve got file named <model name>.GeneratedViews.<language>. Finally just add this file into your project and compile again.
If you’ll run the application, you might notice nice surprise – it’s faster. With my model the time has been shortened to 2 seconds. And of course, this depends on your model, but not bad for a simple smart calling of tool without any additional work. If you’re interested in overall cost of various processes in Entity Framework, read [1] or [2], maybe [3].
[1] http://blogs.msdn.com/adonet/archive/2008/02/04/exploring-the-performance-of-the-ado-net-entity-framework-part-1.aspx
[2] http://blogs.msdn.com/adonet/archive/2008/02/11/exploring-the-performance-of-the-ado-net-entity-framework-part-2.aspx
[3] http://blogs.msdn.com/adonet/archive/2008/03/27/ado-net-entity-framework-performance-comparison.aspx
Originally written for vyvojar.cz.
Today there was a question in db section in builder.cz forum. In essence, how to show people having birthday today or in couple of days if I have a birthdate. Well the solution I’ll show you here is in Firebird syntax, but shouldn’t be a problem to rewrite it to any other platform you like.
So the solution is like this (of course, you can find many other ways how to solve it):
select dateadd(year, datediff(year, birthdate, current_date), birthdate) from
(
select cast('1.2.1993' as date) as birthdate from rdb$database
);
OK, what’s the idea behind. First step is to get difference between these dates in year. Then, using the dateadd function to prevent some mismatch in date arithmetics (i.e. leap years), to add this difference to birthdate. Now you have it in “current year”, so it’s easy to test whether it’s between today and today + x. To improve speed, you can create computed index for this expression.
Some databases behave according to standard so without quoting (which is a good way to hell), you got all in uppercase in your new shinny model generated from database (and yes, you should start modeling in empty model and define mapping later – just not to be screwed with relational world). Anyway a lot of people, including me, is generating model from database and then changing it.
But spending first hour or so of your work with renaming entities (or properties …) isn’t much fun. With same experience came guys from SMS-Timing, because they’re testing Entity Framework support for Firebird right now (and Firebird is behaving according to standard in this). Hence I’ve got the idea to make these identifiers little bit more code and developer friendly.
Because the *.EDMX file is just a couple of XML files together (CSDL, MSL, SSDL and designer stuff) you can tweak it by hand or by simple program. To get rid of all in uppercase I’ve created simple code (or program if you compile it yourself). It’s a simple program transforming names of entities to titlecase and also removing underscores with capitalizing next character.
static void Main(string[] args)
{
if (args.Length != 2)
return;
if (!File.Exists(args[0]))
return;
if (File.Exists(args[1]))
return;
XDocument xdoc = XDocument.Load(args[0]);
const string CSDLNamespace = "http://schemas.microsoft.com/ado/2006/04/edm";
const string MSLNamespace = "urn:schemas-microsoft-com:windows:storage:mapping:CS";
const string DiagramNamespace = "http://schemas.microsoft.com/ado/2007/06/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();
Func<string, string> transformation = (string input) =>
{
Regex re = new Regex(@"(w+)(W*?)$", RegexOptions.None);
return re.Replace(input, new MatchEvaluator(
(Match m) =>
{
string replace = m.Groups[1].Value;
StringBuilder result = new StringBuilder(m.Length);
for (int i = 0; i < replace.Length; i++)
{
if ((i == 0) ||
(i > 0 && replace[i - 1] == '_'))
{
result.Append(char.ToUpper(replace[i]));
}
else if (replace[i] == '_')
{
continue;
}
else
{
result.Append(char.ToLower(replace[i]));
}
}
result.Append(m.Groups[2].Value);
return result.ToString();
}));
};
#region CSDL
foreach (var entitySet in csdl.Element(XName.Get("EntityContainer", CSDLNamespace)).Elements(XName.Get("EntitySet", CSDLNamespace)))
{
entitySet.Attribute("Name").Value = transformation(entitySet.Attribute("Name").Value);
entitySet.Attribute("EntityType").Value = transformation(entitySet.Attribute("EntityType").Value);
}
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 = transformation(end.Attribute("EntitySet").Value);
}
}
foreach (var entityType in csdl.Elements(XName.Get("EntityType", CSDLNamespace)))
{
entityType.Attribute("Name").Value = transformation(entityType.Attribute("Name").Value);
}
foreach (var association in csdl.Elements(XName.Get("Association", CSDLNamespace)))
{
foreach (var end in association.Elements(XName.Get("End", CSDLNamespace)))
{
end.Attribute("Type").Value = transformation(end.Attribute("Type").Value);
}
}
#endregion
#region MSL
foreach (var entitySetMapping in msl.Element(XName.Get("EntityContainerMapping", MSLNamespace)).Elements(XName.Get("EntitySetMapping", MSLNamespace)))
{
entitySetMapping.Attribute("Name").Value = transformation(entitySetMapping.Attribute("Name").Value);
foreach (var entityTypeMapping in entitySetMapping.Elements(XName.Get("EntityTypeMapping", MSLNamespace)))
{
entityTypeMapping.Attribute("TypeName").Value = transformation(entityTypeMapping.Attribute("TypeName").Value);
}
}
#endregion
#region Designer
foreach (var item in designerDiagram.Elements(XName.Get("EntityTypeShape", DiagramNamespace)))
{
item.Attribute("EntityType").Value = transformation(item.Attribute("EntityType").Value);
}
#endregion
using (XmlTextWriter writer = new XmlTextWriter(args[1], Encoding.Default))
{
xdoc.WriteTo(writer);
}
}
The code is pretty simple. It just goes to thru the file and changes what needs to be changed. Mainly CSDL and MSL parts. Changes in designer part are not necessary, but you’ll lose positions etc. when you don’t do it. This is kind of this-afternoon-code – maybe I forgot something, maybe the RE is matching too much… Feel free to report problems in comments.
If you want to change also names of properties you can either tweak the code yourself or ask in comments, if I found some time, I’ll extend it. And of course, if you have some special naming convention in your company (i.e. every table has T_ prefix), just change the transformation function.
Firebird 2.1 has a feature called monitoring tables. You can look at what’s going on inside the database – like connections, running commands etc. For mon$attachments there’s column called mon$remote_process, where you can find path (from client POV, of course) to the program. But this needs cooperation from client. And from now, FirebirdClient (ADO.NET provider for Firebird) does this cooperation. So when you connect to Firebird with your .NET application, you (or maybe better admin) is able to look at above mentioned table(s) and see what commands is your application executing etc.