Dependency Injection

2020-02-14 08:43发布

问题:

We are builing a windows desktop application (not web based) and trying to come up with the best way to implement Repository and UnitOfWork Pattern.

In a typical Asp.Net Mvc application your repositories are injected with data context, services are injected with repositories and finally controllers are injected with services and all is well if you don't hit any exception, you will commit the changes.

In windows forms/wpf applications it is not advisable to use single datacontext ( Oren has a post on this on MSDN) so we have decided to create data context at the presenter. We are using Linq2SQl and we have two different databases to work with based on the view.

Currently I have the following implementation

public interface IRepository<T> where T : class
    {
        IEnumerable<T> Find(Expression<Func<T, bool>> where);
        T Single(Expression<Func<T, bool>> where);
        ...
     }

public class Repository<T> : IRepository<T> where T : class
    {
        private readonly Table<T> _table;

        public Repository(DataContext dataContext)
        {
            _table = dataContext.GetTable<T>();
        }
   }

public Class TodoService :ITodoService
{
       IRepository<Todo> _todoRepository;
       public TodoService(DataContext dataContext)
       {
           _todoRepository = new Repository<Todo>(dataContext)
       }
       ...
}

}

// Presenter for the UI

public class TodoPresenter
{
    public void Save()
    {
       Using (DataContext dataContext = DataContextFactory.GetNewContextForDatabase1())
       {
           ITodoService service = new TodoService(dataContext);
            ...
           service.Save(..);
           dataContext.SubmitChanges();           
       }
    }

}

I would like decouple the presenter from service and would like to inject TodoService when ITodoService is requested, but I cannot inject data context for two reasons, as I have to decide based on the database or can't even maintain a data context at the application level even if we have only one database being a windows application (many views are open at a time as tabs in the app) and with no data context I cannot create Repository classes and can't inject services.

Any idea on how to achieve decoupling in this situation

回答1:

 > I cannot inject data context

but maybe you can inject a factory method that creates the context and the service

public class TodoPresenter
{
    private Func<DataContext> dataContextFactory;
    private Func<DataContext, ITodoService> serviceFactory;

    // created with new TodoPresenter(DataContextFactory.GetNewContextForDatabase1(), 
    //                   dc => new TodoService(dc, 
    //                              new ToDoRepository(dc => new ToDoRepository(dc))));
    public TodoPresenter(Func<DataContext> dataContextFactory, 
                         Func<DataContext, ITodoService> serviceFactory)
    {
        this.dataContextFactory = dataContextFactory;
        this.serviceFactory = serviceFactory;
    }

    public void Save()
    {
        using (DataContext dataContext = this.dataContextFactory())
        {
            ITodoService service = serviceFactory(dataContext);
            // ...
            //service.Save(..);
            //dataContext.SubmitChanges();           
        }

    }
}

Update

The Service needs a factory to get the repository as well

public TodoService(DataContext dataContext, 
         Func<DataContext, IRepository<Todo> todoRepository){...}

An integrationtest with Service and presenter looks like this

  var toDoRepository = new Mock<..>(..);
  var datacontext= new Mock<..>(..);
  var presenter = new TodoPresenter(dc => datacontext, 
                  dc => new TodoService(dc, dc2 => toDoRepository ));

An unitest for presenter starts like this

  var TodoService= new Mock<..>(..);
  var datacontext= new Mock<..>(..);
  var presenter = new TodoPresenter(dc => datacontext, 
                  dc => TodoService);


回答2:

I would wrap the datacontext inside a UnitOfWork. And create a unitofwork either per call/session.



回答3:

have you tried to create the datacontext in the todoservice?

public interface IRepository<T> where T : class
{
    IEnumerable<T> Find(Expression<Func<T, bool>> where);
    T Single(Expression<Func<T, bool>> where);
    void Save(T item);
    void SubmitChanges();
}

public class Repository<T> : IRepository<T> where T : class
{
    public void Save(T item)
    {
    }

    public void SubmitChanges()
    {
    }
}

// TodoService will have its local datacontext

public class TodoService :IDisposable
{
   DataContext dataContext;
   IRepository<Todo> _todoRepository;
   public TodoService()
   {
       dataContext = DataContextFactory.GetNewContextForDatabase1();
       _todoRepository = new Repository<Todo>(dataContext);
   }

   public void Dispose()
   {
       dataContext.Dispose();
   }

   void Save(Todo item)
   {
       _todoRepository.Save(item);
   }

   void SubmitChanges()
   {
       _todoRepository.SubmitChanges();
   }
}

// presenter

class TodoPresenter
{
    public void Save()
    {
       using (TodoService service = new TodoService())
       {
           Todo item = null;
           // ...
           service.Save(item);
           service.SubmitChanges();           
       }
    }
}