How to get property change notifications with EF 4.x DbContext generator
Asked Answered
G

8

13

I'm playing around with Entity Framework 4.3, and so I am using the DbContext Generator to create the context and entity classes.

With the default EF 4 code generator template, entity classes implement INotifyPropertyChanged, and also add Changing and Changed partial methods in property setters.

When I use the EF 4.x DbContext generator, as pictured below, the entity classes are much lighter, and do not include any means of tracking property changes.

enter image description here

Here is an example:

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using System;
using System.Collections.Generic;

namespace SomeNamespace
{
    public partial class SomeTable
    {
        public SomeTable()
        {
            this.Children = new HashSet<Child>();
        }

        public long parent_id { get; set; }
        public long id { get; set; }
        public string filename { get; set; }
        public byte[] file_blob { get; set; }

        public virtual Parent Parent { get; set; }
        public virtual ICollection<Child> Children { get; set; }
    }
}

I must be missing an important piece of the puzzle, but my searches have been fruitless. So my question is: how can I have generated types included property change notifications with EF 4.3?

Edit

I fully agree with @derape answer's; but I am curious as to why I would need to change the .tt file when the EF 4 default code generation template already has the hooks. I mean what about when binding to a WPF DependencyProperty'? Without INotifyPropertyChanged, changes done by a command to a bunch of properties in a bunch of objects won't be reflected in the UI. What am I missing?

Giuliana answered 13/6, 2012 at 8:13 Comment(1)
You're doing it wrong. DBContext generates lightweight POCOs because you're supposed to use it with a pattern such as MVVM in WPF or MVVMC in ASP.NET MVC. Your view models should handle property change notifications or derive from a base class which does. joshsmithonwpf.wordpress.com/2007/08/29/…Peonage
A
23

I recently stumbled upon this problem, i edited my Entity.tt to implement the following changes, a quick patch but works great..

Add the following to the CodeStringGenerator class

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3} : {4}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)),
        "INotifyPropertyChanged");
}


public string Property(EdmProperty edmProperty)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} {2} {{ {3}{6} {4}{5} }}",
        Accessibility.ForProperty(edmProperty),
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)),
        "set { _"+_code.Escape(edmProperty).ToLower()+" = value; OnPropertyChanged(\""+_code.Escape(edmProperty)+"\");}",
        "get { return _"+_code.Escape(edmProperty).ToLower()+"; }");

}
public string Private(EdmProperty edmProperty) {
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1} _{2};",
        "private",
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty).ToLower());

}

Add the following to the generator

using System.ComponentModel;
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
<#
var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
var complexProperties = typeMapper.GetComplexProperties(entity);
#>

public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}

And abit further down

foreach (var edmProperty in simpleProperties)
{
#>
<#=codeStringGenerator.Private(edmProperty)#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
}


foreach(var complexProperty in complexProperties)
{
#>
<#=codeStringGenerator.Private(complexProperty)#>
    <#=codeStringGenerator.Property(complexProperty)#>
<#
}
Abominable answered 30/8, 2012 at 7:57 Comment(2)
You should simply continue using ObjectContext if you bind to data models in your application. The lightweight DBContext classes are for when you use MVVM or MVVMC where your view models implement property changed notifications.Peonage
Thanks a lot. I had to add the property changed event handler right after "<#=codeStringGenerator.EntityClassOpening(entity)#> {" to make it work, but otherwise very nice.Allpowerful
P
14

I have created a variation on Anders answer with the following differences:

  • Fewer changes to the Entity.tt file
  • Using a base class for the INotifyPropertyChanged implementation (handy for introducing other common functionality)
  • Cleaner code layout in the generated model classes

So my steps are:

Create a base class for your model classes to extend:

public abstract class BaseModel : INotifyPropertyChanged
{
    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Thanks Juan Pable Gomez for the suggested improvement! I like your improvement even if the other reviewers did not :)

Update the EntityClassOpening method in the Entity.tt file to the following:

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", string.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType)) ? "BaseModel" : _typeMapper.GetTypeName(entity.BaseType)));
}

Find the line:

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>

