Ajouter des annotations de données à une classe générée par le framework d'entité

93

J'ai la classe suivante générée par le framework d'entité:

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

Je voudrais en faire un champ obligatoire

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

Cependant, comme il s'agit de code généré, cela sera effacé. Je ne peux pas imaginer un moyen de créer une classe partielle car la propriété est définie par la classe partielle générée. Comment puis-je définir la contrainte de manière sûre?

P.Brian.Mackey
la source
Si votre propriété est int, elle est par défaut requise pour modelbinder donc votre attribut [Obligatoire] n'ajoutera rien ici.
Kirill Bestemyanov
@KirillBestemyanov - @ Html.ValidationMessageFor (model => model.Item.Item.ResourceTypeID) doit échouer côté client. Ce ne est pas.
P.Brian.Mackey

Réponses:

143

La classe générée ItemRequestsera toujours une partialclasse. Cela vous permet d'écrire une deuxième classe partielle qui est marquée avec les annotations de données nécessaires. Dans votre cas, la classe partielle ItemRequestressemblerait à ceci:

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
la source
11
La classe partielle ne sera pas régénérée. C'est la raison pour laquelle il est défini comme partiel.
MUG4N
avez-vous manqué le modificateur partiel? Utilisez-vous le même espace de noms?
MUG4N
3
Utilisateurs .NET Core: utilisez ModelMetadataType au lieu de MetadataType.
Bob Kaufman
1
Vous pouvez placer la classe partielle où vous voulez tant que l'espace de noms est identique
MUG4N
40

Comme MUG4N a répondu, vous pouvez utiliser des classes partielles, mais il sera préférable d'utiliser des interfaces à la place. Dans ce cas, vous aurez des erreurs de compilation si le modèle EF ne correspond pas au modèle de validation. Vous pouvez donc modifier vos modèles EF sans craindre que les règles de validation ne soient obsolètes.

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 */
    }
}

PS Si vous utilisez un type de projet différent de ASP.NET MVC (lorsque vous effectuez une validation manuelle des données) n'oubliez pas d'enregistrer vos validateurs

/* Global.asax or similar */

TypeDescriptor.AddProviderTransparent(
    new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Entity), typeof(IEntityMetadata)), typeof(Entity));
dimonser
la source
@dimonser belle solution, j'ai essayé d'ajouter des commentaires xml comme celui-ci également (pour les champs DB qui nécessitent une petite explication dans le code - c'est-à-dire pour être affichés dans intellitype) mais cela ne semble pas fonctionner. Une idée comment faire ça?
Percy
Bonjour @ Rick, vous pouvez mettre un commentaire sur une propriété d'interface, mais vous ne le verrez que lorsque vous travaillez avec une variable d'interface. Ou vous pouvez mettre un commentaire dans une classe partielle. Dans ce cas, vous le verrez lorsque vous travaillerez avec une instance de votre classe. Aucun autre cas disponible. Vous pouvez donc les utiliser tous les deux pour couvrir toutes les situations.Dans le premier cas, vous pouvez décrire les règles de validation de champ et dans le second cas, essayer de décrire les objectifs
dimonser
Réponse vraiment bien pensée, mais ma préférence serait de voir des erreurs de compilation si la validation n'est plus synchronisée avec la classe de cadre d'entité autogénérée. J'ai du mal à penser à une situation où vous pourriez vouloir valider une propriété qui n'est plus présente dans votre classe de cadre d'entité.
Mike
1
Cela ne fonctionne pas pour moi, cela dit que je dois implémenter l'interface IEntityMetadata ...
Worthy7
14

J'ai trouvé une solution comme la réponse de MUG4N , mais à la place, imbriquant la MetaDataclasse dans la classe d'entité, réduisant ainsi le nombre de classes dans votre liste d'espaces de noms publics, et éliminant le besoin d'avoir un nom unique pour chaque classe de métadonnées.

using System.ComponentModel.DataAnnotations;

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

            //...
        }
    }
}
Carter Medlin
la source
Je l'ai utilisé partout dans mon projet. Beaucoup plus facile à organiser. J'ajoute également des propriétés personnalisées [NotMapped]à l'intérieur de la classe partielle lorsque j'en ai besoin.
Carter Medlin
5

C'est une sorte d'extension de la réponse @dimonser si vous régénérez votre modèle de base de données, vous devrez ré-ajouter manuellement des interfaces sur ces classes.

Si vous avez l'estomac pour cela, vous pouvez également modifier votre .tt modèles:

Voici un exemple d'interfaces générant automatiquement sur certaines classes, il s'agit d'un fragment de la méthode de .ttremplacement EntityClassOpeningde la vôtre par la suite (et évidemment var stringsToMatchavec les noms et interfaces de vos entités).

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

Cependant, aucune personne normale ne devrait se faire cela, il a été prouvé dans la Bible que l'on va en Enfer pour cela.

Matas Vaitkevicius
la source
2

Je ne sais pas comment faire ce que vous demandez, mais il existe un moyen de contourner le problème. Validation dynamique des données en remplaçant les GetValidators de votre DataAnnotationsModelValidatorProvider personnalisé. Vous pouvez y lire les règles de validation de chaque champ (à partir d'une base de données, fichier de configuration, etc.) et ajouter des validateurs selon les besoins. Il a les valeurs ajoutées que votre validation n'est plus étroitement liée au modèle et peut être modifiée sans même avoir besoin de redémarrer le site. Bien sûr, cela pourrait être exagéré pour votre cas, mais c'était idéal pour le nôtre!

JTMon
la source
Nous l'avons fait lorsque nous avons mis en place cette structure pour la première fois. Depuis, nous sommes passés à NHibernate, mais cela n'a aucune incidence sur la solution. Notre code de validation a fonctionné tel quel sans changement (seule la couche d'accès aux données a été modifiée).
JTMon
1

Modifiez le modèle T4 en ajoutant les annotations requises, ce fichier est généralement nommé MODELNAME.tt

trouver où le T4 crée la classe et les méthodes pour savoir où les mettre.

     <#=codeStringGenerator.IgnoreJson(navigationProperty)#>


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

            return result;
        }

Vous devrez également ajouter les espaces de noms;

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

Reconstruisez vos classes en sauvegardant votre modèle, toutes vos méthodes doivent être annotées.

tswales
la source