Aggiungi annotazioni di dati a una classe generata dal framework di entità


93

Ho la seguente classe generata da Entity Framework:

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

Vorrei renderlo un campo obbligatorio

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

Tuttavia, poiché questo è codice generato, verrà cancellato. Non riesco a immaginare un modo per creare una classe parziale perché la proprietà è definita dalla classe parziale generata. Come posso definire il vincolo in modo sicuro?


Se la tua proprietà è int, è richiesta per impostazione predefinita per modelbinder, quindi il tuo attributo [Obbligatorio] non aggiungerà nulla qui.
Kirill Bestemyanov

@KirillBestemyanov - @ Html.ValidationMessageFor (model => model.Item.Item.ResourceTypeID) dovrebbe fallire lato client. Non è così.
P.Brian.Mackey

Risposte:


143

La classe generata ItemRequestsarà sempre una partialclasse. Ciò consente di scrivere una seconda classe parziale contrassegnata con le annotazioni dei dati necessarie. Nel tuo caso la classe parziale ItemRequestsarebbe simile a questa:

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

        //...
    }
}

11
La classe parziale non verrà rigenerata. Questo è il motivo per cui è definito parziale.
MUG4N

ti sei perso il modificatore parziale? Usi lo stesso spazio dei nomi?
MUG4N

3
Utenti .NET Core: utilizzare ModelMetadataType invece di MetadataType.
Bob Kaufman

1
Puoi mettere la classe parziale dove vuoi purché lo spazio dei nomi sia identico
MUG4N

40

Come ha risposto MUG4N puoi usare classi parziali ma sarà meglio usare invece le interfacce . In questo caso si verificheranno errori di compilazione se il modello EF non corrisponde al modello di convalida. Quindi puoi modificare i tuoi modelli EF senza temere che le regole di convalida siano obsolete.

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 Se utilizzi un tipo di progetto diverso da ASP.NET MVC (quando esegui la convalida manuale dei dati) non dimenticare di registrare i tuoi validatori

/* Global.asax or similar */

TypeDescriptor.AddProviderTransparent(
    new AssociatedMetadataTypeTypeDescriptionProvider(typeof(Entity), typeof(IEntityMetadata)), typeof(Entity));

@dimonser bella soluzione, ho provato ad aggiungere anche commenti xml come questo (per quei campi DB che necessitano di una piccola spiegazione nel codice - cioè per essere visualizzati in intellitype) ma non sembra funzionare. Qualche idea di come farlo?
Percy

Ciao @Rick, puoi inserire un commento su una proprietà dell'interfaccia, ma lo vedrai solo quando lavori con una variabile dell'interfaccia. Oppure puoi inserire un commento in una classe parziale. In questo caso lo vedrai quando lavori con un'istanza della tua classe. Nessun altro caso disponibile. Quindi puoi usarli entrambi per coprire tutte le situazioni, nel primo caso puoi descrivere le regole di convalida del campo e nel secondo caso provare a descrivere gli scopi
dimonser

Risposta davvero ben congegnata, ma la mia preferenza sarebbe quella di vedere errori di compilazione se la convalida non è più sincronizzata con la classe framework di entità generata automaticamente. Faccio fatica a pensare a una situazione in cui potresti voler convalidare una proprietà che non è più presente nella tua classe di framework di entità.
Mike

1
Questo non funziona per me, dice che devo implementare l'interfaccia IEntityMetadata ...
Worthy7

14

Ho trovato una soluzione come la risposta di MUG4N , ma invece, annidando la MetaDataclasse all'interno della classe entità, riducendo così il numero di classi nel tuo elenco di spazio dei nomi pubblico ed eliminando la necessità di avere un nome univoco per ogni classe di metadati.

using System.ComponentModel.DataAnnotations;

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

            //...
        }
    }
}

L'ho usato in tutto il mio progetto. Molto più facile da organizzare. Aggiungo anche proprietà personalizzate usando anche [NotMapped]all'interno della classe parziale quando ne ho bisogno.
Carter Medlin

5

Questa è una sorta di estensione alla risposta di @dimonser se rigeneri il tuo modello db dovrai aggiungere manualmente le interfacce su quelle classi.

Se hai stomaco per questo, puoi anche modificare i tuoi .ttmodelli:

Ecco un esempio di interfacce di generazione automatica su alcune classi, questo è un frammento del .ttsolo EntityClassOpeningmetodo di sostituzione nel tuo con il seguente (e ovviamente var stringsToMatchcon i nomi e le interfacce delle tue entità).

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

Nessuna persona normale dovrebbe fare questo a se stessa, però, è stato dimostrato nella Bibbia che si va all'Inferno per questo.


2

Non sono sicuro di come fare quello che chiedi, ma c'è un modo per aggirarlo. Convalida dinamica dei dati sovrascrivendo i GetValidators del tuo DataAnnotationsModelValidatorProvider personalizzato. In esso puoi leggere le regole per la convalida di ogni campo (da un database, file di configurazione, ecc.) E aggiungere validatori secondo necessità. Ha il valore aggiunto che la tua convalida non è più strettamente collegata al modello e può essere modificata senza bisogno di riavviare il sito. Ovviamente potrebbe essere eccessivo per il tuo caso, ma era l'ideale per il nostro!


Lo abbiamo fatto quando abbiamo implementato per la prima volta questa struttura. Da allora siamo passati a NHibernate, ma questo non ha alcuna relazione con la soluzione. Il nostro codice di convalida ha funzionato così com'è senza modifiche (è stato modificato solo il livello di accesso ai dati).
JTM

1

Modificare il modello T4 aggiungendo le annotazioni richieste, questo file è solitamente denominato MODELNAME.tt

trova dove il T4 sta creando la classe e i metodi per sapere dove metterli.

     <#=codeStringGenerator.IgnoreJson(navigationProperty)#>


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

            return result;
        }

Dovrai anche aggiungere gli spazi dei nomi;

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

Ricostruisci le tue classi salvando il tuo modello, tutti i tuoi metodi dovrebbero essere annotati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.