and update it to be:

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : BaseModel

Thanks Manolo!

Update the Property method in the Entity.tt file to the following:

public string Property(EdmProperty edmProperty)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "private {1} {3};\r\n"+
        "\t{0} {1} {2} \r\n" +
        "\t{{ \r\n" +
            "\t\t{4}get {{ return {3}; }} \r\n" +
            "\t\t{5}set {{ SetProperty(ref {3}, value); }} \r\n" + 
        "\t}}\r\n",
        Accessibility.ForProperty(edmProperty),
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty),
        "_" + Char.ToLowerInvariant(_code.Escape(edmProperty)[0]) + _code.Escape(edmProperty).Substring(1),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

You're done! Now your model classes will look like:

public partial class User : BaseModel
{
    private int _id;
    public int Id 
    { 
        get { return _id; } 
        set { SetProperty(ref _id,value);}
    }

    private string _name;
    public string Name 
    { 
        get { return _name; } 
        set { SetProperty(ref _name , value); }
    }

Please feel free to (try to) edit this solution if you can see other improvements.

Purgative answered 24/5, 2013 at 7:40 Comment(3)
you also have to find the following line: <#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> and add : BaseModelTopper
Is there some simple way to do the same thing !! because it's not logical to to all this hard work in EF6 , and it's so easy in EF4 !!Kermitkermy
In EF6 on VB this Public Function Property does not exist. Any pointers? I want change notification without handwriting all the code :)Cappello
C
5

I was trying to edit Brian Hinchey solution But EDIT was rejected. Then I post here my addapts to it.

This solution generates Less code for each property Setter, taking advantage of CallerMemberName Attribute.

NOTE: I'm using EF 6.1 and it Works pretty well.

BaseClass now looks as this.

public abstract class BaseModel : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
    {
        if (object.Equals(storage, value)) return false;

        storage = value;
        this.OnPropertyChanged(propertyName);
        return true;
    }


    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var eventHandler = this.PropertyChanged;
        if (eventHandler != null)
        {
            eventHandler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Update the EntityClassOpening method in the Entity.tt Remains exactly as the Brian's one:

public string EntityClassOpening(EntityType entity)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}partial class {2}{3}",
        Accessibility.ForType(entity),
        _code.SpaceAfter(_code.AbstractOption(entity)),
        _code.Escape(entity),
        _code.StringBefore(" : ", string.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType)) ? "BaseModel" : _typeMapper.GetTypeName(entity.BaseType)));
}

As Bryan say's Remember change :

<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
FOR 
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : BaseModel

And my last change is for the Property method

