.net - Where to run a duplicate check for an entity -


i'm looking advice on "best" place put validation logic, such duplicate check entity, when using entity framework code-first, in mvc application.

to use simple example:

public class jobrole {   public int id { get; set; }           public string name { get; set; } } 

the rule "name" field must unique.

when add new jobrole, easy run check in job role repository name doesn't exist.

but if user edits existing jobrole, , accidentally sets name 1 exists, how can check this?

the issue there doesn't need "update" method on repository, job role entity automatically detect changes, there isn't logical place check before attempting save.

i have considered 2 options far:

  1. override validateentry method on dbcontext, , when jobrole entity being saved entitystate.modified, run duplicate check then.
  2. create sort of duplicate-checking service, called controller, before attempting save.

neither seems ideal. using validateentry seems rather late (just before save) , hard test. using service leaves possibility forgets call controller, letting duplicate data through.

is there better way?

your problem validateentity appears validation occurs on savechanges , late you. in entity framework 5.0 can call validation earlier if wish using dbcontext.getvalidationerrors. , of course call dbcontext.validateentity directly. how it:

  1. override validateentity method on dbcontext:

    protected override dbentityvalidationresult                     validateentity(dbentityentry entityentry,                    idictionary<object, object> items) {     //base validation data annotations, ivalidatableobject     var result = base.validateentity(entityentry, items);      //you can choose bail out before custom validation     //if (result.isvalid)     //    return result;      customvalidate(result);     return result; }  private void customvalidate(dbentityvalidationresult result) {     validateorganisation(result);     validateuserprofile(result); }  private void validateorganisation(dbentityvalidationresult result) {     var organisation = result.entry.entity organisation;     if (organisation == null)         return;      if (organisations.any(o => o.name == organisation.name                                 && o.id != organisation.id))         result.validationerrors               .add(new dbvalidationerror("name", "name exists")); }  private void validateuserprofile(dbentityvalidationresult result) {     var userprofile = result.entry.entity userprofile;     if (userprofile == null)         return;      if (userprofiles.any(a => a.username == userprofile.username                                && a.id != userprofile.id))         result.validationerrors.add(new dbvalidationerror("username",                                "username exists")); } 
  2. embed context.savechanges in try catch , create method access context.getvalidationerrors(). in unitofwork class:

    public dictionary<string, string> getvalidationerrors() {     return _context.getvalidationerrors()                    .selectmany(x => x.validationerrors)                    .todictionary(x => x.propertyname, x => x.errormessage); }  public int save() {     try     {         return _context.savechanges();     }     catch (dbentityvalidationexception e)     {         //http://blogs.infosupport.com/improving-dbentityvalidationexception/         var errors = e.entityvalidationerrors           .selectmany(x => x.validationerrors)           .select(x => x.errormessage);          string message = string.join("; ", errors);          throw new dataexception(message);     } } 
  3. in controller, call getvalidationerrors() after adding entity context before savechanges():

    [httppost] public actionresult create(organisation organisation, string returnurl = null) {     _uow.organisationrepository.insertorupdate(organisation);      foreach (var error in _uow.getvalidationerrors())         modelstate.addmodelerror(error.key, error.value);      if (!modelstate.isvalid)         return view();      _uow.save();      if (string.isnullorempty(returnurl))         return redirecttoaction("index");      return redirect(returnurl); } 

my base repository class implements insertorupdate this:

    protected virtual void insertorupdate(t e, int id)     {         if (id == default(int))         {             // new entity             context.set<t>().add(e);         }         else         {             // existing entity             context.entry(e).state = entitystate.modified;         }           } 

i still recommend adding unique constraint database because absolutely guarantee data integrity , provide index can improve efficiency, overriding validateentry gives loads of control on how , when validation occurs.


Comments

Popular posts from this blog

jasper reports - Fixed header in Excel using JasperReports -

media player - Android: mediaplayer went away with unhandled events -

python - ('The SQL contains 0 parameter markers, but 50 parameters were supplied', 'HY000') or TypeError: 'tuple' object is not callable -