3
\$\begingroup\$

This is Part 1 of a question about performance and general best practices. Part 2 is located here. The body of the messages are the same to illustrate their similarity, the difference is the code provided. This one provides the data the text template utilizes, the other provides the logic to generate the code. The focus isn't so much on the template, but the pattern of managing a database that tracks changes across potentially multiple domains.

Another thing I decided I'll be adding: the templates will construct the basic outline of the dialogs through WPF for me. In the case of searching for items within the database, I've already used the data on the entities to generate query builders:

var recipeQuery = new RecipeQueryData(user);
recipeQuery.CreateIngredientsQueryData();
recipeQuery.IngredientsData.CreateItemQueryData();
recipeQuery.IngredientsData.ItemData.NameCriteria = "t";
recipeQuery.IngredientsData.ItemData.SearchNameType = StringSearchType.Contains;
var recipesWithT2 = RecipeQueryData.ConstructQuery(recipeQuery, fcasCtx.Recipes);

Compared with:

var recipesWithT =
    (from recipe in fcasCtx.Recipes
     from ingredient in recipe.Ingredients
     where ingredient.Item.Name.Contains(criteria)
     select recipe).Distinct();

Console.WriteLine(recipesWithT.ToString() == recipesWithT2.ToString());

Yields true. While by itself isn't much, it's interesting that I'll be able to enable a user to have a fairly powerful search tool by specifying nested conditions.

I've uploaded a small example which demonstrates the template's use. It's changed quite a lot since the code posted and edited by lol.upvote. It automatically generates the files and folders based off of the context namespace provided in EntityConstants.tt. To preserve space I've removed all binaries, so you might have to fix it in the package manager console when you load the solution.

I'm kind of new to the realm of Database authoring, so I wanted to get some feedback on a system which generates auditable entities.