public string Property(EdmProperty edmProperty)
{
    return string.Format(
        CultureInfo.InvariantCulture,
        "private {1} {3};\r\n"+
        "\t{0} {1} {2} \r\n" +
        "\t{{ \r\n" +
            "\t\t{4}get {{ return {3}; }} \r\n" +
            "\t\t{5}set {{ SetProperty(ref {3}, value); }} \r\n" + 
        "\t}}\r\n",
        Accessibility.ForProperty(edmProperty),
        _typeMapper.GetTypeName(edmProperty.TypeUsage),
        _code.Escape(edmProperty),
        "_" + Char.ToLowerInvariant(_code.Escape(edmProperty)[0]) + _code.Escape(edmProperty).Substring(1),
        _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
        _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
}

And Finally here is the class looks like:

public partial class User : BaseModel
{
    private int _id;
    public int Id 
    { 
        get { return _id; } 
        set { SetProperty(ref _id , value);} 
    }

    private string _name;
    public string Name 
    { 
        get { return _name; } 
        set { SetProperty(ref _name , value);}
    } 
}

This makes the generated clases more light.

Recently I was working with PropertyChanged.Fody library but for unknown reasons (at least for me) It doesn't work properly some times. Thats the reason I'm land here. This solution (Bryan's solution) Works every time.

Charybdis answered 21/12, 2014 at 14:15 Comment(1)
Thanks for the suggested improvement Juan. I edited my answer to include your suggestion.Purgative
D
1

The solution from Anders above works, however there are a couple of gotchas I found during the process:

In Step 1 where it says "Add the following to the CodeStringGenerator class", only the "public string Private(..." function can be added because the other two already exist. So, you need to find them and replace those two functions not add them, otherwise you'll get errors. To find exactly where you need to put them, do a search for "public class CodeStringGenerator", and look for the functions underneath that.

In step 2 "Add the following to the generator", you only need to add the "using System.ComponentModel" line, and the lines from (and including) "public event PropertyChangedEventHandler...". Again, the other lines already exist, you will find them near the top of the .tt file.

In step 3 "And a bit further down", both of those "foreach" loops also already exist, so they must be replaced and not added. Ultimately only one line is added to each foreach loop, "<#=codeStringGenerator.Private(edmProperty)#>" and "<#=codeStringGenerator.Private(complexProperty)#>" in each loop respectively.

Also, don't replace the wrong loop, there are two additional loops above the ones you need to replace which both cycle through the same objects... make sure you replace the correct ones :-)

I thought I would mention this because as an MVVM / EF novice (used to use NHibernate) I had to make these adjustments for it to work.

Demandant answered 11/4, 2013 at 3:52 Comment(0)
S
0

Well it depends what you are trying to do. If you just want to implement custom properties/methods you could use the funcionality of partial classes. If you want to change lets say setter/getter of your properties in your entity designer you'd have to adapt the dbContext generator template file. It's a T4 template.

Sketch answered 13/6, 2012 at 8:30 Comment(3)
I don't really understand your Edit. Can you maybe rephrase your explanation?Sketch
Also, a word of caution with INotifyPropertyChanged inside of Entities. If you raise the propetychanged event at the wrong time, you will get an EntityMemberChanged or EntityComplexMemberChanged was called without first calling EntityMemberChanging or EntityComplexMemberChanging. Something to do with proxies I think. I haven't found a better way to NotifyPropertyChanged, but it would be great if you could implement INotifyPropertyChanged on the entity itself without having to worry about EF doing something that will crash your program.Anserine
I'm using EF 4.4 if it helps (at least, that's the version on the assembly being referenced), so it's possible that this was fixed with EF 5.Anserine
L
0

I created the following for use with EF 6.1.2 but testing has been fairly limited so use at your own risk.

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

const string inputFile = @"Model.edmx";
var textTransform = DynamicTextTransformation.Create(this);
var code = new CodeGenerationTools(this);
var ef = new MetadataTools(this);
var typeMapper = new TypeMapper(code, ef, textTransform.Errors);
var fileManager = EntityFrameworkTemplateFileManager.Create(this);
var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);
var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef);

if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile))
{
    return string.Empty;
}

WriteHeader(codeStringGenerator, fileManager);

foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))
{
    fileManager.StartNewFile(entity.Name + ".cs");
    BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
<#=codeStringGenerator.EntityClassOpening(entity)#>
{
<#
    var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
    var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
    var complexProperties = typeMapper.GetComplexProperties(entity);

    if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any())
    {
#>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public <#=code.Escape(entity)#>()
    {
<#
        foreach (var edmProperty in propertiesWithDefaultValues)
        {
#>
        this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
<#
        }

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

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

<#
    }

    var simpleProperties = typeMapper.GetSimpleProperties(entity);
    if (simpleProperties.Any())
    {
        foreach (var edmProperty in simpleProperties)
        {
#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
    }

    if (complexProperties.Any())
    {
#>

<#
        foreach(var complexProperty in complexProperties)
        {
#>
    <#=codeStringGenerator.Property(complexProperty)#>
<#
        }
    }

    var navigationProperties = typeMapper.GetNavigationProperties(entity);
    if (navigationProperties.Any())
    {
#>

<#
        foreach (var navigationProperty in navigationProperties)
        {
            if (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            {
#>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
<#
            }
#>
    <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
<#
        }
    }
#>

    #region INotifyPropertyChanged Members
    public event PropertyChangedEventHandler PropertyChanged;

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

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        WhenPropertyChanged(e);

        if (PropertyChanged != null)
        {
            PropertyChanged(this, e);
        }
    }

    partial void WhenPropertyChanged(PropertyChangedEventArgs e);
    #endregion
}
<#
    EndNamespace(code);
}

foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection))
{
    fileManager.StartNewFile(complex.Name + ".cs");
    BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
{
<#
    var complexProperties = typeMapper.GetComplexProperties(complex);
    var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex);

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

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

<#
    }

    var simpleProperties = typeMapper.GetSimpleProperties(complex);
    if (simpleProperties.Any())
    {
        foreach(var edmProperty in simpleProperties)
        {
#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
    }

    if (complexProperties.Any())
    {
#>

<#
        foreach(var edmProperty in complexProperties)
        {
#>
    <#=codeStringGenerator.Property(edmProperty)#>
<#
        }
    }
#>
}
<#
    EndNamespace(code);
}

foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection))
{
    fileManager.StartNewFile(enumType.Name + ".cs");
    BeginNamespace(code);
#>
<#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
<#
    if (typeMapper.EnumIsFlags(enumType))
    {
#>
[Flags]
<#
    }
#>
<#=codeStringGenerator.EnumOpening(enumType)#>
{
<#
    var foundOne = false;

    foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType))
    {
        foundOne = true;
#>
    <#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>,
<#
    }

    if (foundOne)
    {
        this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1);
    }
#>
}
<#
    EndNamespace(code);
}

fileManager.Process();

#>
<#+

public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager)
{
    fileManager.StartHeader();
#>
//------------------------------------------------------------------------------
// <auto-generated>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#>
//
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#>
// <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#>
// </auto-generated>
//------------------------------------------------------------------------------
<#=codeStringGenerator.UsingDirectives(inHeader: true)#>
<#+
    fileManager.EndBlock();
}

public void BeginNamespace(CodeGenerationTools code)
{
    var codeNamespace = code.VsNamespaceSuggestion();
    if (!String.IsNullOrEmpty(codeNamespace))
    {
#>
namespace <#=code.EscapeNamespace(codeNamespace)#>
{
<#+
        PushIndent("    ");
    }
}

public void EndNamespace(CodeGenerationTools code)
{
    if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion()))
    {
        PopIndent();
#>
}
<#+
    }
}

public const string TemplateId = "CSharp_DbContext_Types_EF6";

public class CodeStringGenerator
{
    private readonly CodeGenerationTools _code;
    private readonly TypeMapper _typeMapper;
    private readonly MetadataTools _ef;

