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

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

我怎样才能做到这一点?

【问题讨论】:

    标签: entity-framework-6 dbcontext inotifypropertychanged


    【解决方案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} {{ {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);
        }
    }
    #>
    

    【讨论】:

      【解决方案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 到。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-15
        • 1970-01-01
        • 1970-01-01
        • 2015-10-24
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多