13

ASP.NET MVC 4, EF5, Codice primo, SQL Server 2012 espressoASP.NET MVC 4, EF5, Proprietà univoca nel modello: best practice?

Qual è delle migliori pratiche per far rispettare un valore unico in un modello? Ho una classe di posti che ha una proprietà 'url' che dovrebbe essere unica per ogni posto.

public class Place 
{ 
     [ScaffoldColumn(false)] 
     public virtual int PlaceID { get; set; } 

     [DisplayName("Date Added")] 
     public virtual DateTime DateAdded { get; set; } 

     [Required(ErrorMessage = "Place Name is required")] 
     [StringLength(100)] 
     public virtual string Name { get; set; } 

     public virtual string URL { get; set; } 
}; 

Perché non c'è solo un'annotazione di dati [Unique] che è possibile inserire su di esso?

Ho visto 1 o 2 discussioni su questo, ma non parlo delle migliori pratiche. Usando Code First puoi in qualche modo dire al database di impostare un vincolo univoco sul campo nel database?

Qual è il modo più semplice - e quali sono le migliori pratiche?

risposta

23

Per quanto possa sembrare folle, la migliore pratica al giorno d'oggi è quella di non utilizzare la convalida integrata e utilizzare invece FluentValidation. Quindi il codice sarà molto facile da leggere e super-gestibile dal momento che la convalida sarà gestita su una classe separata che significa meno codice spaghetti.

Pseudo-esempio di ciò che stai cercando di ottenere.

[Validator(typeof(PlaceValidator))] 
class Place 
{ 
    public int Id { get; set; } 
    public DateTime DateAdded { get; set; } 
    public string Name { get; set; } 
    public string Url { get; set; } 
} 

public class PlaceValidator : AbstractValidator<Place> 
{ 
    public PlaceValidator() 
    { 
     RuleFor(x => x.Name).NotEmpty().WithMessage("Place Name is required").Length(0, 100); 
     RuleFor(x => x.Url).Must(BeUniqueUrl).WithMessage("Url already exists"); 
    } 

    private bool BeUniqueUrl(string url) 
    { 
     return new DataContext().Places.FirstOrDefault(x => x.Url == url) == null 
    } 
} 
+0

Interessante.Quindi, avrò bisogno di usarlo solo per qualsiasi proprietà Unica - tutto il resto può rimanere così com'è, codice convenzionale Prima no? Copre tutte le basi, le condizioni di gara, l'unicità affidabile al 100% sul database, ecc.? – niico

+0

ho anche bisogno di installare il pacchetto nuget fluentvalidation? – niico

+0

Questo esempio funzionerà con POCO senza problemi, lo uso su tutti i miei progetti. Come puoi vedere, controlla uniquness nel database su "TryValidateModel()", dopo di che chiami di solito "_db.SaveChanges();" quindi non c'è praticamente alcuna latenza. Sì, ovviamente avrai bisogno del pacchetto nuget. – sed

4

L'unico modo è aggiornare la migrazione una volta generata, supponendo che le si stia utilizzando, in modo da imporre un vincolo univoco sulla colonna.