    public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef)
    {
        ArgumentNotNull(code, "code");
        ArgumentNotNull(typeMapper, "typeMapper");
        ArgumentNotNull(ef, "ef");

        _code = code;
        _typeMapper = typeMapper;
        _ef = ef;
    }

    public string Property(EdmProperty edmProperty)
    {
        StringBuilder propertyCode = new StringBuilder();
        propertyCode.AppendFormat("private {0} _{1};",_typeMapper.GetTypeName(edmProperty.TypeUsage), _code.Escape(edmProperty));
        propertyCode.AppendFormat(
            CultureInfo.InvariantCulture,
            "{0} {1} {2} {{ {3}get{{ return _{2};}} {4}set{{if(_{2} != value){{_{2} = value; OnPropertyChanged(\"{2}\");}}}}}}",
            Accessibility.ForProperty(edmProperty),
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty),
            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));

        return propertyCode.ToString();
    }

    public string NavigationProperty(NavigationProperty navProp)
    {
        var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} {2} {{ {3}get; {4}set; }}",
            AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),
            navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
            _code.Escape(navProp),
            _code.SpaceAfter(Accessibility.ForGetter(navProp)),
            _code.SpaceAfter(Accessibility.ForSetter(navProp)));
    }

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

    public string EntityClassOpening(EntityType entity)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1}partial class {2} : INotifyPropertyChanged{3}",
            Accessibility.ForType(entity),
            _code.SpaceAfter(_code.AbstractOption(entity)),
            _code.Escape(entity),
            _code.StringBefore(", ", _typeMapper.GetTypeName(entity.BaseType)));
    }

    public string EnumOpening(SimpleType enumType)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} enum {1} : {2}",
            Accessibility.ForType(enumType),
            _code.Escape(enumType),
            _code.Escape(_typeMapper.UnderlyingClrType(enumType)));
        }

    public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter)
    {
        var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
        foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable))
        {
            var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null";
            var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")";
            var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))";
            writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit);
        }
    }

    public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);

        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} IQueryable<{1}> {2}({3})",
            AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
            _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
            _code.Escape(edmFunction),
            string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()));
    }

    public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);

        return string.Format(
            CultureInfo.InvariantCulture,
            "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});",
            _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
            edmFunction.NamespaceName,
            edmFunction.Name,
            string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()),
            _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())));
    }

    public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);
        var returnType = _typeMapper.GetReturnType(edmFunction);

        var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray());
        if (includeMergeOption)
        {
            paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption";
        }

        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} {2}({3})",
            AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
            returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
            _code.Escape(edmFunction),
            paramList);
    }

    public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
    {
        var parameters = _typeMapper.GetParameters(edmFunction);
        var returnType = _typeMapper.GetReturnType(edmFunction);

        var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()));
        if (includeMergeOption)
        {
            callParams = ", mergeOption" + callParams;
        }

        return string.Format(
            CultureInfo.InvariantCulture,
            "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});",
            returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
            edmFunction.Name,
            callParams);
    }

    public string DbSet(EntitySet entitySet)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} virtual DbSet<{1}> {2} {{ get; set; }}",
            Accessibility.ForReadOnlyProperty(entitySet),
            _typeMapper.GetTypeName(entitySet.ElementType),
            _code.Escape(entitySet));
    }

    public string UsingDirectives(bool inHeader, bool includeCollections = true)
    {
        return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
            ? string.Format(
                CultureInfo.InvariantCulture,
                "{0}using System;" + Environment.NewLine + 
                "using System.ComponentModel;{1}" +
                "{2}",
                inHeader ? Environment.NewLine : "",
                includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
                inHeader ? "" : Environment.NewLine)
            : "";
    }
}

public class TypeMapper
{
    private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName";

    private readonly System.Collections.IList _errors;
    private readonly CodeGenerationTools _code;
    private readonly MetadataTools _ef;

