Using INotifyPropertyChanged with Entity Framework 6 DbContext Generator

dbcontext entity-framework-6 inotifypropertychanged

Question

I know I can use ObjectContext instead, but I like the features of DbContext / DbSet. My application isn't large enough to warrant me writing complex view models, so I'd like to implement change notification on the EF generated models directly.

How can I achieve this?

1
7
9/17/2014 10:14:26 PM

Accepted Answer

This can be achieved by replacing the default T4 template (the .tt file automatically added by Entity Framework) with the following, then right clicking the .tt file and selecting "Run Custom Tool".

Important: This will regenerate your models which will cause any customizations to be discarded. I recommend you implement your customizations via the template rather than modifying the model class directly to prevent this from being a problem in the future.

Note: Replace string inputFile = @"MessageLog.edmx"; with the name of your edmx file.

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

CodeGenerationTools code = new CodeGenerationTools(this);
MetadataLoader loader = new MetadataLoader(this);
CodeRegion region = new CodeRegion(this, 1);
MetadataTools ef = new MetadataTools(this);

string inputFile = @"MessageLog.edmx";
EdmItemCollection ItemCollection = loader.CreateEdmItemCollection(inputFile);
string namespaceName = code.VsNamespaceSuggestion();

EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this);
WriteHeader(fileManager);

foreach (var entity in ItemCollection.GetItems<EntityType>().OrderBy(e => e.Name))
{
    fileManager.StartNewFile(entity.Name + ".cs");
    BeginNamespace(namespaceName, code);
#>
using System;
using System.Collections.Generic;
using System.ComponentModel;

<#=Accessibility.ForType(entity)#> <#=code.SpaceAfter(code.AbstractOption(entity))#>partial class <#=code.Escape(entity)#> : INotifyPropertyChanged<#=code.StringBefore(", ", code.Escape(entity.BaseType))#>
{
<#
    var propertiesWithDefaultValues = entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity && p.DefaultValue != null);
    var collectionNavigationProperties = entity.NavigationProperties.Where(np => np.DeclaringType == entity && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
    var complexProperties = entity.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == entity);

    if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any())
    {
#>
    public <#=code.Escape(entity)#>()
    {
<#
        foreach (var edmProperty in propertiesWithDefaultValues)
        {
#>
        this.<#=code.Escape(edmProperty)#> = <#=code.CreateLiteral(edmProperty.DefaultValue)#>;
<#
        }

        foreach (var navigationProperty in collectionNavigationProperties)
        {
#>
        this.<#=code.Escape(navigationProperty)#> = new ObservableListSource<<#=code.Escape(navigationProperty.ToEndMember.GetEntityType())#>>();
<#
        }

        foreach (var complexProperty in complexProperties)
        {
#>
        this.<#=code.Escape(complexProperty)#> = new <#=code.Escape(complexProperty.TypeUsage)#>();
<#
        }
#>
    }

<#
    }

    var primitiveProperties = entity.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == entity);
    if (primitiveProperties.Any())
    {
        foreach (var edmProperty in primitiveProperties)
        {
            WriteProperty(code, edmProperty);
        }
    }

    if (complexProperties.Any())
    {
#>

<#
        foreach(var complexProperty in complexProperties)
        {
            WriteProperty(code, complexProperty);
        }
    }

    var navigationProperties = entity.NavigationProperties.Where(np => np.DeclaringType == entity);
    if (navigationProperties.Any())
    {
#>

<#
        foreach (var navigationProperty in navigationProperties)
        {
            WriteNavigationProperty(code, navigationProperty);
        }
    }
#>

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, e);
    }
    #endregion
}
<#
    EndNamespace(namespaceName);
}

foreach (var complex in ItemCollection.GetItems<ComplexType>().OrderBy(e => e.Name))
{
    fileManager.StartNewFile(complex.Name + ".cs");
    BeginNamespace(namespaceName, code);
#>
using System;

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
{
<#
    var complexProperties = complex.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == complex);
    var propertiesWithDefaultValues = complex.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == complex && p.DefaultValue != null);

    if (propertiesWithDefaultValues.Any() || complexProperties.Any())
    {
#>
    public <#=code.Escape(complex)#>()
    {
<#
        foreach (var edmProperty in propertiesWithDefaultValues)
        {
#>
        this.<#=code.Escape(edmProperty)#> = <#=code.CreateLiteral(edmProperty.DefaultValue)#>;
<#
        }

        foreach (var complexProperty in complexProperties)
        {
#>
        this.<#=code.Escape(complexProperty)#> = new <#=code.Escape(complexProperty.TypeUsage)#>();
<#
        }
#>
    }

<#
    }

    var primitiveProperties = complex.Properties.Where(p => p.TypeUsage.EdmType is PrimitiveType && p.DeclaringType == complex);
    if (primitiveProperties.Any())
    {
        foreach(var edmProperty in primitiveProperties)
        {
            WriteProperty(code, edmProperty);
        }
    }

    if (complexProperties.Any())
    {
#>

<#
        foreach(var edmProperty in complexProperties)
        {
            WriteProperty(code, edmProperty);
        }
    }
