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

前端 未结 8 872
忘了有多久
忘了有多久 2020-11-27 18:29

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

With the default EF 4 code genera

相关标签:
8条回答
  • 2020-11-27 19:05

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

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

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

    BaseClass now looks as this.

    public abstract class BaseModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
        {
            if (object.Equals(storage, value)) return false;
    
            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }
    
    
        protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var eventHandler = this.PropertyChanged;
            if (eventHandler != null)
            {
                eventHandler(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
    

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

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

    As Bryan say's Remember change :

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

    And my last change is for the Property method

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

    And Finally here is the class looks like:

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

    This makes the generated clases more light.

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

    0 讨论(0)
  • 2020-11-27 19:05

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

    0 讨论(0)
  • 2020-11-27 19:16

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

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

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

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

    I hope it helps other VB coders.

    The Base class:

    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices
    
    Public MustInherit Class BaseModel
        Implements INotifyPropertyChanged
        Protected Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional propertyName As String = Nothing) As Boolean
            If Equals(storage, value) Then Return False
            storage = value
            OnPropertyChanged(propertyName)
            Return True
        End Function
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
    
        Protected Overridable Sub OnPropertyChanged(propertyName As String)
            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
        End Sub
    End Class
    

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

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

    EntityClassOpening

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

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

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

    0 讨论(0)
  • 2020-11-27 19:22

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

    <#@ template language="C#" debug="false" hostspecific="true"#>
    <#@ include file="EF6.Utility.CS.ttinclude"#><#@ 
     output extension=".cs"#><#
    
    const string inputFile = @"Model.edmx";
    var textTransform = DynamicTextTransformation.Create(this);
    var code = new CodeGenerationTools(this);
    var ef = new MetadataTools(this);
    var typeMapper = new TypeMapper(code, ef, textTransform.Errors);
    var fileManager = EntityFrameworkTemplateFileManager.Create(this);
    var itemCollection = new EdmMetadataLoader(textTransform.Host, textTransform.Errors).CreateEdmItemCollection(inputFile);
    var codeStringGenerator = new CodeStringGenerator(code, typeMapper, ef);
    
    if (!typeMapper.VerifyCaseInsensitiveTypeUniqueness(typeMapper.GetAllGlobalItems(itemCollection), inputFile))
    {
        return string.Empty;
    }
    
    WriteHeader(codeStringGenerator, fileManager);
    
    foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection))
    {
        fileManager.StartNewFile(entity.Name + ".cs");
        BeginNamespace(code);
    #>
    <#=codeStringGenerator.UsingDirectives(inHeader: false)#>
    <#=codeStringGenerator.EntityClassOpening(entity)#>
    {
    <#
        var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
        var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
        var complexProperties = typeMapper.GetComplexProperties(entity);
    
        if (propertiesWithDefaultValues.Any() || collectionNavigationProperties.Any() || complexProperties.Any())
        {
    #>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public <#=code.Escape(entity)#>()
        {
    <#
            foreach (var edmProperty in propertiesWithDefaultValues)
            {
    #>
            this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
    <#
            }
    
            foreach (var navigationProperty in collectionNavigationProperties)
            {
    #>
            this.<#=code.Escape(navigationProperty)#> = new HashSet<<#=typeMapper.GetTypeName(navigationProperty.ToEndMember.GetEntityType())#>>();
    <#
            }
    
            foreach (var complexProperty in complexProperties)
            {
    #>
            this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
    <#
            }
    #>
        }
    
    <#
        }
    
        var simpleProperties = typeMapper.GetSimpleProperties(entity);
        if (simpleProperties.Any())
        {
            foreach (var edmProperty in simpleProperties)
            {
    #>
        <#=codeStringGenerator.Property(edmProperty)#>
    <#
            }
        }
    
        if (complexProperties.Any())
        {
    #>
    
    <#
            foreach(var complexProperty in complexProperties)
            {
    #>
        <#=codeStringGenerator.Property(complexProperty)#>
    <#
            }
        }
    
        var navigationProperties = typeMapper.GetNavigationProperties(entity);
        if (navigationProperties.Any())
        {
    #>
    
    <#
            foreach (var navigationProperty in navigationProperties)
            {
                if (navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
                {
    #>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    <#
                }
    #>
        <#=codeStringGenerator.NavigationProperty(navigationProperty)#>
    <#
            }
        }
    #>
    
        #region INotifyPropertyChanged Members
        public event PropertyChangedEventHandler PropertyChanged;
    
        protected void OnPropertyChanged(string propertyName)
        {
            OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
        }
    
        protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            WhenPropertyChanged(e);
    
            if (PropertyChanged != null)
            {
                PropertyChanged(this, e);
            }
        }
    
        partial void WhenPropertyChanged(PropertyChangedEventArgs e);
        #endregion
    }
    <#
        EndNamespace(code);
    }
    
    foreach (var complex in typeMapper.GetItemsToGenerate<ComplexType>(itemCollection))
    {
        fileManager.StartNewFile(complex.Name + ".cs");
        BeginNamespace(code);
    #>
    <#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
    <#=Accessibility.ForType(complex)#> partial class <#=code.Escape(complex)#>
    {
    <#
        var complexProperties = typeMapper.GetComplexProperties(complex);
        var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(complex);
    
        if (propertiesWithDefaultValues.Any() || complexProperties.Any())
        {
    #>
        public <#=code.Escape(complex)#>()
        {
    <#
            foreach (var edmProperty in propertiesWithDefaultValues)
            {
    #>
            this.<#=code.Escape(edmProperty)#> = <#=typeMapper.CreateLiteral(edmProperty.DefaultValue)#>;
    <#
            }
    
            foreach (var complexProperty in complexProperties)
            {
    #>
            this.<#=code.Escape(complexProperty)#> = new <#=typeMapper.GetTypeName(complexProperty.TypeUsage)#>();
    <#
            }
    #>
        }
    
    <#
        }
    
        var simpleProperties = typeMapper.GetSimpleProperties(complex);
        if (simpleProperties.Any())
        {
            foreach(var edmProperty in simpleProperties)
            {
    #>
        <#=codeStringGenerator.Property(edmProperty)#>
    <#
            }
        }
    
        if (complexProperties.Any())
        {
    #>
    
    <#
            foreach(var edmProperty in complexProperties)
            {
    #>
        <#=codeStringGenerator.Property(edmProperty)#>
    <#
            }
        }
    #>
    }
    <#
        EndNamespace(code);
    }
    
    foreach (var enumType in typeMapper.GetEnumItemsToGenerate(itemCollection))
    {
        fileManager.StartNewFile(enumType.Name + ".cs");
        BeginNamespace(code);
    #>
    <#=codeStringGenerator.UsingDirectives(inHeader: false, includeCollections: false)#>
    <#
        if (typeMapper.EnumIsFlags(enumType))
        {
    #>
    [Flags]
    <#
        }
    #>
    <#=codeStringGenerator.EnumOpening(enumType)#>
    {
    <#
        var foundOne = false;
    
        foreach (MetadataItem member in typeMapper.GetEnumMembers(enumType))
        {
            foundOne = true;
    #>
        <#=code.Escape(typeMapper.GetEnumMemberName(member))#> = <#=typeMapper.GetEnumMemberValue(member)#>,
    <#
        }
    
        if (foundOne)
        {
            this.GenerationEnvironment.Remove(this.GenerationEnvironment.Length - 3, 1);
        }
    #>
    }
    <#
        EndNamespace(code);
    }
    
    fileManager.Process();
    
    #>
    <#+
    
    public void WriteHeader(CodeStringGenerator codeStringGenerator, EntityFrameworkTemplateFileManager fileManager)
    {
        fileManager.StartHeader();
    #>
    //------------------------------------------------------------------------------
    // <auto-generated>
    // <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#>
    //
    // <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#>
    // <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#>
    // </auto-generated>
    //------------------------------------------------------------------------------
    <#=codeStringGenerator.UsingDirectives(inHeader: true)#>
    <#+
        fileManager.EndBlock();
    }
    
    public void BeginNamespace(CodeGenerationTools code)
    {
        var codeNamespace = code.VsNamespaceSuggestion();
        if (!String.IsNullOrEmpty(codeNamespace))
        {
    #>
    namespace <#=code.EscapeNamespace(codeNamespace)#>
    {
    <#+
            PushIndent("    ");
        }
    }
    
    public void EndNamespace(CodeGenerationTools code)
    {
        if (!String.IsNullOrEmpty(code.VsNamespaceSuggestion()))
        {
            PopIndent();
    #>
    }
    <#+
        }
    }
    
    public const string TemplateId = "CSharp_DbContext_Types_EF6";
    
    public class CodeStringGenerator
    {
        private readonly CodeGenerationTools _code;
        private readonly TypeMapper _typeMapper;
        private readonly MetadataTools _ef;
    
        public CodeStringGenerator(CodeGenerationTools code, TypeMapper typeMapper, MetadataTools ef)
        {
            ArgumentNotNull(code, "code");
            ArgumentNotNull(typeMapper, "typeMapper");
            ArgumentNotNull(ef, "ef");
    
            _code = code;
            _typeMapper = typeMapper;
            _ef = ef;
        }
    
        public string Property(EdmProperty edmProperty)
        {
            StringBuilder propertyCode = new StringBuilder();
            propertyCode.AppendFormat("private {0} _{1};",_typeMapper.GetTypeName(edmProperty.TypeUsage), _code.Escape(edmProperty));
            propertyCode.AppendFormat(
                CultureInfo.InvariantCulture,
                "{0} {1} {2} {{ {3}get{{ return _{2};}} {4}set{{if(_{2} != value){{_{2} = value; OnPropertyChanged(\"{2}\");}}}}}}",
                Accessibility.ForProperty(edmProperty),
                _typeMapper.GetTypeName(edmProperty.TypeUsage),
                _code.Escape(edmProperty),
                _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
                _code.SpaceAfter(Accessibility.ForSetter(edmProperty)));
    
            return propertyCode.ToString();
        }
    
        public string NavigationProperty(NavigationProperty navProp)
        {
            var endType = _typeMapper.GetTypeName(navProp.ToEndMember.GetEntityType());
            return string.Format(
                CultureInfo.InvariantCulture,
                "{0} {1} {2} {{ {3}get; {4}set; }}",
                AccessibilityAndVirtual(Accessibility.ForNavigationProperty(navProp)),
                navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("ICollection<" + endType + ">") : endType,
                _code.Escape(navProp),
                _code.SpaceAfter(Accessibility.ForGetter(navProp)),
                _code.SpaceAfter(Accessibility.ForSetter(navProp)));
        }
    
        public string AccessibilityAndVirtual(string accessibility)
        {
            return accessibility + (accessibility != "private" ? " virtual" : "");
        }
    
        public string EntityClassOpening(EntityType entity)
        {
            return string.Format(
                CultureInfo.InvariantCulture,
                "{0} {1}partial class {2} : INotifyPropertyChanged{3}",
                Accessibility.ForType(entity),
                _code.SpaceAfter(_code.AbstractOption(entity)),
                _code.Escape(entity),
                _code.StringBefore(", ", _typeMapper.GetTypeName(entity.BaseType)));
        }
    
        public string EnumOpening(SimpleType enumType)
        {
            return string.Format(
                CultureInfo.InvariantCulture,
                "{0} enum {1} : {2}",
                Accessibility.ForType(enumType),
                _code.Escape(enumType),
                _code.Escape(_typeMapper.UnderlyingClrType(enumType)));
            }
    
        public void WriteFunctionParameters(EdmFunction edmFunction, Action<string, string, string, string> writeParameter)
        {
            var parameters = FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
            foreach (var parameter in parameters.Where(p => p.NeedsLocalVariable))
            {
                var isNotNull = parameter.IsNullableOfT ? parameter.FunctionParameterName + ".HasValue" : parameter.FunctionParameterName + " != null";
                var notNullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", " + parameter.FunctionParameterName + ")";
                var nullInit = "new ObjectParameter(\"" + parameter.EsqlParameterName + "\", typeof(" + TypeMapper.FixNamespaces(parameter.RawClrTypeName) + "))";
                writeParameter(parameter.LocalVariableName, isNotNull, notNullInit, nullInit);
            }
        }
    
        public string ComposableFunctionMethod(EdmFunction edmFunction, string modelNamespace)
        {
            var parameters = _typeMapper.GetParameters(edmFunction);
    
            return string.Format(
                CultureInfo.InvariantCulture,
                "{0} IQueryable<{1}> {2}({3})",
                AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
                _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
                _code.Escape(edmFunction),
                string.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray()));
        }
    
        public string ComposableCreateQuery(EdmFunction edmFunction, string modelNamespace)
        {
            var parameters = _typeMapper.GetParameters(edmFunction);
    
            return string.Format(
                CultureInfo.InvariantCulture,
                "return ((IObjectContextAdapter)this).ObjectContext.CreateQuery<{0}>(\"[{1}].[{2}]({3})\"{4});",
                _typeMapper.GetTypeName(_typeMapper.GetReturnType(edmFunction), modelNamespace),
                edmFunction.NamespaceName,
                edmFunction.Name,
                string.Join(", ", parameters.Select(p => "@" + p.EsqlParameterName).ToArray()),
                _code.StringBefore(", ", string.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray())));
        }
    
        public string FunctionMethod(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
        {
            var parameters = _typeMapper.GetParameters(edmFunction);
            var returnType = _typeMapper.GetReturnType(edmFunction);
    
            var paramList = String.Join(", ", parameters.Select(p => TypeMapper.FixNamespaces(p.FunctionParameterType) + " " + p.FunctionParameterName).ToArray());
            if (includeMergeOption)
            {
                paramList = _code.StringAfter(paramList, ", ") + "MergeOption mergeOption";
            }
    
            return string.Format(
                CultureInfo.InvariantCulture,
                "{0} {1} {2}({3})",
                AccessibilityAndVirtual(Accessibility.ForMethod(edmFunction)),
                returnType == null ? "int" : "ObjectResult<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
                _code.Escape(edmFunction),
                paramList);
        }
    
        public string ExecuteFunction(EdmFunction edmFunction, string modelNamespace, bool includeMergeOption)
        {
            var parameters = _typeMapper.GetParameters(edmFunction);
            var returnType = _typeMapper.GetReturnType(edmFunction);
    
            var callParams = _code.StringBefore(", ", String.Join(", ", parameters.Select(p => p.ExecuteParameterName).ToArray()));
            if (includeMergeOption)
            {
                callParams = ", mergeOption" + callParams;
            }
    
            return string.Format(
                CultureInfo.InvariantCulture,
                "return ((IObjectContextAdapter)this).ObjectContext.ExecuteFunction{0}(\"{1}\"{2});",
                returnType == null ? "" : "<" + _typeMapper.GetTypeName(returnType, modelNamespace) + ">",
                edmFunction.Name,
                callParams);
        }
    
        public string DbSet(EntitySet entitySet)
        {
            return string.Format(
                CultureInfo.InvariantCulture,
                "{0} virtual DbSet<{1}> {2} {{ get; set; }}",
                Accessibility.ForReadOnlyProperty(entitySet),
                _typeMapper.GetTypeName(entitySet.ElementType),
                _code.Escape(entitySet));
        }
    
        public string UsingDirectives(bool inHeader, bool includeCollections = true)
        {
            return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
                ? string.Format(
                    CultureInfo.InvariantCulture,
                    "{0}using System;" + Environment.NewLine + 
                    "using System.ComponentModel;{1}" +
                    "{2}",
                    inHeader ? Environment.NewLine : "",
                    includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
                    inHeader ? "" : Environment.NewLine)
                : "";
        }
    }
    
    public class TypeMapper
    {
        private const string ExternalTypeNameAttributeName = @"http://schemas.microsoft.com/ado/2006/04/codegeneration:ExternalTypeName";
    
        private readonly System.Collections.IList _errors;
        private readonly CodeGenerationTools _code;
        private readonly MetadataTools _ef;
    
        public TypeMapper(CodeGenerationTools code, MetadataTools ef, System.Collections.IList errors)
        {
            ArgumentNotNull(code, "code");
            ArgumentNotNull(ef, "ef");
            ArgumentNotNull(errors, "errors");
    
            _code = code;
            _ef = ef;
            _errors = errors;
        }
    
        public static string FixNamespaces(string typeName)
        {
            return typeName.Replace("System.Data.Spatial.", "System.Data.Entity.Spatial.");
        }
    
        public string GetTypeName(TypeUsage typeUsage)
        {
            return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace: null);
        }
    
        public string GetTypeName(EdmType edmType)
        {
            return GetTypeName(edmType, isNullable: null, modelNamespace: null);
        }
    
        public string GetTypeName(TypeUsage typeUsage, string modelNamespace)
        {
            return typeUsage == null ? null : GetTypeName(typeUsage.EdmType, _ef.IsNullable(typeUsage), modelNamespace);
        }
    
        public string GetTypeName(EdmType edmType, string modelNamespace)
        {
            return GetTypeName(edmType, isNullable: null, modelNamespace: modelNamespace);
        }
    
        public string GetTypeName(EdmType edmType, bool? isNullable, string modelNamespace)
        {
            if (edmType == null)
            {
                return null;
            }
    
            var collectionType = edmType as CollectionType;
            if (collectionType != null)
            {
                return String.Format(CultureInfo.InvariantCulture, "ICollection<{0}>", GetTypeName(collectionType.TypeUsage, modelNamespace));
            }
    
            var typeName = _code.Escape(edmType.MetadataProperties
                                    .Where(p => p.Name == ExternalTypeNameAttributeName)
                                    .Select(p => (string)p.Value)
                                    .FirstOrDefault())
                ?? (modelNamespace != null && edmType.NamespaceName != modelNamespace ?
                    _code.CreateFullName(_code.EscapeNamespace(edmType.NamespaceName), _code.Escape(edmType)) :
                    _code.Escape(edmType));
    
            if (edmType is StructuralType)
            {
                return typeName;
            }
    
            if (edmType is SimpleType)
            {
                var clrType = UnderlyingClrType(edmType);
                if (!IsEnumType(edmType))
                {
                    typeName = _code.Escape(clrType);
                }
    
                typeName = FixNamespaces(typeName);
    
                return clrType.IsValueType && isNullable == true ?
                    String.Format(CultureInfo.InvariantCulture, "Nullable<{0}>", typeName) :
                    typeName;
            }
    
            throw new ArgumentException("edmType");
        }
    
        public Type UnderlyingClrType(EdmType edmType)
        {
            ArgumentNotNull(edmType, "edmType");
    
            var primitiveType = edmType as PrimitiveType;
            if (primitiveType != null)
            {
                return primitiveType.ClrEquivalentType;
            }
    
            if (IsEnumType(edmType))
            {
                return GetEnumUnderlyingType(edmType).ClrEquivalentType;
            }
    
            return typeof(object);
        }
    
        public object GetEnumMemberValue(MetadataItem enumMember)
        {
            ArgumentNotNull(enumMember, "enumMember");
    
            var valueProperty = enumMember.GetType().GetProperty("Value");
            return valueProperty == null ? null : valueProperty.GetValue(enumMember, null);
        }
    
        public string GetEnumMemberName(MetadataItem enumMember)
        {
            ArgumentNotNull(enumMember, "enumMember");
    
            var nameProperty = enumMember.GetType().GetProperty("Name");
            return nameProperty == null ? null : (string)nameProperty.GetValue(enumMember, null);
        }
    
        public System.Collections.IEnumerable GetEnumMembers(EdmType enumType)
        {
            ArgumentNotNull(enumType, "enumType");
    
            var membersProperty = enumType.GetType().GetProperty("Members");
            return membersProperty != null 
                ? (System.Collections.IEnumerable)membersProperty.GetValue(enumType, null)
                : Enumerable.Empty<MetadataItem>();
        }
    
        public bool EnumIsFlags(EdmType enumType)
        {
            ArgumentNotNull(enumType, "enumType");
    
            var isFlagsProperty = enumType.GetType().GetProperty("IsFlags");
            return isFlagsProperty != null && (bool)isFlagsProperty.GetValue(enumType, null);
        }
    
        public bool IsEnumType(GlobalItem edmType)
        {
            ArgumentNotNull(edmType, "edmType");
    
            return edmType.GetType().Name == "EnumType";
        }
    
        public PrimitiveType GetEnumUnderlyingType(EdmType enumType)
        {
            ArgumentNotNull(enumType, "enumType");
    
            return (PrimitiveType)enumType.GetType().GetProperty("UnderlyingType").GetValue(enumType, null);
        }
    
        public string CreateLiteral(object value)
        {
            if (value == null || value.GetType() != typeof(TimeSpan))
            {
                return _code.CreateLiteral(value);
            }
    
            return string.Format(CultureInfo.InvariantCulture, "new TimeSpan({0})", ((TimeSpan)value).Ticks);
        }
    
        public bool VerifyCaseInsensitiveTypeUniqueness(IEnumerable<string> types, string sourceFile)
        {
            ArgumentNotNull(types, "types");
            ArgumentNotNull(sourceFile, "sourceFile");
    
            var hash = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase);
            if (types.Any(item => !hash.Add(item)))
            {
                _errors.Add(
                    new CompilerError(sourceFile, -1, -1, "6023",
                        String.Format(CultureInfo.CurrentCulture, CodeGenerationTools.GetResourceString("Template_CaseInsensitiveTypeConflict"))));
                return false;
            }
            return true;
        }
    
        public IEnumerable<SimpleType> GetEnumItemsToGenerate(IEnumerable<GlobalItem> itemCollection)
        {
            return GetItemsToGenerate<SimpleType>(itemCollection)
                .Where(e => IsEnumType(e));
        }
    
        public IEnumerable<T> GetItemsToGenerate<T>(IEnumerable<GlobalItem> itemCollection) where T: EdmType
        {
            return itemCollection
                .OfType<T>()
                .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName))
                .OrderBy(i => i.Name);
        }
    
        public IEnumerable<string> GetAllGlobalItems(IEnumerable<GlobalItem> itemCollection)
        {
            return itemCollection
                .Where(i => i is EntityType || i is ComplexType || i is EntityContainer || IsEnumType(i))
                .Select(g => GetGlobalItemName(g));
        }
    
        public string GetGlobalItemName(GlobalItem item)
        {
            if (item is EdmType)
            {
                return ((EdmType)item).Name;
            }
            else
            {
                return ((EntityContainer)item).Name;
            }
        }
    
        public IEnumerable<EdmProperty> GetSimpleProperties(EntityType type)
        {
            return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
        }
    
        public IEnumerable<EdmProperty> GetSimpleProperties(ComplexType type)
        {
            return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type);
        }
    
        public IEnumerable<EdmProperty> GetComplexProperties(EntityType type)
        {
            return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
        }
    
        public IEnumerable<EdmProperty> GetComplexProperties(ComplexType type)
        {
            return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type);
        }
    
        public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(EntityType type)
        {
            return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
        }
    
        public IEnumerable<EdmProperty> GetPropertiesWithDefaultValues(ComplexType type)
        {
            return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null);
        }
    
        public IEnumerable<NavigationProperty> GetNavigationProperties(EntityType type)
        {
            return type.NavigationProperties.Where(np => np.DeclaringType == type);
        }
    
        public IEnumerable<NavigationProperty> GetCollectionNavigationProperties(EntityType type)
        {
            return type.NavigationProperties.Where(np => np.DeclaringType == type && np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many);
        }
    
        public FunctionParameter GetReturnParameter(EdmFunction edmFunction)
        {
            ArgumentNotNull(edmFunction, "edmFunction");
    
            var returnParamsProperty = edmFunction.GetType().GetProperty("ReturnParameters");
            return returnParamsProperty == null
                ? edmFunction.ReturnParameter
                : ((IEnumerable<FunctionParameter>)returnParamsProperty.GetValue(edmFunction, null)).FirstOrDefault();
        }
    
        public bool IsComposable(EdmFunction edmFunction)
        {
            ArgumentNotNull(edmFunction, "edmFunction");
    
            var isComposableProperty = edmFunction.GetType().GetProperty("IsComposableAttribute");
            return isComposableProperty != null && (bool)isComposableProperty.GetValue(edmFunction, null);
        }
    
        public IEnumerable<FunctionImportParameter> GetParameters(EdmFunction edmFunction)
        {
            return FunctionImportParameter.Create(edmFunction.Parameters, _code, _ef);
        }
    
        public TypeUsage GetReturnType(EdmFunction edmFunction)
        {
            var returnParam = GetReturnParameter(edmFunction);
            return returnParam == null ? null : _ef.GetElementType(returnParam.TypeUsage);
        }
    
        public bool GenerateMergeOptionFunction(EdmFunction edmFunction, bool includeMergeOption)
        {
            var returnType = GetReturnType(edmFunction);
            return !includeMergeOption && returnType != null && returnType.EdmType.BuiltInTypeKind == BuiltInTypeKind.EntityType;
        }
    }
    
    public static void ArgumentNotNull<T>(T arg, string name) where T : class
    {
        if (arg == null)
        {
            throw new ArgumentNullException(name);
        }
    }
    #>
    
    0 讨论(0)
  • 2020-11-27 19:25

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

    Add the following to the CodeStringGenerator class

    public string EntityClassOpening(EntityType entity)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1}partial class {2}{3} : {4}",
            Accessibility.ForType(entity),
            _code.SpaceAfter(_code.AbstractOption(entity)),
            _code.Escape(entity),
            _code.StringBefore(" : ", _typeMapper.GetTypeName(entity.BaseType)),
            "INotifyPropertyChanged");
    }
    
    
    public string Property(EdmProperty edmProperty)
    {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} {2} {{ {3}{6} {4}{5} }}",
            Accessibility.ForProperty(edmProperty),
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty),
            _code.SpaceAfter(Accessibility.ForGetter(edmProperty)),
            _code.SpaceAfter(Accessibility.ForSetter(edmProperty)),
            "set { _"+_code.Escape(edmProperty).ToLower()+" = value; OnPropertyChanged(\""+_code.Escape(edmProperty)+"\");}",
            "get { return _"+_code.Escape(edmProperty).ToLower()+"; }");
    
    }
    public string Private(EdmProperty edmProperty) {
        return string.Format(
            CultureInfo.InvariantCulture,
            "{0} {1} _{2};",
            "private",
            _typeMapper.GetTypeName(edmProperty.TypeUsage),
            _code.Escape(edmProperty).ToLower());
    
    }
    

    Add the following to the generator

    using System.ComponentModel;
    <#=codeStringGenerator.EntityClassOpening(entity)#>
    {
    <#
    var propertiesWithDefaultValues = typeMapper.GetPropertiesWithDefaultValues(entity);
    var collectionNavigationProperties = typeMapper.GetCollectionNavigationProperties(entity);
    var complexProperties = typeMapper.GetComplexProperties(entity);
    #>
    
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged(string propertyName)
    {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
    

    And abit further down

    foreach (var edmProperty in simpleProperties)
    {
    #>
    <#=codeStringGenerator.Private(edmProperty)#>
        <#=codeStringGenerator.Property(edmProperty)#>
    <#
    }
    
    
    foreach(var complexProperty in complexProperties)
    {
    #>
    <#=codeStringGenerator.Private(complexProperty)#>
        <#=codeStringGenerator.Property(complexProperty)#>
    <#
    }
    
    0 讨论(0)
  • 2020-11-27 19:25

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

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

    So my steps are:

    Create a base class for your model classes to extend:

    public abstract class BaseModel : INotifyPropertyChanged
    {
        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] String propertyName = null)
        {
            if (object.Equals(storage, value)) return false;
    
            storage = value;
            this.OnPropertyChanged(propertyName);
            return true;
        }
    
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    

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

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

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

    Find the line:

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

    and update it to be:

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

    Thanks Manolo!

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

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

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

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

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

    0 讨论(0)
提交回复
热议问题