    public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors)
    {
        ArgumentNotNull(code, "code");
        ArgumentNotNull(ef, "ef");
        ArgumentNotNull(errors, "errors");

        _code = code;
        _ef = ef;
        _errors = errors;
    }

    public static string FixNamespaces(string typeName)
    {
        return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial.");
    }

    public string GetTypeName(TypeUsage typeUsage)
    {
        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);
    }

    public string GetTypeName(EdmType edmType)
    {
        return GetTypeName(edmType, isNullable: null, modelNamespace: null);
    }

    public string GetTypeName(TypeUsage typeUsage, string modelNamespace)
    {
        return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);
    }

    public string GetTypeName(EdmType edmType, string modelNamespace)
    {
        return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);
    }

    public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace)
    {
        if (edmType == null)
        {
            return null;
        }

        var collectionType = edmType as CollectionType;
        if (collectionType != null)
        {
            return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace));
        }

        var typeName = _code.Escape(edmType.MetadataProperties
                                .Where(p => p.Name == ExternalTypeNameAttributeName)
                                .Select(p => (string)p.Value)
                                .FirstOrDefault())
            ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ?
                _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) :
                _code.Escape(edmType));

        if (edmType is StructuralType)
        {
            return typeName;
        }

        if (edmType is SimpleType)
        {
            var clrType = UnderlyingClrType(edmType);
            if (!IsEnumType(edmType))
            {
                typeName = _code.Escape(clrType);
            }

            typeName = FixNamespaces(typeName);

            return clrType.IsValueType && isNullable == true ?
                String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) :
                typeName;
        }

        throw new ArgumentException("edmType");
    }

    public Type UnderlyingClrType(EdmType edmType)
    {
        ArgumentNotNull(edmType, "edmType");

        var primitiveType = edmType as PrimitiveType;
        if (primitiveType != null)
        {
            return primitiveType.ClrEquivalentType;
        }

        if (IsEnumType(edmType))
        {
            return GetEnumUnderlyingType(edmType).ClrEquivalentType;
        }

        return typeof(object);
    }

    public object GetEnumMemberValue(MetadataItem enumMember)
    {
        ArgumentNotNull(enumMember, "enumMember");

        var valueProperty = enumMember.GetType().GetProperty("Value");
        return valueProperty == null ? null : valueProperty.GetValue(enumMember, null);
    }

    public string GetEnumMemberName(MetadataItem enumMember)
    {
        ArgumentNotNull(enumMember, "enumMember");

        var nameProperty = enumMember.GetType().GetProperty("Name");
        return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null);
    }

    public System.Collections.IEnumerable GetEnumMembers(EdmType enumType)
    {
        ArgumentNotNull(enumType, "enumType");

        var membersProperty = enumType.GetType().GetProperty("Members");
        return membersProperty != null 
            ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null)
            : Enumerable.Empty<MetadataItem>();
    }

    public bool EnumIsFlags(EdmType enumType)
    {
        ArgumentNotNull(enumType, "enumType");

        var isFlagsProperty = enumType.GetType().GetProperty("IsFlags");
        return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null);
    }

    public bool IsEnumType(GlobalItem edmType)
    {
        ArgumentNotNull(edmType, "edmType");

        return edmType.GetType().Name == "EnumType";
    }

    public PrimitiveType GetEnumUnderlyingType(EdmType enumType)
    {
        ArgumentNotNull(enumType, "enumType");

        return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null);
    }

    public string CreateLiteral(object value)
    {
        if (value == null || value.GetType() != typeof(TimeSpan))
        {
            return _code.CreateLiteral(value);
        }

        return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks);
    }

    public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)
    {
        ArgumentNotNull(types, "types");
        ArgumentNotNull(sourceFile, "sourceFile");

        var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
        if (types.Any(item => !hash.Add(item)))
        {
            _errors.Add(
                new CompilerError(sourceFile, -1, -1, "6023",
                    String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict"))));
            return false;
        }
        return true;
    }

    public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection)
    {
        return GetItemsToGenerate<SimpleType>(itemCollection)
            .Where(e => IsEnumType(e));
    }

    public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType
    {
        return itemCollection
            .OfType<T>()
            .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName))
            .OrderBy(i => i.Name);
    }

    public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection)
    {
        return itemCollection
            .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i))
            .Select(g => GetGlobalItemName(g));
    }

    public string GetGlobalItemName(GlobalItem item)
    {
        if (item is EdmType)
        {
            return ((EdmType)item).Name;
        }
        else
        {
            return ((EntityContainer)item).Name;
        }
    }

    public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetComplexProperties(EntityType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
    }

    public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
    }

    public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type)
    {
        return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
    }

    public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type)
    {
        return type.NavigationProperties.Where(np => np.DeclaringType == type);
    }

    public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type)
    {
        return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
    }

    public FunctionParameter GetReturnParameter(EdmFunction edmFunction)
    {
        ArgumentNotNull(edmFunction, "edmFunction");

        var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters");
        return returnParamsProperty == null
            ? edmFunction.ReturnParameter
            : ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault();
    }

    public bool IsComposable(EdmFunction edmFunction)
    {
        ArgumentNotNull(edmFunction, "edmFunction");

        var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute");
        return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null);
    }

    public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction)
    {
        return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
    }

    public TypeUsage GetReturnType(EdmFunction edmFunction)
    {
        var returnParam = GetReturnParameter(edmFunction);
        return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage);
    }

    public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption)
    {
        var returnType = GetReturnType(edmFunction);
        return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
    }
}

public static void ArgumentNotNull<T>(T arg, string name) where T : class
{
    if (arg == null)
    {
        throw new ArgumentNullException(name);
    }
}
#>
Lamar answered 5/1, 2015 at 13:24 Comment(1)
If you choose to use this you will notice that the partial classes generated have a partial method called WhenPropertyChanged. If you implement a partial class next to the generated partial classes then and implement this then it is called before the PropertyChanged. Actually I've just spotted a bug, this will only be called if there is a listener on the PropertyChanged event and that is not the intention.Lamar
C
0

