C# 如何使用EF 4.x DbContext生成器获取属性更改通知

C# 如何使用EF 4.x DbContext生成器获取属性更改通知,c#,.net,entity-framework,inotifypropertychanged,C#,.net,Entity Framework,Inotifypropertychanged,我正在使用EntityFramework4.3,因此我使用DbContext生成器来创建上下文和实体类 使用默认的EF 4代码生成器模板,实体类实现INotifyPropertyChanged,并在属性设置器中添加Changing和Changed分部方法 当我使用EF 4.x DbContext生成器时,如下图所示,实体类要轻得多,并且不包括任何跟踪属性更改的方法 以下是一个例子: //------------------------------------------------------

我正在使用EntityFramework4.3,因此我使用DbContext生成器来创建上下文和实体类

使用默认的EF 4代码生成器模板,实体类实现INotifyPropertyChanged,并在属性设置器中添加
Changing
Changed
分部方法

当我使用EF 4.x DbContext生成器时,如下图所示,实体类要轻得多,并且不包括任何跟踪属性更改的方法

以下是一个例子:

//------------------------------------------------------------------------------
// <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; }
    }
}
//------------------------------------------------------------------------------
// 
//此代码是从模板生成的。
//
//手动更改此文件可能会导致应用程序出现意外行为。
//如果重新生成代码,将覆盖对此文件的手动更改。
// 
//------------------------------------------------------------------------------
使用制度;
使用System.Collections.Generic;
名称空间名称空间
{
公共部分类
{
公共表()
{
this.Children=newhashset();
}
公共长父_id{get;set;}
公共长id{get;set;}
公共字符串文件名{get;set;}
公共字节[]文件\u blob{get;set;}
公共虚拟父级{get;set;}
公共虚拟ICollection子项{get;set;}
}
}
我肯定错过了谜题中的一个重要部分,但我的搜索一直没有结果。所以我的问题是:如何使用EF4.3生成包含属性更改通知的类型

编辑


我完全同意@derape answer的观点;但我很好奇,当EF4默认代码生成模板已经有挂钩时,为什么我需要更改
.tt
文件。我的意思是,当绑定到WPF
dependencProperty
'时会发生什么?如果没有INotifyPropertyChanged,命令对一组对象中的一组属性所做的更改将不会反映在UI中。我错过了什么?

这取决于你想做什么。如果只想实现自定义属性/方法,可以使用分部类的函数性。
如果您想更改实体设计器中属性的setter/getter,则必须调整dbContext生成器模板文件。这是一个T4模板。

我最近偶然发现了这个问题,我编辑了我的Entity.tt以实现以下更改,这是一个快速补丁,但效果很好

将以下内容添加到CodeStringGenerator类中

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());

}
将以下内容添加到生成器中

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));
}
使用System.ComponentModel;
{
公共事件属性更改事件处理程序属性更改;
受保护的虚拟void OnPropertyChanged(字符串propertyName)
{
PropertyChangedEventHandler处理程序=PropertyChanged;
if(handler!=null)handler(这是新的PropertyChangedEventArgs(propertyName));
}
并稍微向下移动

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


foreach(var complexProperty in complexProperties)
{
#>
<#=codeStringGenerator.Private(complexProperty)#>
    <#=codeStringGenerator.Property(complexProperty)#>
<#
}
foreach(simpleProperties中的var-edmProperty)
{
#>

上述Anders的解决方案是可行的,但在这个过程中我发现了几个问题:

在第1步中,它说“将以下内容添加到CodeStringGenerator类”,只能添加“public string Private(…”函数,因为其他两个函数已经存在。因此,您需要找到它们并替换这两个函数,而不是添加它们,否则您将得到错误。要准确找到需要放置它们的位置,请搜索“公共类CodeStringGenerator”,并查找其下面的函数

在步骤2“将以下内容添加到生成器”中,您只需要添加“using System.ComponentModel”行,以及来自(包括)“public event Property ChangedEventHandler…”的行。同样,其他行已经存在,您将在.tt文件顶部附近找到它们

在步骤3“以及更进一步的步骤”中,这两个“foreach”循环也已经存在,因此必须替换它们,而不是添加它们。最终,每个循环中的每个foreach循环只添加一行,分别为“”和“”

另外,不要替换错误的循环,在需要替换的循环之上还有两个额外的循环,它们都在相同的对象中循环…确保替换正确的循环:-)


我想我会提到这一点,因为作为一名MVVM/EF新手(曾经使用NHibernate),我必须对其进行这些调整才能正常工作。

如果您希望在直接绑定到数据模型类时收到属性更改通知,则应继续使用ObjectContext


轻量级DBContext类适用于MVVM或MVVMC等模式,其中视图模型实现属性更改通知,UI仅绑定到视图模型的属性。您从未绑定到这些模式中的数据模型类。

我在Anders answer上创建了一个变体,但有以下区别:

  • 减少对Entity.tt文件的更改
  • 为INotifyPropertyChanged实现使用基类(便于引入其他常见功能)
  • 生成的模型类中更清晰的代码布局
因此,我的步骤是:

为要扩展的模型类创建基类:

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));
    }
}
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)));
}
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : BaseModel
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)));
}
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); }
    }
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)));
}
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 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));
    }
}
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)));
}
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : BaseModel
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)));
}
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); }
    }
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)));
}
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 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));
    }
}
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)));
}
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
<#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#> : BaseModel
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)));
}
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); }
    }
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)));
}
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

如果您能看到其他改进,请随时(尝试)编辑此解决方案。

我试图编辑Brian Hinchey解决方案,但编辑被拒绝。然后我将我的添加贴到这里

此解决方案为每个属性设置器生成较少的代码,利用了CallerMemberName属性

注意:我正在使用EF 6.1,它运行得非常好

基类现在看起来是这样的

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));
        }
    }
}
正如布莱恩所说,让我们记住改变:

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