.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:
- override validateentry method on dbcontext, , when jobrole entity being saved entitystate.modified, run duplicate check then.
- 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:
override
validateentity
method ondbcontext
: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")); }
embed
context.savechanges
in try catch , create method accesscontext.getvalidationerrors(
). inunitofwork
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); } }
in controller, call
getvalidationerrors()
after adding entity context beforesavechanges()
:[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
Post a Comment