Cómo recibir notificaciones de cambio de propiedad con EF 4.x DbContext generator

Estoy jugando con Entity Framework 4.3, y entonces estoy usando el generador de DbContext para crear las clases de contexto y entidad.

Con la plantilla de generador de código EF 4 predeterminada, las clases de entidad implementan INotifyPropertyChanged y también agregan métodos parciales Changing y Changed en los establecedores de propiedades.

Cuando utilizo el generador EF 4.x DbContext, como se muestra a continuación, las clases de entidad son mucho más livianas y no incluyen ningún medio para rastrear los cambios de propiedad.

enter image description here

Aquí hay un ejemplo:

 //------------------------------------------------------------------------------ //  // This code was generated from a template. // // Manual changes to this file may cause unexpected behavior in your application. // Manual changes to this file will be overwritten if the code is regenerated. //  //------------------------------------------------------------------------------ using System; using System.Collections.Generic; namespace SomeNamespace { public partial class SomeTable { public SomeTable() { this.Children = new HashSet(); } public long parent_id { get; set; } public long id { get; set; } public string filename { get; set; } public byte[] file_blob { get; set; } public virtual Parent Parent { get; set; } public virtual ICollection Children { get; set; } } } 

Debo extrañar una pieza importante del rompecabezas, pero mis búsquedas han sido infructuosas. Entonces mi pregunta es: ¿cómo puedo haber generado los tipos que incluyen notificaciones de cambio de propiedad con EF 4.3?

Editar

Estoy completamente de acuerdo con @derape answer’s; pero tengo curiosidad sobre por qué tendría que cambiar el archivo .tt cuando la plantilla de generación de código predeterminada EF 4 ya tiene los ganchos. Quiero decir, ¿qué pasa cuando se vincula a WPF DependencyProperty ‘? Sin INotifyPropertyChanged, los cambios realizados por un comando a un grupo de propiedades en un grupo de objetos no se reflejarán en la UI. ¿Qué me estoy perdiendo?

Recientemente me encontré con este problema, edité mi Entity.tt para implementar los siguientes cambios, un parche rápido pero funciona muy bien.

Agregue lo siguiente a la clase CodeStringGenerator

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

Agregue lo siguiente al generador

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

Y un poco más abajo

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

He creado una variación en la respuesta de Anders con las siguientes diferencias:

  • Menos cambios en el archivo Entity.tt
  • Usar una clase base para la implementación INotifyPropertyChanged (útil para introducir otras funcionalidades comunes)
  • Diseño de código más limpio en las clases de modelo generadas

Entonces mis pasos son:

Crea una clase base para que tus clases modelo se extiendan:

 public abstract class BaseModel : INotifyPropertyChanged { protected bool SetProperty(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)); } } 

¡Gracias Juan Pable Gomez por la mejora sugerida! Me gusta su mejora incluso si los otros revisores no lo hicieron 🙂

Actualice el método EntityClassOpening en el archivo Entity.tt a lo siguiente:

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

Encuentra la línea:

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

y actualizarlo para que sea:

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

Gracias Manolo!

Actualice el método de propiedad en el archivo Entity.tt a lo siguiente:

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

¡Terminaste! Ahora sus clases modelo se verán así:

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

Por favor, siéntase libre de (intente) editar esta solución si puede ver otras mejoras.

Estaba intentando editar la solución de Brian Hinchey Pero EDIT fue rechazada. Luego publico aquí mis addatos.

Esta solución genera menos código para cada propiedad Setter, aprovechando el atributo CallerMemberName .

NOTA: Estoy usando EF 6.1 y funciona bastante bien.

BaseClass ahora se ve así.

 public abstract class BaseModel : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected bool SetProperty(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)); } } } 

Actualice el método EntityClassOpening en Entity.tt. Permanece exactamente como el de Brian:

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

Como dice Bryan, recuerda el cambio:

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

Y mi último cambio es por el método de propiedad

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

Y finalmente aquí está la clase se ve así:

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

Esto hace que las clases generadas sean más claras.

Recientemente estaba trabajando con la biblioteca PropertyChanged.Fody pero por razones desconocidas (al menos para mí) No funciona correctamente algunas veces. Esa es la razón por la que estoy aquí. Esta solución (la solución de Bryan) funciona todo el tiempo.

La solución de Anders anterior funciona, sin embargo, hay un par de errores que encontré durante el proceso:

En el Paso 1, donde dice “Agregar lo siguiente a la clase CodeStringGenerator”, solo se puede agregar la función privada de cadena pública (…) porque las otras dos ya existen. Por lo tanto, debe encontrarlas y reemplazar esas dos funciones. no los agregue, de lo contrario, obtendrá errores. Para encontrar exactamente dónde debe colocarlos, realice una búsqueda de “clase pública CodeStringGenerator” y busque las funciones que se encuentran debajo.