I work with Visual Basic and tend to enjoy refactoring legacy and non layered architectures.

So, I've worked out how to do this using answers from above but in Entity Framework 6.1.3 and .net 4.6.1.

I've translated from the answers given by others, so I can only take credit for the bit of rediscovery and translation I've done this evening.

The project this is for is quite tiny, it's winforms and I wanted databinding rather than a slew of manual updates to controls. I did NOT want to add even more complexity by adding in more separation as here there was not enough benefit. There's just enough to justify this :).

I hope it helps other VB coders.

The Base class:

Imports System.ComponentModel
Imports System.Runtime.CompilerServices

Public MustInherit Class BaseModel
    Implements INotifyPropertyChanged
    Protected Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional propertyName As String = Nothing) As Boolean
        If Equals(storage, value) Then Return False
        storage = value
        OnPropertyChanged(propertyName)
        Return True
    End Function

    Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

    Protected Overridable Sub OnPropertyChanged(propertyName As String)
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub
End Class

Public string Property does not exist in the VB TT file (Make sure it's the ModelName.tt not the ModelName.Context.tt). Note the use of the conditional for the nullable comparison. If you don't have this, you won't get the thing firing!

Public Function AnyProperty(accessibility As String, type As String, name As String, getterAccessibility As String, setterAccessibility As String, defaultValue As String)
            Dim CompareLine = if(type.contains("Null"), $"if Not Nullable.Equals({name}, value) ", $"if {name} <> value ")
            Return String.Format( _
                CultureInfo.InvariantCulture, _
                "{6}    Private _{0} As {1}{2}{6}" & _
                "    {3} Property {0} As {1}{6}" & _
                "        {4}Get{6}" & _
                "            Return _{0}{6}" & _
                "        End Get{6}" & _
                "        {5}Set(ByVal value As {1}){6}" & _
                "             {7} then  SetProperty(_{0}, value){6}" & _
                "        End Set{6}" & _
                "    End Property",  _
                name, _
                type, _
                defaultValue, _
                accessibility, _
                getterAccessibility, _
                setterAccessibility, _
                Environment.NewLine,
                CompareLine)
 End Function

EntityClassOpening

Public Function EntityClassOpening(entity As EntityType) As String
        Return String.Format( _
            CultureInfo.InvariantCulture, _
            "Partial {0} {1}Class {2}{3}", _
            Accessibility.ForType(entity), _
            _code.SpaceAfter(_code.MustInheritOption(entity)), _
            _code.Escape(entity), _
            _code.StringBefore(Environment.Newline & " Inherits ", If(String.IsNullOrEmpty(_typeMapper.GetTypeName(entity.BaseType)), "BaseModel", _typeMapper.GetTypeName(entity.BaseType))))
  End Function

If I discover anything significant to add to this, I'll do so.

Please do note my use case: lightweight VB project, no justification for introducing MVVM/MVC and repository layers but plenty of reason to want databinding rather than annoying conditional updates to and from controls - many of which have an EditValue that are of type Object (Devexpress winforms controls).

Cappello answered 23/9, 2016 at 19:39 Comment(0)
P
-2

You should continue using ObjectContext if you want property change notifications when you bind directly to the data model classes.

The lightweight DBContext classes are for patterns such as MVVM or MVVMC where your view model implements property change notifications and your UI only binds to the view model's properties. You never bind to data model classes in these patterns.

Peonage answered 22/4, 2013 at 6:17 Comment(1)
Being honest I'd have to wonder what the purpose is of using generated code (such as EF Model First) if you are then going to hand code copies of every entity you create for use in the UI. To my way of thinking we provide patterns to make our lives easier as developers. I'm working on a project at the moment where the (generated) business entities do handle INotifyPropertyChanged and it gives us so many benefits that I'm prepared to risk the scorn of the purists that say we should be deep copying the model into the viewmodel.Lamar

© 2022 - 2024 — McMap. All rights reserved.