ASP.NET MVC 4, EF5, Unique property in model - best practice?

前端 未结 7 517
隐瞒了意图╮
隐瞒了意图╮ 2020-12-13 07:24

ASP.NET MVC 4, EF5, Code First, SQL Server 2012 Express

What is best practice to enforce a unique value in a model? I have a places class that has a

相关标签:
7条回答
  • 2020-12-13 07:43

    As crazy as it might sound the best practice nowadays is to not use built-in validation and instead use FluentValidation. Then the code will be very easy to read and super-maintainable since validation will be managed on separate class meaning less spaghetti code.

    Pseudo-example of what you are trying to achieve.

    [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 讨论(0)
  • 2020-12-13 07:48

    well it's simple but idk if this is efficient or not. Just check before adding a new user whether the email already exists or not.

    if (!db.Users.Any(x => x.Email == data.Email))
     // your code for adding
    else
     // send a viewbag to the view
     //  ViewBag.error = "Email Already Exist";
    
    0 讨论(0)
  • 2020-12-13 07:49

    Faced similar issue in my ASP.NET Razor Page Project. Creating custom UniqueDataAttribute didn't work, because on Edit, it would throw an error if you're not changing unique field.

    I needed unique Book Name. This is how I resolved:

    1. I added unique constraint to the field in database via EF Core migrations. Added following in ApplicationDbContext class and then ran migration.

    Code:

    protected override void OnModelCreating(ModelBuilder builder)
            {
                builder.Entity<Book>()
                    .HasIndex(u => u.Name)
                    .IsUnique();
            }
    
    1. Next, created helper/extension method as follows.

    Code:

            // Validate uniqueness of Name field in database.
            // If validation is done on existing record, pass the id of the record.
            // Else, if validating new record Name, then id is set to dummy key integer -1
            public static bool UniqueNameInDb(this string data, ApplicationDbContext db, int id = -1)
            {
                var duplicateData = from o in db.Book
                                    where o.Name == data && o.Id != id
                                    select o;
                if(duplicateData.Any())
                {
                    return false;
                }
                return true;
            }
        }
    
    1. Then used it in Create and Edit page model in OnPost() method as follows.

    Create model:

    public async Task<IActionResult> OnPost()
            {
                if(ModelState.IsValid)
                {
                    if (!Book.Name.UniqueNameInDb(_db)) //<--Uniqueness validation
                    {
                        ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
                        return Page();
                    }
    
                    await _db.Book.AddAsync(Book);
                    await _db.SaveChangesAsync();
    
                    return RedirectToPage("Index");
    
                }
                else
                {
                    return Page();
                }
            }
    

    Edit Model:

    public async Task<IActionResult> OnPost()
            {
                if(ModelState.IsValid)
                {
                    var bookFromDb = await _db.Book.FindAsync(Book.Id);
                    if (!Book.Name.UniqueNameInDb(_db, Book.Id)) //<--Uniqueness validation
                    {
                        ModelState.AddModelError("Book.Name", "Name already exist"); //<-- Add error to the ModelState, that would be displayed in view.
                        return Page();
                    }
                    bookFromDb.Name = Book.Name;
                    bookFromDb.Author = Book.Author;
    
                    await _db.SaveChangesAsync();
    
                    return RedirectToPage("Index");
                }
    
                return Page();
            }
    

    PS: Your Razor view should've Model validation set on in the form to capture and display the error.

    i.e,

    <div class="text-danger" asp-validation-summary="ModelOnly"></div>
    

    and below validation against the field.

    <span asp-validation-for="Book.Name" class="text-danger"></span>
    
    0 讨论(0)
  • 2020-12-13 07:50

    The only way is to update your migration once you generate it, assuming you are using them, so that it enforces a unique constraint on the column.

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

    The hard bit, though, is enforcing the constraint at the code level before you get to the database. For that you might need a repository that contains the complete list of unique values and makes sure that new entities don't violate that through a factory method.

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

    References:

    • Repository - Fowler (the original source of Repository)
    • Repostory - MSDN
    • Tutorial: Repository in MVC (www.asp.net)
    • Singleton in C# - SO

    Footnote:

    There are a lot of requests for [Unique] in code first, but it looks like it isn't even making version 6: http://entityframework.codeplex.com/wikipage?title=Roadmap

    You could try voting for it here: http://data.uservoice.com/forums/72025-entity-framework-feature-suggestions/suggestions/1050579-unique-constraint-i-e-candidate-key-support

    0 讨论(0)
  • 2020-12-13 07:54

    This link might help: 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; }
    }
    
    0 讨论(0)
  • 2020-12-13 07:55

    You may do this checking in the code level before saving the data to the Database tables.

    You can try using the Remote data annotation on your viewmodel to do an asynchronous validation to make the UI more responsive.

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

    Make sure you have an IsExists action method in your Placecontroller which accepts a URL paramtere and check it againist your table and return true or false.

    This msdn link has a sample program to show how to implement Remote attribute to do instant validation.

    Also, If you are using a Stored procedure (For some reason), you can do an EXISTS check there before the INSERT query.

    0 讨论(0)
提交回复
热议问题