I'm using Visual Studio 2013 with the Entity Framework 6.0.2. Handling Migrations through the Package Manager Console. I'm rather unfamiliar with EF Performance, and what such a change tracking system would do. I know that I could enable the change tracking on the server, but this system will involve tracking who changed what, based off of their identity defined within the database (and honestly, I don't understand the SQL server change tracking one bit.) It's meant to focus on potentially multiple domains, so whoever would use the client would login using SQL Authentication. The end result is Active Directory supplies the authentication, and users are handled by storing their SID within the registry which links to the proper domain.

A portion of the logic from Part 2 was moved here for space reasons when it was updated to the most recent version of the code.

Template

Header

<#@ template debug="true" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using AllenCopeland.FCAS.Administration;
using AllenCopeland.FCAS.Instances;
<#
const long ER_NONE                                  = 0x000,
           ER_NOTTRACKED                            = 0x001,
           ER_REQUIRED                              = 0x002,
           ER_MAXLEN                                = 0x004,
           ER_KEY                                   = 0x008,
           ER_FIXED                                 = 0x010 | ER_NOTTRACKED,
           ER_COLLECTION                            = 0x020 | ER_NOTTRACKED,
           ER_COLLECTION_BIDIRECTIONAL              = 0x040 | ER_COLLECTION,
           ER_COLLECTION_UNIDIRECTIONAL_REQUIRED    = 0x080 | ER_COLLECTION,
           ER_COLLECTION_UNIDIRECTIONAL_OPTIONAL    = 0x100 | ER_COLLECTION,
           ER_IGNORE_ON_ADDMETHOD                   = 0x200,
           ER_REQUIRED_NO_ATTRIBUTE                 = 0x400 | ER_REQUIRED;
var nullableTypes = new string[] { "bool", "byte", "short", "int", "long", "decimal", "double", "float" };
const string ContextName = "FCASDBContext";
const string ContextNamespace = "AllenCopeland.FCAS";
const string authorityEntity = "DomainUser";
const string authorityAuthIDFieldName = "AuthUserID";
const string authorityAuthIDFieldValue = "AuthorizerID";
var shortformLookup = new Dictionary<string, string>() 
    { 
        { "string",     "System.String"     },
        { "bool",       "System.Boolean"    },
        { "byte",       "System.Byte"       },
        { "short",      "System.Int16"      },
        { "int",        "System.Int32"      },
        { "long",       "System.Int64"      },
        { "decimal",    "System.Decimal"    },
        { "double",     "System.Double"     },
        { "float",      "System.Single"     },
    };

Entities

var entities = new [] 
{
    new { 
            Namespace             = "AllenCopeland.FCAS.Stock",
            EntityName            = "DistributorInventoryItem",
            PluralName            = "DistributorInventoryItems",
            PluralInstanceName    = "DistributorInventoryItemInstances",
            Properties            = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"DistributorInventoryItem\" />"}, 
                new { Name = "Price",               DataType = "decimal",       Requirements = ER_REQUIRED,     Description = "of the price of the <see cref=\"DistributorInventoryItem\" />" },
                new { Name = "PriceByWeight",       DataType = "bool",          Requirements = ER_REQUIRED,     Description = "on whether the <see cref=\"DistributorInventoryItem.Price\" /> is charged by weight." },
                new { Name = "Quantity",            DataType = "int",           Requirements = ER_REQUIRED,     Description = "of the number of items received relative to each <see cref=\"DistributorInventoryItem.Pack\" />" },
                new { Name = "QuantityMax",         DataType = "int",           Requirements = ER_NONE,         Description = "of the maximum number of items received relative to each <see cref=\"DistributorInventoryItem.Pack\" />" },
                new { Name = "Pack",                DataType = "int",           Requirements = ER_REQUIRED,     Description = "of the number of packs for the <see cref=\"DistributorInventoryItem\" /> received per each unit on an order." },
                new { Name = "Distributor",         DataType = "Distributor",   Requirements = ER_REQUIRED,     Description = "the <see cref=\"DistributorInventoryItem\" /> belongs to." },
                new { Name = "ProductID",           DataType = "int",           Requirements = ER_REQUIRED,     Description = "that represents the unique identifier of the <see cref=\"DistributorInventoryItem\" /> relative to its <see cref=\"Distributor\" />." },
                new { Name = "Brand",               DataType = "Brand",         Requirements = ER_REQUIRED,     Description = "that represents the unique identifier of the <see cref=\"DistributorInventoryItem\" /> relative to its <see cref=\"Distributor\" />." },
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace               = "AllenCopeland.FCAS.Stock",
            EntityName              = "Distributor",
            PluralName              = "Distributors",
            PluralInstanceName      = "DistributorInstances",
            Properties              = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"Distributor\" />." },
                new { Name = "Logo",                DataType = "DatabaseImage", Requirements = ER_NONE,         Description = "which represents the logo for the <see cref=\"Distributor\" />." },
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace               = "AllenCopeland.FCAS.Stock",
            EntityName              = "Brand",
            PluralName              = "Brands",
            PluralInstanceName      = "BrandInstances",
            Properties              = new []
            {
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"Brand\" />" }, 
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace               = "AllenCopeland.FCAS",
            EntityName              = "DatabaseImage",
            PluralName              = "DatabaseImages",
            PluralInstanceName      = "DatabaseImageInstances",
            Properties              = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"DatabaseImage\" />." }, 
                new { Name = "Description",         DataType = "string",        Requirements = ER_NONE,         Description = "of the description of the <see cref=\"DatabaseImage\" />." }, 
                new { Name = "Data",                DataType = "Byte[]",        Requirements = ER_NOTTRACKED | ER_REQUIRED,
                                                                                                                Description = "which contains the binary data of the <see cref=\"DatabaseImage\" />." }, 
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace = "AllenCopeland.FCAS.Administration",
            EntityName = "DomainUser",
            PluralName = "DomainUsers",
            PluralInstanceName = "DomainUserInstances",
            Properties = new []
            { 
                new { Name = "SecurityIdentifier",  DataType = "string",        Requirements = ER_KEY | ER_FIXED | ER_MAXLEN | (46L << 32) | ER_REQUIRED, 
                                                                                                                Description = "of the security identifier of the <see cref=\"DomainUser\" />" }, 
                new { Name = "Disabled",            DataType = "bool",          Requirements = ER_NONE | ER_IGNORE_ON_ADDMETHOD,
                                                                                                                Description = "of whether the <see cref=\"DomainUser\" /> is disabled." }, 
                new { Name = "Domain",              DataType = "Domain",        Requirements = ER_REQUIRED_NO_ATTRIBUTE | ER_FIXED, 
                                                                                                                Description = "which denotes the domain from which the <see cref=\"DomainUser\" /> is derived." }, 
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace = "AllenCopeland.FCAS.Administration",
            EntityName = "Domain",
            PluralName = "Domains",
            PluralInstanceName = "DomainInstances",
            Properties = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"Domain\" />" }, 
                new { Name = "Companies",           DataType = "Company",       Requirements = ER_COLLECTION_BIDIRECTIONAL, 
                                                                                                                Description = "set which denotes which companies have access to the <see cref=\"Domain\" />." }
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace = "AllenCopeland.FCAS.Administration",
            EntityName = "Company",
            PluralName = "Companies",
            PluralInstanceName = "CompanyInstances",
            Properties = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"Company\" />" }, 
                new { Name = "Description",         DataType = "string",        Requirements = ER_NONE,         Description = "of the descriptive text of the <see cref=\"Company\" />" }, 
                new { Name = "Domains",             DataType = "Domain",        Requirements = ER_COLLECTION_BIDIRECTIONAL, 
                                                                                                                Description = "set which denotes which domains the <see cref=\"Company\" /> has access to the ." }
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace = "AllenCopeland.FCAS.Stock",
            EntityName = "InventoryItem",
            PluralName = "InventoryItems",
            PluralInstanceName = "InventoryItemInstances",
            Properties = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"InventoryItem\" />" }, 
                new { Name = "Units",               DataType = "InventoryUnit", Requirements = ER_COLLECTION_BIDIRECTIONAL, 
                                                                                                                Description = "set which denotes the inventory units associated to the <see cref=\"InventoryItem\" />" }, 
                new { Name = "Categories",          DataType = "InventoryItemCategory", 
                                                                                Requirements = ER_COLLECTION_BIDIRECTIONAL, 
                                                                                                                Description = "set which denotes the inventory categories associated to the <see cref=\"InventoryItem\" />" }, 
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace = "AllenCopeland.FCAS.Stock",
            EntityName = "InventoryItemCategory",
            PluralName = "InventoryItemCategories",
            PluralInstanceName = "InventoryItemCategoryInstances",
            Properties = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"InventoryItem\" />" }, 
                new { Name = "Items",               DataType = "InventoryItem", Requirements = ER_COLLECTION_BIDIRECTIONAL, 
                                                                                                                Description = "set which denotes the inventory items associated to the <see cref=\"InventoryItemCategory\" />" }, 
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
    new {
            Namespace = "AllenCopeland.FCAS.Stock",
            EntityName = "InventoryUnit",
            PluralName = "InventoryUnits",
            PluralInstanceName = "InventoryUnitInstances",
            Properties = new []
            { 
                new { Name = "Name",                DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the name of the <see cref=\"InventoryUnit\" />" }, 
                new { Name = "PluralName",          DataType = "string",        Requirements = ER_REQUIRED,     Description = "of the plural name of the <see cref=\"InventoryUnit\" />" }, 
                new { Name = "ShortName",           DataType = "string",        Requirements = ER_NONE,         Description = "of the shortened name of the <see cref=\"InventoryUnit\" />" }, 
                new { Name = "Items",               DataType = "InventoryItem", Requirements = ER_COLLECTION_BIDIRECTIONAL, 
                                                                                                                Description = "set which denotes the inventory items associated to the <see cref=\"InventoryUnit\" />" }, 
            }.ToDictionary(keySelector => keySelector.Name, valueSelector => valueSelector),
        },
};

Followed by:

var orderedEntities = (from e in entities
                       orderby e.Namespace,
                               e.EntityName
                       group e by e.Namespace).ToDictionary(key=>key.Key, value=>value.ToArray());
int index = 0;
var indices = entities.ToDictionary(keySelector=>keySelector, valueSelector=>0);
foreach (var entity in from ns in orderedEntities.Keys
                       from entity in orderedEntities[ns]
                       select entity)
    indices[entity] = index++;
    var maxEL = index.ToString().Length;

Enums

#>
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
<#
foreach (var entityNamespace in orderedEntities.Keys)
{
#>
namespace <# WriteLine(entityNamespace);  #>
{
    using <# Write(entityNamespace);  #>.Instances;
    <#
    foreach (var entity in orderedEntities[entityNamespace])
    {
        var currentEntityIndex = indices[entity];
    #>
    /// <summary>
    /// Denotes the potential elements within a <see cref="<# Write(entity.EntityName); #>" />
    /// that can change.
    /// </summary>
    [Flags]
    public enum <# Write(entity.EntityName); #>Changes :
        <#
            int pC = entity.Properties.Count - entity.Properties.Values.Where(k=>(k.Requirements & ER_NOTTRACKED) == ER_NOTTRACKED).Count();
            if (pC <= 6)
                WriteLine("byte");
            else if (pC <= 13)
                WriteLine("short");
            else if (pC <= 29)
                WriteLine("int");
            else
                WriteLine("long");
        #>
    {
        /// <summary>
        /// The <see cref="<# Write(entity.EntityName); #>" /> was created.
        /// </summary>
        Created = 1,<#
        WriteLine("");
        int offset = 0;
        foreach (var property in entity.Properties)
        {
            if ((property.Value.Requirements & ER_NOTTRACKED) == ER_NOTTRACKED)
                continue;#>
        /// <summary>
        /// The <see cref="<# Write(string.Format("{0}.{1}", entity.EntityName, property.Key)); #>"/>
        /// was changed on the <see cref="<# Write(entity.EntityName); #>" />.
        /// </summary>
        <#WriteLine(string.Format("{0} = {1},", property.Key, Math.Pow(2, ++offset)));#><#}
        #>
        /// <summary>
        /// The <see cref="<# Write(entity.EntityName); #>.Deleted"/>
        /// was changed on the <see cref="<# Write(entity.EntityName); #>" />.
        /// </summary>
        Deleted = <# Write(Math.Pow(2, ++offset).ToString()); #>,
    }

Entity Classes

    public class <# Write(entity.EntityName); #> :
        GenericRootEntity<<# Write(entity.EntityName); #>, <# Write(entity.EntityName); #>Changes, <# Write(entity.EntityName); #>Instance>
    {

        <#if (entity.EntityName == authorityEntity) {#>internal const string <# Write(authorityAuthIDFieldName); #> = "<# Write(authorityAuthIDFieldValue); #>";<#}#>

        internal const string TableName = "[" + FCASDBContext.TablePrefix + "<# Write(entity.PluralName); #>]";

<# 
        foreach (var property in entity.Properties.Values)
        {
            if ((property.Requirements & ER_COLLECTION) == ER_COLLECTION)
            {#>
        /// <summary>
        /// Data member for <see cref="<# Write(property.Name); #>" />
        /// </summary>
        private ICollection<<# Write(property.DataType); #>> _<# Write(property.Name); #>;
<#}}#>

        public bool Deleted { get; protected set; }

<# 
        foreach (var property in entity.Properties)
        {#>


        /// <summary>
        /// Returns the <see cref="<#
        string shortForm;
        shortformLookup.TryGetValue(property.Value.DataType, out shortForm);
        shortForm = shortForm ?? property.Value.DataType;
        Write(shortForm);
        #>"/> value <# WriteLine(property.Value.Description); #>
        /// </summary>
<# 
            if ((property.Value.Requirements & ER_REQUIRED) == ER_REQUIRED && (property.Value.Requirements & ER_REQUIRED_NO_ATTRIBUTE) != ER_REQUIRED_NO_ATTRIBUTE){
                #>
        [Required()]
<#}
            if ((property.Value.Requirements & ER_KEY) == ER_KEY){
                #>
        [Key()]
<#}
            if ((property.Value.Requirements & ER_KEY) == ER_KEY){
                #>
        [MaxLength(<# Write(((property.Value.Requirements & 0x7FFFFFFF00000000L) >> 32).ToString()); #>)]
<#}
            if ((property.Value.Requirements & ER_FIXED) == ER_FIXED){
                #>
        [Editable(false)]
<#}
        if (((property.Value.Requirements & ER_COLLECTION) == ER_COLLECTION))
        {#>
        public virtual ICollection<<# Write(property.Value.DataType);#>> <# WriteLine(property.Key); #>
        {
            get
            {
                if (this._<# Write(property.Key); #> == null)
                    this._<# Write(property.Key); #> = new HashSet<<# Write(property.Value.DataType);#>>();
                return this._<# Write(property.Key); #>;
            }
            protected set
            {
                this._<# Write(property.Key); #> = value;
            }
        }<#
        }
        else
        {
            var targetEnt = entities.FirstOrDefault(ent=>ent.EntityName == property.Value.DataType);
        #>
        public <# WriteLine(string.Format("{3}{0} {1} {{ get; {2}set; }}", string.Format("{0}{1}", property.Value.DataType, (nullableTypes.Contains(property.Value.DataType) && (property.Value.Requirements & ER_REQUIRED) != ER_REQUIRED) ? "?" : string.Empty), property.Key, ((property.Value.Requirements & ER_NOTTRACKED) == ER_NOTTRACKED) ? string.Empty : "protected ", targetEnt != null ? "virtual " : string.Empty)); 
        }
        }

        foreach (var property in entity.Properties)
        {
        if ((property.Value.Requirements & ER_NOTTRACKED) == ER_NOTTRACKED)
            continue;#>

        internal void Set<# Write(property.Key); #>(<# Write(property.Value.DataType); #> value, <# Write(authorityEntity); #> authorizingUser)
        {
            if (this.<# Write(property.Key); #> == value)
                return;
            this.PropertyChange(<# Write(entity.EntityName); #>Changes.<# Write(property.Key); #>, this.<# Write(property.Key); #> = value, authorizingUser);
        }
        <#}#>


        internal void Delete(<# Write(authorityEntity); #> authorizingUser)
        {
            if (this.Deleted)
                return;
            this.PropertyChange(<# Write(entity.EntityName); #>Changes.Deleted, this.Deleted = true, authorizingUser);
        }

        internal void Undelete(<# Write(authorityEntity); #> authorizingUser)
        {
            if (!this.Deleted)
                return;
            this.PropertyChange(<# Write(entity.EntityName); #>Changes.Deleted, this.Deleted = false, authorizingUser);
        }
    }
<#
    }
#>
}
<#
}

The referenced GenericInstanceEntity is here.

The referenced GenericRootEntity is here.

The results of running the Text Template is here.

The referenced method and associated constant values are:

internal const string TablePrefix = "FCAS.";
internal const string TableInstancesPrefix = "FCAS.INST.";
internal const string TableMappingPrefix = "FCAS.MAP.";


internal static bool ValuesEqual<TValue>(TValue left, TValue right)
{
    var obj = (object)left;
    if (obj != null)
    if (typeof(IEquatable<TValue>).IsAssignableFrom(typeof(TValue)))
        return ((IEquatable<TValue>)left).Equals(right);
    else
        return left.Equals(right);
    else
    return ((object)right) == null;
}

I'm posting this because I'm curious if this is a decent path to follow for such a project, or if I'm sorely mislead. Bidirectional collections of entities are supported; however, single 1:many relationships aren't present within the template's construction. The means to which change tracking is managed is a bit convoluted; however, its goal is to simplify construction of such a model. I'm pretty sure if there's overhead it will mostly be due to the entity framework itself.

I'd like to know if I'm on the right track.

Deletion of primary entities is not allowed, for data retention reasons. The appearance of deletion is handled through flags.

Changes are tracked by Column, and only the columns that you state are, by your own design, malleable. It seems to make more sense than trying to use the Standard T-SQL change tracking, unless there's something I missed about it.

\$\endgroup\$
  • \$\begingroup\$ Please include the code you want reviewed, in your post - don't just link to it. Link to on-the-side stuff, not to the code you want reviewed. As it currently stands, this question could be put on hold until it's edited to include the code. \$\endgroup\$ – Mathieu Guindon Feb 16 '14 at 17:05
  • \$\begingroup\$ It's 560 lines for the template, and over 2544 lines for the resulted code the template generates. Is that still okay? \$\endgroup\$ – Alexander Morou Feb 16 '14 at 20:11
  • 1
    \$\begingroup\$ 560 lines should be fine. If you feel it's too much to review, you can break it down into multiple questions. \$\endgroup\$ – Mathieu Guindon Feb 16 '14 at 21:52
  • 2
    \$\begingroup\$ I just noticed (as I edited your post to split up the code block parts) your edit says The focus isn't so much on the template, but the pattern of managing a database that tracks changes across potentially multiple domains. - this would normally be off-topic on this site, we... review code ;) \$\endgroup\$ – Mathieu Guindon Feb 18 '14 at 3:48
  • 3
    \$\begingroup\$ Point. We'll focus on reviewing the code then. Nice clean edit, by the way, appreciate it. I'm putting together a more comprehensive example that focuses on the code exported by the template. I'll post back shortly to get it back on track. \$\endgroup\$ – Alexander Morou Feb 18 '14 at 3:59
2
\$\begingroup\$

I'm not familiar with template coding but I could follow this script all along, except this one-liner:

public <# WriteLine(string.Format("{3}{0} {1} {{ get; {2}set; }}", string.Format("{0}{1}", property.Value.DataType, (nullableTypes.Contains(property.Value.DataType) && (property.Value.Requirements & ER_REQUIRED) != ER_REQUIRED) ? "?" : string.Empty), property.Key, ((property.Value.Requirements & ER_NOTTRACKED) == ER_NOTTRACKED) ? string.Empty : "protected ", targetEnt != null ? "virtual " : string.Empty)); 

Doesn't parse in my poor little fried brain. I couldn't figure out where the protected was going to end up.

I need to break it down to see what it does:

WriteLine(
    string.Format("{3}{0} {1} {{ get; {2}set; }}", 
        string.Format("{0}{1}", 
            property.Value.DataType, 
            (nullableTypes.Contains(property.Value.DataType) 
             && (property.Value.Requirements & ER_REQUIRED) != ER_REQUIRED) 
                ? "?" 
                : string.Empty), 
        property.Key, 
        ((property.Value.Requirements & ER_NOTTRACKED) == ER_NOTTRACKED) 
                ? string.Empty 
                : "protected ", 
        targetEnt != null 
                ? "virtual " 
                : string.Empty)); 

How about at least putting the arguments in order?

WriteLine(
    string.Format("{0}{1} {2} {{ get; {3}set; }}", 
        targetEnt != null 
                ? "virtual " 
                : string.Empty
        string.Format("{0}{1}", 
            property.Value.DataType, 
            (nullableTypes.Contains(property.Value.DataType) 
             && (property.Value.Requirements & ER_REQUIRED) != ER_REQUIRED) 
                ? "?" 
                : string.Empty), 
        property.Key, 
        ((property.Value.Requirements & ER_NOTTRACKED) == ER_NOTTRACKED) 
                ? string.Empty 
                : "protected ")); 

If it's possible, I'd try to break this into something like 3 lines, be it only for readability.


Minor annoyance, the resulting code seems to have mixed tabs and spaces:

enter image description here

I don't like the redundant this qualifier sprinkled everywhere, but the code is very consistent about it, so it's only down to personal preference here.


I find this is a very interesting approach, however it's also a very intrusive one: if I understand your code correctly, your entities are being so much more than the regular average POCO out there.

This breaks the Single Responsibility Principle. I believe a decorator would be less intrusive, and more focused - basically compose the functionality rather than inheriting it.

\$\endgroup\$
  • \$\begingroup\$ Could you clarify on the point of a 'decorator'? The goal behind this approach is to enable a high fidelity auditable set of entities. Shortly I will update with the most recent version. This was my first foray with the T4 templating engine, so the newest version cleans a lot of it up and generates a file per type. Also: the generic base types were initially introduced to simplify writing this code by hand, are you saying I should merge the code with the template and clean it up some? \$\endgroup\$ – Alexander Morou Feb 18 '14 at 2:16
  • \$\begingroup\$ A decorator would encapsulate the functionality you're tucking into your base class. If you were to change your auditing strategy in the future, you've painted yourself in a corner with inheritance; with composition, a POCO remains just a POCO and the audit code and DomainUser dependencies can live in a ActiveDirectoryAuditEntity decorator; entities should have only 1 reason to change, and you've hard-wired them to AD - a decorator would shift this coupling to a class that explicitly says "Pick me! I work with AD!". \$\endgroup\$ – Mathieu Guindon Feb 18 '14 at 2:46
  • \$\begingroup\$ Perhaps I'm a bit confused because I'm unfamiliar with the idea of the decorator pattern...? The resulted instance that's constructed tracks each of the possible fields that could change and the entity handles detecting which exact fields have changed and persists only those changes. Which part of the overall design breaks this pattern? The reliance on the DomainUser, the use of a secondary class to handle change tracking, or something else? \$\endgroup\$ – Alexander Morou Feb 18 '14 at 2:58
  • \$\begingroup\$ @AlexanderMorou Maybe I didn't completely grasp what you're doing. Are your POCO classes (the entities EF is using) inherited from GenericRootEntity<TRoot, TRootProperties, TInstance>? \$\endgroup\$ – Mathieu Guindon Feb 18 '14 at 3:03
  • 1
    \$\begingroup\$ There's three types generated for each defined Entity. The Changes enum which is more of a helpful design tool when you're debugging so you can look at it to see what change is represented by a given Instance. The Primary type which derives from the GenericRootEntity3, and the Instance type which derives from GenericInstanceEntity3 in which inheritors mirror the structure of the TRoot Entity, except with nullable types as nullable always. The template generates the code to handle high fidelity auditing. \$\endgroup\$ – Alexander Morou Feb 18 '14 at 3:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.