public override void Up() { 
    // create table 
    CreateTable("dbo.MyTable", ...; 
    Sql("ALTER TABLE MyTable ADD CONSTRAINT U_MyUniqueColumn UNIQUE(MyUniqueColumn)"); 
} 
public override void Down() { 
    Sql("ALTER TABLE MyTable DROP CONSTRAINT U_MyUniqueColumn"); 
} 

Il bit rigido, tuttavia, sta applicando il vincolo a livello di codice prima di arrivare al database. Per questo potrebbe essere necessario un repository che contenga l'elenco completo di valori univoci e si assicuri che le nuove entità non violino questo attraverso un metodo factory.

// Repository for illustration only 
public class Repo { 
    SortedList<string, Entity1> uniqueKey1 = ...; // assuming a unique string column 
    public Entity1 NewEntity1(string keyValue) { 
    if (uniqueKey1.ContainsKey(keyValue) throw new ArgumentException ... ; 
    return new Entity1 { MyUniqueKeyValue = keyValue }; 
    } 
} 

Riferimenti:

Footnot e:

Ci sono un sacco di richieste di [Unica] nel codice prima, ma sembra che non è nemmeno facendo versione 6: http://entityframework.codeplex.com/wikipage?title=Roadmap

Si potrebbe provare il voto per qui: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support

+0

Non ho mai usato un repository o fabbrica - qualche link/tutorial raccomandazioni? Suppongo che potrei anche "manualmente" convalidare l'input dell'utente per i nuovi Luoghi per garantire che l'URL sia univoco? Può essere applicato a livello di oggetto nel caso in cui la convalida sia errata, ad esempio impostando un vincolo univoco in SQL Server (forse il tuo suggerimento lo copre?) Sembra che ci potrebbe essere una soluzione migliore a questo perché è un requisito comune? – niico

+0

Nota: questa è un'app relativamente piccola con un singolo sviluppatore per ora - quindi una soluzione semplice/rapida sarebbe buona. Devo anche imporre un campo unico per gli utenti, ad esempio nel loro handle o email di Twitter. – niico

+0

@ user2254951. Modifica con codice e collegamenti forniti. Avrai bisogno di un luogo comune per verificare l'univocità, da qui il mio suggerimento singleton che vivrà in memoria per tutta la durata dell'appodominio in IIS, dovrai solo inizializzarlo alla prima query, ma è abbastanza facile. –

3

È possibile eseguire questo controllo a livello di codice prima di salvare i dati nelle tabelle del database.

È possibile provare a utilizzare l'annotazione dei dati Remote sul tuo viewmodel per eseguire una convalida asincrona per rendere l'interfaccia utente più reattiva.

public class CreatePlaceVM 
{ 
    [Required] 
    public string PlaceName { set;get;} 

    [Required] 
    [Remote("IsExist", "Place", ErrorMessage = "URL exist!") 
    public virtual string URL { get; set; } 
} 

Assicurarsi di avere un metodo di IsExists azione nel tuo Placecontroller che accetta un paramtere URL e controllare che againist vostro tavolo e restituire true o false.

Questo msdn link ha un programma di esempio per mostrare come implementare l'attributo Remote per eseguire la convalida istantanea.

Inoltre, se si utilizza una stored procedure (Per qualche ragione), si può fare un controllo EXISTS lì prima che la query INSERT.

+0

Questo sembra interessante: devo implementare qualcos'altro per farlo funzionare? Cosa succede se un altro utente esegue la stessa query allo stesso tempo? C'è un modo per impedire anche duplicati a livello di deeper/database, ad esempio? – niico

+0

Fatelo due volte (una volta con il metodo sopra (per indicare all'utente di cambiare il valore duplicato) e il secondo nel metodo di azione HttpPost appena prima di salvare (Controllare il db per vedere se l'elemento esiste o no). Non ci sono problemi a meno che tu non abbia problemi di prestazioni enormi (che suppongo tu non abbia) – Shyju

+0

Sì, un controllo come questo dovrebbe essere in una transazione con il codice che inserisce il nuovo URL, per garantire che non ci siano condizioni di gara. transazione, esiste ?, inserire, commit – AaronLS

6

Questo collegamento potrebbe aiutare: https://github.com/fatihBulbul/UniqueAttribute

[Table("TestModels")] 
public class TestModel 
{ 

    [Key] 
    public int Id { get; set; } 

    [Display(Name = "Some", Description = "desc")] 
    [Unique(ErrorMessage = "This already exist !!")] 
    public string SomeThing { get; set; } 
} 
1

ho risolto il problema generale di consentire l'iniezione del costruttore nel vostro flusso di convalida, integrando nel meccanismo DataAnnotations normali senza ricorrere a strutture in this answer, consentendo una per scrivere:

class MyModel 
{ 
    ... 
    [Required, StringLength(42)] 
    [ValidatorService(typeof(MyDiDependentValidator), ErrorMessage = "It's simply unacceptable")] 
    public string MyProperty { get; set; } 
    .... 
} 

public class MyDiDependentValidator : Validator<MyModel> 
{ 
    readonly IUnitOfWork _iLoveWrappingStuff; 

    public MyDiDependentValidator(IUnitOfWork iLoveWrappingStuff) 
    { 
     _iLoveWrappingStuff = iLoveWrappingStuff; 
    } 

    protected override bool IsValid(MyModel instance, object value) 
    { 
     var attempted = (string)value; 
     return _iLoveWrappingStuff.SaysCanHazCheez(instance, attempted); 
    } 
} 

Con alcune classi di supporto (guarda laggiù), lo cavi p e.g. in ASP.NET MVC come così nel : -

DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
    typeof(ValidatorServiceAttribute), 
    (metadata, context, attribute) => 
     new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));