将 INotifyPropertyChanged 与 Entity Framework 6 DbContext 生成器一起使用

Posted

技术标签:

【中文标题】将 INotifyPropertyChanged 与 Entity Framework 6 DbContext 生成器一起使用【英文标题】:Using INotifyPropertyChanged with Entity Framework 6 DbContext Generator 【发布时间】:2014-11-12 02:47:47 【问题描述】:

我知道我可以使用 ObjectContext 代替,但我喜欢 DbContext / DbSet 的功能。我的应用程序不够大,不足以保证我编写复杂的视图模型,所以我想直接在 EF 生成的模型上实现更改通知。

我怎样才能做到这一点?

【问题讨论】:

【参考方案1】:

我使用名为 PropertyChanged.Fody 的 NuGet 包在实体类上实现 INPC 取得了巨大成功。只需安装包,然后将 [ImplementPropertyChanged] 属性添加到任何类,PropertyChanged.Fody 将作为构建过程的一部分将 INPC“注入”到类中。例如,如果您有一个名为 Customer 的生成实体类,只需在项目中的某处添加以下代码。

using PropertyChanged;

[ImplementPropertyChanged]
public partial class Customer


您可以使用其他属性来控制 PropertyChanged 包的行为。详情请见https://github.com/Fody/PropertyChanged。

【讨论】:

也许我应该补充一点,每当我这样做时,我都会为 EF 类使用单独的专用项目/程序集。这样,Fody 可以在依赖项目需要之前将 INPC 内容编入程序集。【参考方案2】:

我也有问题。这是我的适用于 6.1.2 的实现。显然,此解决方案的问题在于 EF 中的每次更改都需要一个新的 tt 脚本。也许微软可以提供不同的选项来提供不同级别的支持。

<#@ 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)
    
        if (PropertyChanged != null)
        
            WhenPropertyChanged(e);
            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  3get return _2; 4setif(_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  3get; 4set; ",
            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 1partial class 2 : INotifyPropertyChanged3",
            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.ExecuteFunction0(\"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,
                "0using 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);
    

#>

【讨论】:

【参考方案3】:

这可以通过将默认的 T4 模板(Entity Framework 自动添加的 .tt 文件)替换为以下内容,然后右键单击 .tt 文件并选择“运行自定义工具”来实现。

重要提示:这将重新生成您的模型,这将导致任何自定义被丢弃。我建议您通过模板实现自定义,而不是直接修改模型类,以防止将来出现问题。

注意:将 string inputFile = @"MessageLog.edmx"; 替换为您的 edmx 文件的名称。

<#@ 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;

#>

【讨论】:

仅供参考,EF6 模板与 EF4 模板非常不同,这就是我在此处发布此内容的原因。希望它可以帮助某人。 谢谢丹,这确实是一个很大的帮助。 @AH。这不是建设性的评论。如果您发布自己的问题,详细说明您正在尝试做什么以及收到的错误,那么也许我可以帮助您弄清楚如何解决它们。 我按照您写的内容运行自定义工具时遇到了几个错误。【参考方案4】:

我个人对 Fody 的实现有一些问题,也许这篇文章可以帮助你T4 Personalization

注意:它适用于 EF 6.1 到。

【讨论】:

以上是关于将 INotifyPropertyChanged 与 Entity Framework 6 DbContext 生成器一起使用的主要内容,如果未能解决你的问题,请参考以下文章

WCF DataContracts 中的 INotifyPropertyChanged

为Page添加INotifyPropertyChanged功能

使用属性... INotifyPropertyChanged

INotifyPropertyChanged WPF

INotifyPropertyChanged的作用

INotifyPropertyChanged WPF