Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Entity Validation and the Entity Framework – Part 1

DZone's Guide to

Entity Validation and the Entity Framework – Part 1

· DevOps Zone
Free Resource

Download the blueprint that can take a company of any maturity level all the way up to enterprise-scale continuous delivery using a combination of Automic Release Automation, Automic’s 20+ years of business automation experience, and the proven tools and practices the company is already leveraging.

Introduction

The next logical step in my journey across the Entity Framework v6 RC1 after having established a fairly robust data access approach was to imbue my solution with some more “smarts” when it came to entity validation.

Out-of-the-box Entity Framework POCO entities are exactly as advertised – plain old CLR objects – and, as such, do not contain any inherent validation hints or help.

There are a plethora of validation approaches available, including extending the entity classes with partial classes, but I wanted something implemented which would be core to the data access approach – and (because I’m lazy) something that didn’t require any hand written code, outside of modifying a template.  After all, the EDMX already knows about nullable fields and field lengths, right?  Why not take advantage of that knowledge?

To be clear – this is not an approach designed to validate entities in an interactive fashion as there are other approaches which would work better, including UI validation.  Instead, this is an implementation designed to catch (and reject) any inserts or updates which would genuinely fail if an attempt to commit the changes was made – i.e. this is a last ditch sanity check.

Interrogating the T4 template

To accomplish what I want to do, you’ll have to be comfortable hand editing the T4 template which is used to generate the Entity Framework POCO entities.  If you haven’t done a lot of templating before, this might seem a bit daunting at first.

Untitled_thumb[2]

If you expand the <DataModel>.edmx file, you’ll see there are a host of linked dependency files.  The one we’re interested in specifically is called Model.tt and it is a direct dependency of the EDMX file itself.

There’s a fairly obvious format to the template, and you can actually play with the template and see the results.  Every time you save the template it triggers the templating engine – but be warned, any errors will break the template and you’ll get an error message.

An Example Schema Object

Let’s look at an example from my previous data schema.  As you can see, nothing terribly scary here, but there are a couple of required fields (not-nullable) and some fields which have defined lengths (we’re focusing on strings here).

image_thumb5

What I’m aiming to produce are some Data Annotations decorating the appropriate properties on my corresponding Catalog data entity, which would look like this (apologies for the text wrapping):

// Simple Properties

[Required(ErrorMessage="[Property 'CatalogId' in entity 'Catalog' is a required property.]")]
public int CatalogId { get; set; }
        
[StringLength(100, ErrorMessage="[The field 'Title' in entity 'Catalog' cannot be longer than 100 characters.]")]
[Required(ErrorMessage="[Property 'Title' in entity 'Catalog' is a required property.]")]
public string Title { get; set; }
        
[StringLength(400, ErrorMessage="[The field 'Description' in entity 'Catalog' cannot be longer than 400 characters.]")]
public string Description { get; set; }

public Nullable<System.DateTime> DateTaken { get; set; }
        
[StringLength(50, ErrorMessage="[The field 'OriginalFilename' in entity 'Catalog' cannot be longer than 50 characters.]")]
public string OriginalFilename { get; set; }

public Nullable<int> Orientation { get; set; }

// End Simple Properties

To accomplish this I also need the class implementation to use the following namespace:

using System.ComponentModel.DataAnnotations;

Modifying the T4 Template

There are two key changes which need to be made.  The first is to insert the namespace – which I’ve done in a less than graceful manner by just dropping it into the template right after the BeginNamespace call:

foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>
(itemCollection))
{
    fileManager.StartNewFile(entity.Name + ".cs");
    BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
using System.ComponentModel.DataAnnotations;

<#=codeStringGenerator.EntityClassOpening(entity)#> : EntityBase
{

Two things to note here – in bold – one is the base class I added previously, and the second is the DataAnnotations namespace.  Next, we’ll add the logic to prefix properties with the annotations we desire for validation.

You’re looking to move beyond the default constructor and find a section which looks like this:

foreach (var edmProperty in simpleProperties)
{

This section handles generation of (simple) properties for the entity.  There are two main conditions we want to check for – non-nullable properties (required fields) and fields with a fixed length.  Here is the full (modified) code for the section of the T4 template which generates simple properties:

Note: I started writing this by hand until found this problem  solved in the following article, but I modified the annotations to provide a bit more context (including the property names and source entity type).  What you see here is more or less copied from the linked article.

var simpleProperties = typeMapper.GetSimpleProperties(entity);
if (simpleProperties.Any())
{
foreach (var edmProperty in simpleProperties)
{
// begin max length attribute
if (code.Escape(edmProperty.TypeUsage) == "string")
{
int maxLength = 0;
if (edmProperty.TypeUsage.Facets["MaxLength"].Value != null && 
    Int32.TryParse(edmProperty.TypeUsage.Facets["MaxLength"].Value.ToString(), out maxLength))
{
#>    
    [StringLength(<#=code.CreateLiteral(maxLength)#>, 
    ErrorMessage="[The field '<#=edmProperty.Name#>' in entity '<#=entity.Name#>' cannot be longer than <#=code.CreateLiteral(maxLength)#> characters.]")]
<#
}
}
    // begin required attribute
  if (edmProperty.TypeUsage.Facets["Nullable"].Value.ToString() =="False")
  {
#>
    [Required(ErrorMessage="[Property '<#=edmProperty.Name#>' in entity '<#=entity.Name#>' is a required property.]")]
<#
    }
#>    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
    }
#>

Again, apologies for the wrapped and poorly indented code – limitations of this blogging format unfortunately.  I’ve included my copy of my model.tt below so you can get a properly formatted version.  Code in italics is original non-modified template code.

The Outcome

This produces the following generated entity – with data annotations:

// Simple Properties
[Required(ErrorMessage="[Property 'CatalogId' in entity 'Catalog' is a required property.]")]
public int CatalogId { get; set; }
        
[StringLength(100, ErrorMessage="[The field 'Title' in entity 'Catalog' cannot be longer than 100 characters.]")]
[Required(ErrorMessage="[Property 'Title' in entity 'Catalog' is a required property.]")]
public string Title { get; set; }
        
[StringLength(400, ErrorMessage="[The field 'Description' in entity 'Catalog' cannot be longer than 400 characters.]")]
public string Description { get; set; }
public Nullable<System.DateTime> DateTaken { get; set; }
        
[StringLength(50, ErrorMessage="[The field 'OriginalFilename' in entity 'Catalog' cannot be longer than 50 characters.]")]
public string OriginalFilename { get; set; }
public Nullable<int> Orientation { get; set; }
// End Simple Properties

Summary

We’re not done just yet.. but this is a start.  In part 2, I’ll be showing how this effort is put to use, by integrating it into my previously documented data access approach.  Next article should be up soon.

My Template

Here’s my [ T4 Template ]

Massively Helpful

Download the ‘Practical Blueprint to Continuous Delivery’ to learn how Automic Release Automation can help you begin or continue your company’s digital transformation.

Topics:

Published at DZone with permission of Rob Sanders, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}