c# - Using Simple Injector with Unit Of Work & Repository Pattern in Windows Form -


i'm trying implement ioc in windows form application. choice fell on simple injector, because it's fast , lightweight. implement unit of work , repository pattern in apps. here structure:

dbcontext:

public class membercontext : dbcontext {     public membercontext()         : base("name=membercontext")     { }      public dbset<member> members { get; set; }      protected override void onmodelcreating(dbmodelbuilder modelbuilder)     {         base.onmodelcreating(modelbuilder);          modelbuilder.conventions.remove<pluralizingtablenameconvention>();\     } } 

model:

public class member {     public int memberid { get; set; }     public string name { get; set; } } 

genericrepository:

public abstract class genericrepository<tentity> : igenericrepository<tentity>      tentity : class {     internal dbcontext context;     internal dbset<tentity> dbset;      public genericrepository(dbcontext context)     {         this.context = context;         this.dbset = context.set<tentity>();     }      public virtual void insert(tentity entity)     {         dbset.add(entity);     } } 

memberrepository:

public class memberrepository : genericrepository<member>, imemberrepository {     public memberrepository(dbcontext context)         : base(context)     { } } 

unitofwork:

public class unitofwork : iunitofwork {     public dbcontext context;      public unitofwork(dbcontext context)     {         this.context = context;     }      public void savechanges()     {         context.savechanges();     }      private bool disposed = false;      protected virtual void dispose(bool disposing)     {         if (!this.disposed)         {             if (disposing)             {                 context.dispose();             }         }          this.disposed = true;     }      public void dispose()     {         dispose(true);         gc.suppressfinalize(this);     } } 

memberservice:

public class memberservice : imemberservice {     private readonly iunitofwork unitofwork;     private readonly imemberrepository memberrepository;      public memberservice(iunitofwork unitofwork, imemberrepository memberrepository)     {         this.unitofwork = unitofwork;         this.memberrepository = memberrepository;     }      public void save(member member)     {         save(new list<member> { member });     }      public void save(list<member> members)     {         members.foreach(m =>             {                 if (m.memberid == default(int))                 {                     memberrepository.insert(m);                 }             });         unitofwork.savechanges();     } } 

in member form add textbox input member name , button save database. code in member form:

frmmember:

public partial class frmmember : form {     private readonly imemberservice memberservice;      public frmmember(imemberservice memberservice)     {         initializecomponent();          this.memberservice = memberservice;     }      private void btnsave_click(object sender, eventargs e)     {         member member = new member();         member.name = txtname.text;         memberservice.save(member);     } } 

i implement simpleinjector (refer http://simpleinjector.readthedocs.org/en/latest/windowsformsintegration.html) in program.cs seen in code below:

static class program {     private static container container;      [stathread]     static void main()     {         application.enablevisualstyles();         application.setcompatibletextrenderingdefault(false);         bootstrap();         application.run(new frmmember((memberservice)container.getinstance(typeof(imemberservice))));     }      private static void bootstrap()     {         container = new container();          container.registersingle<imemberrepository, memberrepository>();         container.register<imemberservice, memberservice>();         container.register<dbcontext, membercontext>();         container.register<iunitofwork, unitofwork>();          container.verify();     } } 

when run program , add member, doesn't save database. if changed container.register container.registersingle, save database. documentation, registersingle make class singleton. can't using registerlifetimescope because generate error

"the registered delegate type imemberservice threw exception. iunitofwork registered 'lifetime scope' lifestyle, instance requested outside context of lifetime scope"

1) how use simpleinjector in windows form unitofwork & repository pattern?
2) implement patterns correctly?

the problem have difference in lifestyles between service, repository, unitofwork , dbcontext.

because memberrepository has singleton lifetime simple injector create 1 instance reused lifetime of application, days, weeks or months winforms application. direct consequence registering memberrepository singleton dependencies of class become singleton well, no matter lifestyle used in registration. these dependencies called ‘captive dependencies’.

as side note: diagnostic services of simple injector able spot configuration mistake , show/throw potential lifestyle mismatch warning.

so memberrepository singleton , has 1 , same dbcontext throughout application lifetime. unitofwork, has dependency on dbcontext receive different instance of dbcontext, because registration dbcontext transient. context will, in example, never save newly created member because dbcontext not have newly created member, member created in different dbcontext.

when change registration of dbcontext registersingle start working, because every service, class or whatever depending on dbcontext same instance.

but not solution because having 1 dbcontext lifetime of application trouble, know. explained in great detail in post.

the solution need using scoped instance of dbcontext, tried. missing information on how use lifetime scope feature of simple injector (and of other containers out there). when using scoped lifetime there must active lifetime scope exception message states. starting lifetime scope pretty simple:

using (container.beginlifetimescope())  {     // instances resolved within scope     // lifetimescope lifestyle     // same instance } 

you can read in detail here.

changing registrations to:

container.registerlifetimescope<imemberrepository, memberrepository>(); container.registerlifetimescope<imemberservice, memberservice>(); container.registerlifetimescope<dbcontext, membercontext>(); container.registerlifetimescope<iunitofwork, unitofwork>(); 

and changing code btnsaveclick() to:

private void btnsave_click(object sender, eventargs e) {     member member = new member();     member.name = txtname.text;      using (container.beginlifetimescope())      {         var memberservice = container.getinstance<imemberservice>();         memberservice.save(member);     } } 

is need.

but have introduced new problem. using service locator anti pattern scoped instance of imemberservice implementation. therefore need infrastructural object handle cross cutting concern in application. decorator perfect way implement this. see here. like:

public class lifetimescopedmemberservicedecorator : imemberservice {     private readonly func<imemberservice> decorateefactory;     private readonly container container;      public lifetimescopedmemberservicedecorator(func<imemberservice> decorateefactory,         container container)     {         this.decorateefactory = decorateefactory;         this.container = container;     }      public void save(list<member> members)     {         using (this.container.beginlifetimescope())         {             imemberservice service = this.decorateefactory.invoke();              service.save(members);         }     } } 

you register (singleton) decorator in simple injector container this:

container.registerdecorator(typeof(imemberservice),                                    typeof(lifetimescopedmemberservicedecorator),     lifestyle.singleton); 

the container provide class depends on imemberservice lifetimescopedmemberservice. in container inject func<imemberservice> which, when invoked, return instance container using configured lifestyle.

adding decorator (and registration) , changing lifestyles fix issue example.

i expect application in end have imemberservice, iuserservice, icustomerservice, etc... need decorator each , every ixxxservice, not dry if ask me. if services implement save(list<t> items) consider creating open generic interface:

public interface iservice<t> {     void save(list<t> items);  }  public class memberservice : iservice<member> {      // same code before } 

you register implementations in 1 line using batch registration:

container.registermanyforopengeneric(typeof(iservice<>),     new lifetimescopelifestyle(),     assembly.getexecutingassembly()); 

and can wrap these instances single open generic implementation of above mentioned lifetimescopeservicedecorator.

it imo better use command / handler pattern (you should read link!) type of work. in short: in pattern every use case translated message object (a command) handled single command handler, can decorated e.g. savechangescommandhandlerdecorator , lifetimescopecommandhandlerdecorator , loggingdecorator , on.

your example like:

public interface icommandhandler<tcommand> {     void handle(tcommand command); }  public class createmembercommand {     public string membername { get; set; } }  public class createmembercommandhandler : icommandhandler<createmembercommand> {     //notice need memberrepository 0 imo     private readonly igenericrepository<member> memberrepository;      public createmembercommandhandler(igenericrepository<member> memberrepository)     {         this.memberrepository = memberrepository;     }      public void handle(createmembercommand command)     {         var member = new member {name = command.membername};         this.memberrepository.insert(member);     } }  public class savechangescommandhandlerdecorator<tcommand>     : icommandhandler<tcommand> {     private icommandhandler<tcommand> decoratee;     private dbcontext db;      public savechangescommandhandlerdecorator(         icommandhandler<tcommand> decoratee, dbcontext db)     {         this.decoratee = decoratee;         this.db = db;     }      public void handle(tcommand command)     {         this.decoratee.handle(command);         this.db.savechanges();     } }  public partial class frmmember : form {     private readonly icommandhandler<createmembercommand> commandhandler;      public frmmember(icommandhandler<createmembercommand> commandhandler)     {         initializecomponent();         this.commandhandler = commandhandler;     }      private void btnsave_click(object sender, eventargs e)     {         this.commandhandler.handle(             new createmembercommand { membername = txtname.text });     } } 

register as:

// simple injector v3.x container.register(typeof(igenericrepository<>),      typeof(genericrepository<>)); container.register(typeof(icommandhandler<>),      new[] { assembly.getexecutingassembly() }); container.registerdecorator(typeof(icommandhandler<>),      typeof(savechangescommandhandlerdecorator<>)); container.registerdecorator(typeof(icommandhandler<>),     typeof(lifetimescopedcommandhandlerdecorator<>),     lifestyle.singleton);  // simple injector v2.x container.registeropengeneric(typeof(igenericrepository<>),      typeof(genericrepository<>)); container.registermanyforopengeneric(typeof(icommandhandler<>),      assembly.getexecutingassembly()); container.registerdecorator(typeof(icommandhandler<>),      typeof(savechangescommandhandlerdecorator<>)); container.registerdecorator(typeof(icommandhandler<>),     typeof(lifetimescopedcommandhandlerdecorator<>),     lifestyle.singleton); 

this design remove need unitofwork , (specific) service completely.


Comments

Popular posts from this blog

c++ - No viable overloaded operator for references a map -

java - Custom OutputStreamAppender not run: LOGBACK: No context given for <MYAPPENDER> -

java - Cannot secure connection using TLS -