#>
}
<#
    EndNamespace(namespaceName);
}

if (!VerifyTypesAreCaseInsensitiveUnique(ItemCollection))
{
    return "";
}

fileManager.Process();

#>
<#+
string GetResourceString(string resourceName)
{
    if(_resourceManager == null)
    {
        _resourceManager = new System.Resources.ResourceManager("System.Data.Entity.Design", typeof(System.Data.Entity.Design.MetadataItemCollectionFactory).Assembly);
    }

    return _resourceManager.GetString(resourceName, null);
}
System.Resources.ResourceManager _resourceManager;

void WriteHeader(EntityFrameworkTemplateFileManager fileManager)
{
    fileManager.StartHeader();
#>
//------------------------------------------------------------------------------
// <auto-generated>
// <#=GetResourceString("Template_GeneratedCodeCommentLine1")#>
//
// <#=GetResourceString("Template_GeneratedCodeCommentLine2")#>
// <#=GetResourceString("Template_GeneratedCodeCommentLine3")#>
// </auto-generated>
//------------------------------------------------------------------------------

<#+
    fileManager.EndBlock();
}

void BeginNamespace(string namespaceName, CodeGenerationTools code)
{
    CodeRegion region = new CodeRegion(this);
    if (!String.IsNullOrEmpty(namespaceName))
    {
#>
namespace <#=code.EscapeNamespace(namespaceName)#>
{
<#+
        PushIndent(CodeRegion.GetIndent(1));
    }
}


void EndNamespace(string namespaceName)
{
    if (!String.IsNullOrEmpty(namespaceName))
    {
        PopIndent();
#>
}
<#+
    }
}

void WriteProperty(CodeGenerationTools code, EdmProperty edmProperty)
{
    WriteProperty(Accessibility.ForProperty(edmProperty),
                  code.Escape(edmProperty.TypeUsage),
                  code.Escape(edmProperty),
                  code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                  code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

void WriteNavigationProperty(CodeGenerationTools code, NavigationProperty navigationProperty)
{
    var endType = code.Escape(navigationProperty.ToEndMember.GetEntityType());
    WriteProperty(PropertyVirtualModifier(Accessibility.ForProperty(navigationProperty)),
                  navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ObservableListSource<" + endType + ">") : endType,
                  code.Escape(navigationProperty),
                  code.SpaceAfter(Accessibility.ForGetter(navigationProperty)),
                  code.SpaceAfter(Accessibility.ForSetter(navigationProperty)));
}

void WriteProperty(string accessibility, string type, string name, string getterAccessibility, string setterAccessibility)
{
#>
    private <#=type#> _<#=name#>;
    public <#=type#> <#=name#>
    {
        <#=getterAccessibility#>get { return _<#=name#>; }
        <#=setterAccessibility#>set
        {
            if (_<#=name#> != value)
            {
                _<#=name#> = value;
                OnPropertyChanged("<#=name#>");
            }
        }
    }
<#+
}

string PropertyVirtualModifier(string accessibility)
{
    return accessibility + (accessibility != "private" ? " virtual" : "");
}

bool VerifyTypesAreCaseInsensitiveUnique(EdmItemCollection itemCollection)
{
    var alreadySeen = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
    foreach(var type in itemCollection.GetItems<StructuralType>())
    {
        if (!(type is EntityType || type is ComplexType))
        {
            continue;
        }

        if (alreadySeen.ContainsKey(type.FullName))
        {
            Error(String.Format(CultureInfo.CurrentCulture, "This template does not support types that differ only by case, the types {0} are not supported", type.FullName));
            return false;
        }
        else
        {
            alreadySeen.Add(type.FullName, true);
        }
    }

    return true;
}
#>
2
6/17/2015 5:12:06 PM

Popular Answer

I've had great success using a NuGet package called PropertyChanged.Fody to get INPC implemented on the entity classes. Just install the package, then add the [ImplementPropertyChanged] attribute to any class and PropertyChanged.Fody will "inject" INPC into the class as part of the build process. For example if you have a generated entity class called Customer, just add the following code somewhere in your project.

using PropertyChanged;

[ImplementPropertyChanged]
public partial class Customer
{
}

There are other attributes you can use to control the behavior of the PropertyChanged package. See https://github.com/Fody/PropertyChanged for details.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow