13

ASP.NET MVC 4, EF5, -Code Erste, SQL Server 2012 ExpressASP.NET MVC 4, EF5, Einzigartige Eigenschaft im Modell - Best Practice?

Was best practice ist einen eindeutigen Wert in einem Modell zu erzwingen? Ich habe eine Klasse Orte, die eine "URL" -Eigenschaft hat, die für jeden Ort eindeutig sein sollte.

Warum gibt es nicht nur eine eindeutige [eindeutige] Datenannotation, die Sie darauf platzieren können?

Ich habe 1 oder 2 Diskussionen darüber gesehen, aber keine Rede von Best Practice. Mit Code First können Sie die Datenbank irgendwie anweisen, eine eindeutige Einschränkung für das Feld in der Datenbank festzulegen?

Was ist der einfachste Weg - und was ist die beste Vorgehensweise?

Antwort

23

So verrückt, wie es klingen mag, ist die beste Praxis heute nicht verwenden integrierte Validierung und stattdessen FluentValidation verwenden. Dann wird der Code sehr leicht zu lesen und super wartbar sein, da die Validierung auf separaten Klassen verwaltet wird, was weniger Spaghetti-Code bedeutet.

Pseudo-Beispiel für das, was Sie erreichen möchten.

[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

Interessant.Also muss ich das nur für beliebige Unique-Eigenschaften verwenden - alles andere kann so bleiben wie es ist, herkömmlicher Code zuerst richtig? Beinhaltet dies alle Grundlagen, Rennbedingungen, 100% zuverlässige Eindeutigkeit in der Datenbank usw.? – niico

+0

muss ich auch das fluentvalidation nugget-Paket installieren? – niico

+0

Dieses Beispiel funktioniert problemlos mit POCO, ich benutze es für alle meine Projekte. Wie Sie sehen können, wird es in der Datenbank auf 'TryValidateModel()' nach der Alleinstellung suchen, nach der Sie normalerweise '_db.SaveChanges(); 'aufrufen, so dass es im Grunde überhaupt keine Latenz gibt. Ja, natürlich brauchen Sie ein Paket. – sed

4

Die einzige Möglichkeit besteht darin, Ihre Migration zu aktualisieren, sobald Sie sie erstellt haben, vorausgesetzt, dass Sie sie verwenden, sodass eine eindeutige Einschränkung für die Spalte erzwungen wird.

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

Das harte Bit erzwingt jedoch die Einschränkung auf Codeebene, bevor Sie in die Datenbank gelangen. Dazu benötigen Sie möglicherweise ein Repository, das die vollständige Liste der eindeutigen Werte enthält, und stellt sicher, dass neue Entitäten dies nicht durch eine Factory-Methode verletzen.

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

Referenzen:

Footnot e:

Es gibt viele Anfragen nach [Einzigartige] in Code zuerst, aber es sieht aus wie es nicht einmal Version 6 machen wird: http://entityframework.codeplex.com/wikipage?title=Roadmap

Sie versuchen es hier stimmen könnte: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support

+0

Ich habe noch nie ein Repository oder eine Fabrik - Link/Tutorial Empfehlungen? Ich nehme an, ich könnte auch Benutzereingaben für neue Orte 'manuell' validieren, um sicherzustellen, dass die URL eindeutig ist? Kann dies auf der Objektebene erzwungen werden, wenn die Validierung fehlerhaft ist - wie das Setzen einer eindeutigen Einschränkung in SQL Server (vielleicht deckt Ihr Vorschlag dies ab?) Scheint es könnte eine bessere Lösung für dieses sein, da es eine allgemeine Anforderung ist? – niico

+0

Hinweis - dies ist eine relativ kleine App mit einem einzigen Entwickler für jetzt - so eine einfache/schnelle Lösung wäre gut. Ich muss auch ein eindeutiges Feld für Benutzer erzwingen - sagen Sie in ihrem Twitter-Handle oder E-Mail. – niico

+0

@ user2254951. Edit mit Code und Links zur Verfügung gestellt. Sie werden einen gemeinsamen Ort benötigen, um nach Eindeutigkeit zu suchen, daher mein Singleton-Vorschlag, da er für die Dauer Ihrer App-Domäne in IIS im Speicher verbleibt, Sie müssen ihn nur bei der ersten Abfrage einsetzen, aber das ist einfach genug. –

3

Sie können dies in der Codeebene vor dem Speichern der Daten in den Datenbanktabellen durchführen.

Sie können versuchen, die Datenannotation Remote in Ihrem Ansichtsmodell zu verwenden, um eine asynchrone Überprüfung durchzuführen, damit die Benutzeroberfläche besser reagiert.

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

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

Stellen Sie sicher, dass eine IsExists Aktionsmethode in Ihrem Placecontroller haben, die eine URL paramtere akzeptiert und überprüfen Sie es againist Tisch und wahr oder falsch zurück.

Diese msdn link enthält ein Beispielprogramm, um zu zeigen, wie das Remote-Attribut implementiert wird, um eine sofortige Validierung durchzuführen.

Auch, wenn Sie eine gespeicherte Prozedur (Aus irgendeinem Grunde) verwenden, können Sie einen EXISTS Scheck dort vor der INSERT Abfrage tun.

+0

Das sieht cool aus - muss ich etwas anderes implementieren, damit es funktioniert? Was passiert, wenn ein anderer Benutzer die gleiche Abfrage zur gleichen Zeit ausführt - gibt es eine Möglichkeit, Dubletten auf tieferer/Datenbankebene zu verhindern, sagen wir? – niico

+0

Tun Sie es zweimal (einmal mit dem obigen Weg (um anzuzeigen, dass der Benutzer den duplizierten Wert ändern kann) und zweitens in Ihrer HttpPost-Aktionsmethode, kurz bevor Sie speichern (Überprüfen Sie die db, um zu sehen, ob das Element existiert oder nicht) Habe kein Problem, außer du hast ein paar große Performance-Probleme (die ich vermute, hast du nicht) – Shyju

+0

Yeh, sollte eine Überprüfung wie diese in einer Transaktion mit dem Code sein, der die neue URL einfügt, um keine Race Conditions zu gewährleisten Transaktion, existiert ?, einfügen, commit – AaronLS

6

könnte Dieser Link helfen: 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

löste ich das allgemeine Problem der Konstruktor Injektion in Ihrem Validation Fluss ermöglicht, in den normalen DataAnnotations Mechanismus zu integrieren, ohne in this answer zu Frameworks zurückgreifen, eine ermöglicht zu schreiben:

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

Mit ein paar Helfer Klassen (siehe dort), verdrahten Sie es p z.B. in ASP.NET MVC wie so in der Global.asax: -

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