Agregar anotaciones de datos a una clase generada por el marco de la entidad

93

Tengo la siguiente clase generada por el marco de la entidad:

public partial class ItemRequest
{
    public int RequestId { get; set; }
    //...

Me gustaría hacer de este un campo obligatorio

[Required]
public int RequestId { get;set; }

Sin embargo, debido a que este es un código generado, se borrará. No puedo imaginar una forma de crear una clase parcial porque la propiedad está definida por la clase parcial generada. ¿Cómo puedo definir la restricción de forma segura?

P. Brian Mackey
fuente
Si su propiedad es int, por defecto es requerida para modelbinder por lo que su atributo [Requerido] no agregará nada aquí.
Kirill Bestemyanov
@KirillBestemyanov - @ Html.ValidationMessageFor (model => model.Item.Item.ResourceTypeID) debería fallar en el lado del cliente. No es asi.
P.Brian.Mackey

Respuestas:

143

La clase generada ItemRequestsiempre será una partialclase. Esto le permite escribir una segunda clase parcial que está marcada con las anotaciones de datos necesarias. En su caso, la clase parcial ItemRequestse vería así:

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

//make sure the namespace is equal to the other partial class ItemRequest
namespace MvcApplication1.Models 
{
    [MetadataType(typeof(ItemRequestMetaData))]
    public partial class ItemRequest
    {
    }

    public class ItemRequestMetaData
    {
        [Required]
        public int RequestId {get;set;}

        //...
    }
}
MUG4N
fuente
11
La clase parcial no se regenerará. Esa es la razón por la que se define como parcial.
MUG4N
¿Echaste de menos el modificador parcial? ¿Usas el mismo espacio de nombres?
MUG4N
3
Usuarios de .NET Core: use ModelMetadataType en lugar de MetadataType.
Bob Kaufman
1
Puede poner la clase parcial donde desee siempre que el espacio de nombres sea idéntico
MUG4N
40

Como respondió MUG4N , puede usar clases parciales, pero será mejor usar interfaces en su lugar. En este caso, tendrá errores de compilación si el modelo EF no se corresponde con el modelo de validación. Por lo tanto, puede modificar sus modelos EF sin temor a que las reglas de validación estén desactualizadas.

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace YourApplication.Models
{
    public interface IEntityMetadata
    {
        [Required]
        Int32 Id { get; set; }
    }

    [MetadataType(typeof(IEntityMetadata))]
    public partial class Entity : IEntityMetadata
    {
        /* Id property has already existed in the mapped class */
    }
}

PD Si está utilizando un tipo de proyecto que es diferente de ASP.NET MVC (cuando realiza la validación manual de datos) no olvide registrar sus validadores

/* Global.asax or similar */

TypeDescriptor.AddProviderTransparent(
    new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Entity), typeof(IEntityMetadata)), typeof(Entity));
dimonser
fuente
@dimonser buena solución, intenté agregar comentarios xml como este también (para aquellos campos de base de datos que necesitan una pequeña explicación en el código, es decir, para mostrarse en intellitype) pero no parece funcionar. ¿Alguna idea de cómo hacerlo?
Percy
Hola @Rick, puedes poner un comentario en una propiedad de interfaz, pero lo verás solo cuando trabajes con una variable de interfaz. O puede poner un comentario en una clase parcial. En este caso, lo verá cuando trabaje con una instancia de su clase. No hay otros casos disponibles. Así que usted puede utilizar los dos para cubrir todas las situaciones, en el primer caso se puede describir reglas de validación de campo y en el segundo caso, tratar de describir los propósitos
dimonser
Respuesta realmente bien pensada, pero mi preferencia sería ver errores de compilación si la validación ya no está sincronizada con la clase de marco de entidad autogenerada. Estoy luchando por pensar en una situación en la que es posible que desee validar una propiedad que ya no está presente en su clase de marco de entidad.
Mike
1
Esto no funciona para mí, dice que necesito implementar la interfaz IEntityMetadata ...
Worthy7
14

Encontré una solución como la respuesta de MUG4N , pero en cambio, anidando la MetaDataclase dentro de la clase de entidad, reduciendo así el número de clases en su lista de espacios de nombres públicos y eliminando la necesidad de tener un nombre único para cada clase de metadatos.

using System.ComponentModel.DataAnnotations;

namespace MvcApplication1.Models 
{
    [MetadataType(typeof(MetaData))]
    public partial class ItemRequest
    {
        public class MetaData
        {
            [Required]
            public int RequestId;

            //...
        }
    }
}
Carter Medlin
fuente
He estado usando esto en todo mi proyecto. Mucho más fácil de organizar. También agrego propiedades personalizadas usando [NotMapped]dentro de la clase parcial también cuando las necesito.
Carter Medlin
5

Esta es una especie de extensión de la respuesta de @dimonser. Si regenera su modelo de base de datos, tendrá que volver a agregar manualmente las interfaces en esas clases.

Si tienes estómago para ello también puedes modificar tus .ttplantillas:

Aquí hay un ejemplo de interfaces de generación automática en algunas clases, este es un fragmento de .ttsimplemente reemplazar el EntityClassOpeningmétodo en el suyo con el siguiente (y obviamente var stringsToMatchcon los nombres de sus entidades e interfaces).

public string EntityClassOpening(EntityType entity)
{
    var stringsToMatch = new Dictionary<string,string> { { "Answer", "IJourneyAnswer" }, { "Fee", "ILegalFee" } };
    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)),
        stringsToMatch.Any(o => _code.Escape(entity).Contains(o.Key)) ? " : " + stringsToMatch.Single(o => _code.Escape(entity).Contains(o.Key)).Value : string.Empty);
}

Sin embargo, ninguna persona normal debería hacerse esto a sí mismo, se ha demostrado en la Biblia que uno va al infierno por esto.

Matas Vaitkevicius
fuente
2

No estoy seguro de cómo hacer lo que estás pidiendo, pero hay una forma de evitarlo. Validación dinámica de datos anulando los GetValidators de su DataAnnotationsModelValidatorProvider personalizado. En él puede leer las reglas para validar cada campo (de una base de datos, archivo de configuración, etc.) y agregar validadores según sea necesario. Tiene los valores agregados de que su validación ya no está estrechamente unida al modelo y se puede cambiar sin necesidad de reiniciar el sitio. Por supuesto que podría ser excesivo para su caso, ¡pero fue ideal para el nuestro!

JTMon
fuente
Lo hicimos cuando implementamos esta estructura por primera vez. Desde entonces, nos cambiamos a NHibernate, pero esto no tiene nada que ver con la solución. Nuestro código de validación funcionó como está sin cambios (solo se cambió la capa de acceso a datos).
JTM
1

Modifique la plantilla T4 agregando las anotaciones requeridas, este archivo generalmente se llama MODELNAME.tt

encuentre dónde está creando el T4 la clase y los métodos para saber dónde colocarlos.

     <#=codeStringGenerator.IgnoreJson(navigationProperty)#>


//create this method in file
public string IgnoreJson(NavigationProperty navigationProperty){
            string result = navigationProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? "" : @"[JsonIgnore]
    [IgnoreDataMember]";

            return result;
        }

También deberá agregar los espacios de nombres;

<#=codeStringGenerator.UsingDirectives(inHeader: false)#>
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
using System.Runtime.Serialization;

Reconstruya sus clases guardando su modelo, todos sus métodos deben estar anotados.

tswales
fuente