1

In my wpf app I need to be able to read/write data from specific table/tables, so, as I'm using SimpleInjector to use all services and stuff.

So, basically I want to be able to use repository in some of my services.

First I found this How to use Simple injector, Repository and Context - code first

And decided to do something like that:

Entry point:

public partial class App : Application
{
    public static Container appContainer;
    private void AppStartup(object sender, StartupEventArgs e)
    {


    }

    private void ConfigureContainer()
    {
        appContainer = new Container();
        appContainer.Options.DefaultScopedLifestyle = new ThreadScopedLifestyle();

        appContainer.RegisterSingleton<INLogger, LoggerNlog>();

        //some of services registration

        appContainer.Register<DbContext>(() =>
        {
            string connectionString = System.Configuration.ConfigurationSettings.AppSettings["ConnectionString"].ToString();
            return new ExcelSlicerContext(connectionString);
        }, Lifestyle.Scoped);

        appContainer.Register(typeof(IRepository<>), typeof(BaseRepository<>), Lifestyle.Scoped);

        appContainer.Register<IMainAppSerivce, SlicerMainService>();
        appContainer.Register<MainViewModel>();

        appContainer.Verify();
    }

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        ConfigureContainer();

        using (ThreadScopedLifestyle.BeginScope(appContainer))
        {
            var viewModel = appContainer.GetInstance<MainViewModel>();

            var mainWindow = new MainWindow();
            mainWindow.DataContext = viewModel;
            mainWindow.Show();
        }
    }

}

Example of service:

public class SlicerMainService : IMainAppSerivce
{

    private INLogger _nLogger;
    //  ...
    private IRepository<FactModel> _dataFactRepository;

    public SlicerMainService(INLogger nLogger, IRepository<FactModel> dataFactRepository)
    {
        _nLogger = nLogger;
        //...
        _dataFactRepository = dataFactRepository;
    }

    //...
    public void TestWorkWithDb(int[] sheetIndexList)
    {
        var t = _dataFactRepository.Get(x => x.Row > 6).ToList();
    }

}

So, in this example, when it tries to get some data it simple says that DbContext is disposed.

I know it is because of in need to use scope like using (ThreadScopedLifestyle.BeginScope(myContainer)) but is it good practice to pass container to some services? I doubt it.

Whats the way of properly inject IRepository<T> here?

Code for repositories & dbcontext:

 public class ExcelSlicerContext: DbContext
    {
        public DbSet<FactModel> FactData { get; set; }

        public ExcelSlicerContext(string connectionString) : base(connectionString)
        {
            Database.SetInitializer(
                new CreateDatabaseIfNotExists<ExcelSlicerContext>()
            );
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<FactModel>().ToTable("fact");
        }
    }

Repository:

 public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class
{
    DbContext _context;
    DbSet<TEntity> _dbSet;

    public BaseRepository(DbContext context)
    {
        _context = context;
        _dbSet = context.Set<TEntity>();
    }

    public IEnumerable<TEntity> Get()
    {
        return _dbSet.AsNoTracking().ToList();
    }

    public IEnumerable<TEntity> Get(Func<TEntity, bool> predicate)
    {
        return _dbSet.AsNoTracking().Where(predicate).ToList();
    }

    public TEntity GetFirst(Func<TEntity, bool> predicate)
    {
        return _dbSet.AsNoTracking().FirstOrDefault(predicate);
    }

    public TEntity FindById(int id)
    {
        return _dbSet.Find(id);
    }

    public void Create(TEntity item)
    {
        _dbSet.Add(item);
        _context.SaveChanges();
    }

    public void CreateFromRange(IEnumerable<TEntity> itemList)
    {
        _dbSet.AddRange(itemList);
        _context.SaveChanges();
    }
    public void Update(TEntity item)
    {
        _context.Entry(item).State = EntityState.Modified;
        _context.SaveChanges();
    }
    public void Remove(TEntity item)
    {
        _dbSet.Remove(item);
        _context.SaveChanges();
    }

    public void RemoveWhere(Func<TEntity, bool> predicate)
    {
        _dbSet.RemoveRange(_dbSet.Where(predicate).ToList());
        _context.SaveChanges();
    }

    public void Add(TEntity item)
    {
        _dbSet.Add(item);
    }

    public void AddRange(IEnumerable<TEntity> itemList)
    {
        _dbSet.AddRange(itemList);
    }

    public void Save(bool recreateContext)
    {
        if (recreateContext)
        {
            RecreateContext();
        }
        _context.SaveChanges();
    }

    private void RecreateContext()
    {
        var constring = _context.Database.Connection.ConnectionString;
        _context.Dispose();
        _context = new ExcelSlicerContext(constring);
        _context.Configuration.AutoDetectChangesEnabled = false;
    }
}

