ASP.NET MVC 4, EF5, Code First, SQL Server 2012 Express
Quelle est la meilleure pratique pour appliquer une valeur unique dans un modèle? J'ai une classe d'endroits qui a une propriété "url" qui devrait être unique pour chaque endroit.
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; }
};
Pourquoi n'y a-t-il pas simplement une annotation de données [Unique] que vous pouvez y placer?
J'ai vu 1 ou 2 discussions à ce sujet, mais aucune discussion sur les meilleures pratiques. À l'aide de Code First, pouvez-vous en quelque sorte dire à la base de données de définir une contrainte unique sur le champ dans la base de données?
Quelle est la manière la plus simple - et quelle est la meilleure pratique?
Aussi fou que cela puisse paraître, la meilleure pratique de nos jours est de ne pas utiliser la validation intégrée et utiliser à la place FluentValidation . Ensuite, le code sera très facile à lire et super-maintenable puisque la validation sera gérée sur une classe séparée, ce qui signifie moins de code spaghetti.
Pseudo-exemple de ce que vous essayez d'accomplir.
[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
}
}
Ce lien pourrait aider: 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; }
}
La seule façon est de mettre à jour votre migration une fois que vous l'avez générée, en supposant que vous les utilisez, afin qu'elle applique une contrainte unique sur la colonne.
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");
}
Le plus dur, cependant, est d'appliquer la contrainte au niveau du code avant d'accéder à la base de données. Pour cela, vous pourriez avoir besoin d'un référentiel qui contient la liste complète des valeurs uniques et s'assure que les nouvelles entités ne violent pas cela via une méthode d'usine.
// 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 };
}
}
Références:
Note de bas de page:
Il y a d'abord beaucoup de demandes pour [Unique] dans le code, mais il semble qu'il ne crée même pas la version 6: http://entityframework.codeplex.com/wikipage?title=Roadmap
Vous pouvez essayer de voter pour cela ici: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-ie-candidate-key-support
Vous pouvez effectuer cette vérification dans le niveau de code avant d'enregistrer les données dans les tables de base de données.
Vous pouvez essayer d'utiliser l'annotation de données Remote
sur votre modèle de vue pour effectuer une validation asynchrone afin de rendre l'interface utilisateur plus réactive.
public class CreatePlaceVM
{
[Required]
public string PlaceName { set;get;}
[Required]
[Remote("IsExist", "Place", ErrorMessage = "URL exist!")
public virtual string URL { get; set; }
}
Assurez-vous que vous disposez d'une méthode d'action IsExists
dans votre Placecontroller
qui accepte un paramètre URL
et vérifiez-la de nouveau dans votre table et retournez true ou false.
Ce lien msdn a un exemple de programme pour montrer comment implémenter l'attribut distant pour effectuer une validation instantanée.
De plus, si vous utilisez une procédure stockée (( pour une raison quelconque), vous pouvez faire un EXISTS
vérifiez-le avant la requête INSERT
.
J'ai résolu le problème général de l'activation de l'injection de constructeur dans votre flux de validation, en l'intégrant dans le mécanisme DataAnnotations normal sans recourir aux frameworks dans cette réponse , permettant d'écrire:
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);
}
}
Avec certaines classes d'assistance (regardez là-bas), vous le câblez par exemple dans ASP.NET MVC comme dans le Global.asax
: -
DataAnnotationsModelValidatorProvider.RegisterAdapterFactory(
typeof(ValidatorServiceAttribute),
(metadata, context, attribute) =>
new DataAnnotationsModelValidatorEx(metadata, context, attribute, true));