En el paso 2 “Agregue lo siguiente al generador”, solo necesita agregar la línea “using System.ComponentModel” y las líneas de (e incluyendo) “public event PropertyChangedEventHandler …”. Nuevamente, las otras líneas ya existen, las encontrará cerca de la parte superior del archivo .tt.

En el paso 3 “Y un poco más abajo”, ambos bucles “foreach” también ya existen, por lo que deben reemplazarse y no agregarse. En última instancia, solo se agrega una línea a cada bucle foreach, “<# = codeStringGenerator.Private (edmProperty) #>” y “<# = codeStringGenerator.Private (complexProperty) #>” en cada bucle, respectivamente.

Además, no reemplace el bucle equivocado, hay dos bucles adicionales por encima de los que necesita reemplazar, que pasan por los mismos objetos … asegúrese de reemplazar los correctos 🙂

Pensé que mencionaría esto porque como novato MVVM / EF (usado para usar NHibernate) tuve que hacer estos ajustes para que funcione.

Bueno, depende de lo que estás tratando de hacer. Si solo desea implementar propiedades / métodos personalizados, puede usar la funcionalidad de clases parciales. Si quiere cambiar, digamos setter / getter de sus propiedades en el diseñador de su entidad, tendría que adaptar el archivo de la plantilla del generador dbContext. Es una plantilla T4.

Creé lo siguiente para utilizar con EF 6.1.2 pero las pruebas han sido bastante limitadas, así que utilícelas bajo su propio riesgo.

 <#@ 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(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(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(); #> //------------------------------------------------------------------------------ //  // <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine1")#> // // <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine2")#> // <#=CodeGenerationTools.GetResourceString("Template_GeneratedCodeCommentLine3")#> //  //------------------------------------------------------------------------------ <#=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 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(); } 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 types, string sourceFile) { ArgumentNotNull(types, "types"); ArgumentNotNull(sourceFile, "sourceFile"); var hash = new HashSet(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 GetEnumItemsToGenerate(IEnumerable itemCollection) { return GetItemsToGenerate(itemCollection) .Where(e => IsEnumType(e)); } public IEnumerable GetItemsToGenerate(IEnumerable itemCollection) where T: EdmType { return itemCollection .OfType() .Where(i => !i.MetadataProperties.Any(p => p.Name == ExternalTypeNameAttributeName)) .OrderBy(i => i.Name); } public IEnumerable GetAllGlobalItems(IEnumerable 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 GetSimpleProperties(EntityType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); } public IEnumerable GetSimpleProperties(ComplexType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type); } public IEnumerable GetComplexProperties(EntityType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); } public IEnumerable GetComplexProperties(ComplexType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is ComplexType && p.DeclaringType == type); } public IEnumerable GetPropertiesWithDefaultValues(EntityType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); } public IEnumerable GetPropertiesWithDefaultValues(ComplexType type) { return type.Properties.Where(p => p.TypeUsage.EdmType is SimpleType && p.DeclaringType == type && p.DefaultValue != null); } public IEnumerable GetNavigationProperties(EntityType type) { return type.NavigationProperties.Where(np => np.DeclaringType == type); } public IEnumerable 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)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 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 arg, string name) where T : class { if (arg == null) { throw new ArgumentNullException(name); } } #> 

Trabajo con Visual Basic y tiendo a disfrutar de la refacturación de architectures heredadas y no estratificadas.

Por lo tanto, he resuelto cómo hacerlo utilizando las respuestas de arriba, pero en Entity Framework 6.1.3 y .net 4.6.1.

He traducido de las respuestas dadas por otros, así que solo me puedo atribuir el mérito por el poco redescubrimiento y traducción que he hecho esta noche.

El proyecto para el que esto es es bastante pequeño, es winforms y quería enlaces de datos en lugar de una serie de actualizaciones manuales para los controles. NO quería agregar aún más complejidad agregando más separación ya que aquí no había suficiente beneficio. Solo hay suficiente para justificar esto :).

Espero que ayude a otros codificadores de VB.

La clase base:

 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,  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 

La propiedad de cadena pública no existe en el archivo VB TT (asegúrese de que sea el ModelName.tt no el ModelName.Context.tt). Tenga en cuenta el uso del condicional para la comparación que admite nulos. ¡Si no tienes esto, no lograrás que la cosa dispare!

 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 

Si descubro algo significativo que agregar a esto, lo haré.

Tenga en cuenta mi caso de uso: proyecto VB liviano, sin justificación para introducir MVVM / MVC y capas de repository, pero hay muchas razones para querer enlaces de datos en lugar de molestas actualizaciones condicionales hacia y desde controles, muchos de los cuales tienen un EditValue que son de tipo Object ( Controles Devexpress winforms).

Debe continuar usando ObjectContext si desea notificaciones de cambio de propiedad cuando se vincula directamente a las clases del modelo de datos.

Las clases ligeras de DBContext son para patrones como MVVM o MVVMC, donde su modelo de vista implementa las notificaciones de cambio de propiedad y su UI solo se une a las propiedades del modelo de vista. Nunca se une a las clases de modelo de datos en estos patrones.