Interface for repository:

 public interface IRepository<TEntity> where TEntity : class
    {
        void Create(TEntity item);
        void CreateFromRange(IEnumerable<TEntity> itemList);
        void Add(TEntity item);
        void AddRange(IEnumerable<TEntity> itemList);
        void Save(bool recreateContext);
        TEntity FindById(int id);
        IEnumerable<TEntity> Get();
        IEnumerable<TEntity> Get(Func<TEntity, bool> predicate);
        TEntity GetFirst(Func<TEntity, bool> predicate);
        void Remove(TEntity item);
        void RemoveWhere(Func<TEntity, bool> predicate);
        void Update(TEntity item);

    }

UPD

After I read the answer, which linked as duplicate I implemented things as it was implemeneted in the example, e.g.:

 public class ThreadScopedCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
    {
        private readonly Func<ICommandHandler<TCommand>> decorateeFactory;
        private readonly Container container;

        public ThreadScopedCommandHandlerDecorator(Func<ICommandHandler<TCommand>> decorateeFactory, Container container)
        {
            this.decorateeFactory = decorateeFactory;
            this.container = container;
        }

        public void Handle(TCommand command)
        {
            using (ThreadScopedLifestyle.BeginScope(container))
            {
                var handler = decorateeFactory.Invoke();
                handler.Handle(command);

            }
        }
    }

Saving changes handler:

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();
        }
    }

as decorators and, as example, such a command:

 public class GetFactModelCommandHandler : ICommandHandler<GetFactModelCommand>
    {
        private readonly IRepository<FactModel> _factRepository;

        public GetFactModelCommandHandler(IRepository<FactModel> factRepository)
        {
            _factRepository = factRepository;
        }

        public void Handle(GetFactModelCommand command)
        {
            var data = _factRepository.Get(x => x.DocGuid == command.DocGuid);
        }
    }

But then I realized, that on every new action for every different entity I need to implement commands and command handlers!

It dozens and dozens of classes!

And also, how I could get results from command (I know I can make something like ICommandHandler<Command, TResult> and have filed there for store the result but... its not like Command Pattern should be implemented AFAIK

Is there any other way? Just implement some IService for particular entity?

DanilGholtsman
  • 2,354
  • 4
  • 38
  • 69
  • 1
    Although the question is about winforms, the answer would be the same: https://stackoverflow.com/a/29808487/3294832 – Ric .Net Jan 10 '18 at 08:26
  • 1
    And apart from the question, I would suggest you rethink the `IRepository` interface. Most members can be easily extracted to extension methods, which makes the interface much smaller, thereby adhering to [ISP](https://en.wikipedia.org/wiki/Interface_segregation_principle) – Ric .Net Jan 10 '18 at 08:29
  • @Ric.Net hmm, is it only me or the solution for this question looks like overcomplicated? I mean, commands, decorator - is there way to make it more simple? Like just pass ` IRepository _dataFactRepository` and call `Save`? – DanilGholtsman Jan 10 '18 at 09:34
  • @Ric.Net yep, thank you, I will) – DanilGholtsman Jan 10 '18 at 09:35
  • > overcomplicated Well, when your application starts to grow you quickly find a lot of code duplication. With commands, every class has it's own responsibility and things like creating a scope, saving, transaction handling etc. will be there already (in a generic decorator). Things become quickly oversimplified when using commands. If you application won't grow and has a single use case, commands indeed could be overcomplicated, but it depends purely on that fact. – Ric .Net Jan 10 '18 at 09:52
  • @Ric.Net yeah, I guess you right. Actually application is very simple and for temp purposes but I am not sure really (thats why instead of pure sql I decided to use ORM-stuff) – DanilGholtsman Jan 10 '18 at 09:55
  • @Ric.Net I'm sorry, but I still don't get how in final example is participating `ThreadScopedCommandHandlerDecorator` ? – DanilGholtsman Jan 10 '18 at 12:08
  • I would advise you to read the provided link and if this is not sufficient, ask a detailed question on this subject – Ric .Net Jan 10 '18 at 13:03
  • @Ric.Net oh, well, I read but, I mean, in final code parts it's looks like (except for registrating things) `ThreadScopedCommandHandlerDecorator ` not involved or maybe I just misunderstanfing things. Will try to implement it as I see it anyway – DanilGholtsman Jan 10 '18 at 13:25
  • @Ric.Net ahh I get it, I just wasnt know how it works with SimpleInjector – DanilGholtsman Jan 10 '18 at 13:50
  • @Ric.Net oh thank you, sir, it working now! I'm sorry for all of the misunderstaing – DanilGholtsman Jan 10 '18 at 14:00
  • @Ric.Net but now I got other connected questions. Please, can you review it? – DanilGholtsman Jan 10 '18 at 16:24
  • Sure, post a link to your related question. – Ric .Net Jan 10 '18 at 23:33
  • @Ric.Net it's in current question, but after UPD mark) – DanilGholtsman Jan 11 '18 at 04:14
  • You really should move that to a new question. – Ric .Net Jan 14 '18 at 19:13

0 Answers0