Reverse Engineering a database into code

Coordinator
Nov 15, 2012 at 4:06 PM
Edited Nov 15, 2012 at 4:09 PM

As of NOF 5.0, we now provide full support for the 'Code First' approach to using Entity Framework.  It is increasingly apparent that this is where Microsoft is now also making the most investment, and our recommendation is that all users of Naked Objects adopt this for their projects .  (I am in the process of updating the documentation to this effect).

The term 'Code First' is mis-leading  -  it really means 'using Entity Framework without an .edmx file'  -  it does not mean that you have to start with the code. If you are building a domain model to work against an existing database schema (or partial schema) then you can still work with the 'code first' configuration (don't blame us, blame Micosoft for this confusion!)

The good news is that if you are using a licensed version of Visual Studio (either 2010 or 2012), then you can use the Entity Framework Power Tools plug in, available from http://visualstudiogallery.msdn.microsoft.com/72a60b14-1581-4b9b-89f2-846072eff19d , which includes a powerful mechanism to reverse engineer into code.  (Unfortunately, this plug-in can't be installed into the free Visual Studio Express edition.) The tool uses T4 templates to create the classes corresponding to entities, the DbContext and the mappings (using the 'fluent API').

However, if you use the default templates that come with the tool, the partial classes created will not work with Naked Objects because:

1.  It does not make properties virtual

2. The created classes initialise any collections in the constructor.  (In Naked Objects  there is no need to write a custom constructor for a domain class, and doing stuff in a constructor can cause significant problems, so best not to do it at all).

To fix this, I have just created an alternative implementation of the Entities.tt template, which creates entity classes compatible with Naked Objects and also adds a number of common attributes such as [MemberOrder] and [Mask].  This will be included in the next release of Naked Objects, but I have attached it here for anyone who wants to start using it now.

To replace the standard one used by the EF Power Tools Reverse Engineering function, I suggest you first read Rowan Miller's blog post here:  http://romiller.com/2012/05/09/customizing-reverse-engineer-code-first-in-the-ef-power-tools/

Then:

- on the project, invoke:  Entity Framework –> Customize Reverse Engineer Templates

- replace the Entity.tt file that has been added with the Naked Objects version included here

- transform all templates

 ------------------------------

Here's the replacement code for Entity.tt.  BTW this has had minimal testing so far  -  it will be better tested when we include it in the release.

<#@ template hostspecific="true" language="C#" #>
<#@ include file="EF.Utility.CS.ttinclude" #><#@
 output extension=".cs" #><#

    var efHost = (EfTextTemplateHost)Host;
    var code = new CodeGenerationTools(this);
#>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using NakedObjects;

namespace <#= code.EscapeNamespace(efHost.Namespace) #>
{
	
    public class <#= efHost.EntityType.Name #>
    {
	
	#region Injected Services
	
	#endregion
	
	#region LifeCycle Methods
	
	#endregion
<#    
    foreach (var property in efHost.EntityType.Properties)
    {
		#>
		
<#
		CodeRegion region = new CodeRegion(this, 1);
		string edmType  = property.TypeUsage.ToString().Substring(4);
		region.Begin(code.Escape(property)+" ("+edmType+")");
		
		StartAttributeLine();
	    AddAttribute(NextMemberOrder());
	    if (property.Nullable) { 
			AddAttribute("Optionally");
	    }
	    if (property.TypeUsage.ToString().Equals("Edm.String")) {
	    	int maxLength = 0;
			var facetValue = property.TypeUsage.Facets.Where(x => x.Name == "MaxLength").First().Value;
			if (facetValue != null) {
	    		if (Int32.TryParse(facetValue.ToString(), out maxLength)) { 
					AddAttribute("StringLength("+maxLength.ToString()+")");
	   			}
			}
	  	} 
		if (property.TypeUsage.ToString().Equals("Edm.DateTime")) {
	    AddAttribute("Mask(\"d\")");
	    }
		OutputAttributeLine(); 
		#>
        <#= Accessibility.ForProperty(property) #> <#= code.Escape(property.TypeUsage) #> <#= code.Escape(property) #> { get; set; }
<#
    	region.End();
	}

    foreach (var navProperty in efHost.EntityType.NavigationProperties.Where(np => np.DeclaringType == efHost.EntityType))
    {
		#>
		
<#
        if (navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
        {
			//Render a collection  
			CodeRegion region = new CodeRegion(this, 1);
		    EntityType toType = navProperty.ToEndMember.GetEntityType();
			region.Begin(code.Escape(navProperty)+" (Collection of "+toType.Name+")"); #>
		private ICollection<<#=code.Escape(toType) #>> <#=code.FieldName(navProperty)#> = new List<<#=code.Escape(toType) #>>();
<#
			StartAttributeLine();
		    AddAttribute(NextMemberOrder());
			AddAttribute("Disabled");
			OutputAttributeLine(); 
			#>
        public virtual ICollection<<#= code.Escape(navProperty.ToEndMember.GetEntityType()) #>> <#= code.Escape(navProperty) #> { get; set; }
<#
        	region.End();
		}
        else
        {
			//Render a (single) reference property
			CodeRegion region = new CodeRegion(this, 1);	
	    	EntityType toType  = navProperty.ToEndMember.GetEntityType(); 
			region.Begin(code.Escape(navProperty)+" ("+toType.Name+")");#>
		
<# 
		    StartAttributeLine();
		    AddAttribute(NextMemberOrder());
			OutputAttributeLine(); 
#>
        public virtual <#= code.Escape(navProperty.ToEndMember.GetEntityType()) #> <#= code.Escape(navProperty) #> { get; set; }
<#
        	region.End();
		}
    }
#>
    }
}
<#+  
//Building attributes
private StringBuilder attBuild; 

private void StartAttributeLine() {
	attBuild = new StringBuilder();
	attBuild.Append("[");
}

private void AddAttribute(string att) {
	if (attBuild.Length > 1) {
		attBuild.Append(", ");
	}
	attBuild.Append(att);
}

private void OutputAttributeLine() {
	if (attBuild.Length == 1) {
		return; //It means no attribute has been added to the line
    }
	attBuild.Append("]"); #>
<#= attBuild.ToString() #>
<#+    attBuild = null;
}   

//MemberOrder
private int count; 
private int increment = 10;

private void InitialiseMemberOrder() {
   count = 100; 
}

private string NextMemberOrder() {
	int nmo = count;
	count = count + increment;
	return "MemberOrder("+nmo.ToString() +")";
}	
#>