0

I have a user-added "Identification" property which needs not to be duplicated. (different from the DB Id).

I have added the following method to my ViewModel:

 public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            GearContext db = new GearContext();
            if (db.Items.Where(p => p.Identification == this.Identification).Count() > 0)
            {
                yield return new ValidationResult("Identification already in use, please chose a different one. ", new[] { "Identification" });
            }
        }

The problem is that this prevents me to edit my model. I would like this validation to happen only when a new entry is created, not on edit.

I have tried the following type of edit in my controller:

if (ModelState.IsValid)
            {
                var item = db.Items.Find(viewModel.ItemId);
                Mapper.Map<ItemViewModel, Item>(viewModel, item);
                if (TryUpdateModel(item, null, null, new string[] { "Identification" }))
                {
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }


            }

also tried without the "TryUpdateModel" at all (simple viewmodel => model and db.save).

I thought about implementing the validation method in my DB context instead and run it only on item.State == EntityState.Added but I believe I don't have access the the edited model properties there.

Orphu.of.io
  • 125
  • 1
  • 16
  • Would it not be easier to have Identification as part of a unique key on the relevant table, and leave all this to the DBMS? Even if you implement this in MVC, a console application or Windows service could still break this logic. Having it in the db means nothing can. – dyson Apr 30 '14 at 07:34
  • Is it possible to define a unique key with code first? Also, wouldn't I have the same issue? I feel like it's the way I am updating my model which could be wrong (it shouldn't touch Identification/Id at all). – Orphu.of.io Apr 30 '14 at 07:37
  • It certainly wasn't possible previously; you may wish to check EF6 to see whether that has been changed. However, to stay within MVC, do you have separate Create and Edit views? Presumably so. If so, have separate view models for each of these and use the controller to create instances of these when the relevant GET request comes in. The Create model can then contain this validation whereas the Edit model can have this as an immutable property and so not need the check. – dyson Apr 30 '14 at 07:54
  • Seems like it's now possible to define unique keys as such [Index("TitleIndex", unique: true)] in EF 6.1. Unfortunately this project uses 6.0. Nevertheless I think that creating seperate viewmodels for Create and Edit would probably be the way to go for me. Thanks for the suggestion. – Orphu.of.io Apr 30 '14 at 08:07

2 Answers2

1

To prevent the item being edited from triggering a validation error add a check on the ID to the query you use. i.e. change this line:

if (db.Items.Where(p => p.Identification == this.Identification).Count() > 0)

to this:

if (db.Items.Where(p => p.ID != this.ID && p.Identification == this.Identification).Count() > 0)

As an aside, I'd also change .Count() > 0 to .Any() so that the query will look for no more than 1 record

Also, I really think you should look into implementing this in the DbContext to avoid contaminating the Model class with the DbContext. The way to do it is described here: https://stackoverflow.com/a/18736484/150342

Community
  • 1
  • 1
Colin
  • 22,328
  • 17
  • 103
  • 197
  • This works perfectly. Also using .Any() instead of Count() now. I will look into the link you posted as well. Thanks! – Orphu.of.io Apr 30 '14 at 11:37
0

What you can do is change your IValidatableObject method to make it take a collection. Something like this

public ICollection<IValidatableObject> Validations { get; set; }

public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
    var valid = Enumerable.Empty<ValidationResult>();

    if (Validations != null)
    {
        valid = Validations.Aggregate(valid, (current, validate) => current.Concat(validate.Validate(validationContext)));
    }
    return valid;
}

You can now attach validations to the Validations collection which the IValidatableObject interface will call. When adding you can add your duplicate check. When your editing leave it off or add a different validation check.

With that said I do think duplicate key fields should be the responsibility of the database and not validation method. Also even with this check there is still a possibility you can have duplicates. The IValidatableObject check is performed before the data is sent up to SQL. So you can have two entities with the same key hitting this check at the same time and both will pass and both will get added to SQL. If you really want to make sure you will need to override the dbcontexts savechanges method

public override int SaveChanges()
{
    var results = base.SaveChanges();

    // Run validation again for adds
    PostValidation();

    return results;
}

As long as you are in a transaction it hasn't been committed yet and you can check SQL for duplicates keys now and if so throw an error and rollback the transaction.

CharlesNRice
  • 3,219
  • 1
  • 16
  • 25
  • Thanks for the answer. Some valuable information there that I will most likely use in the future. I accepted Colin answer because it's very straightforward. – Orphu.of.io Apr 30 '14 at 11:39