1

I have question regarding passing by some information to my repositories classes. Currently I have two repositories, CsvRepository and SqlRepository. For both of them, I want to pass some information, currently there is only path to be passed for CsvRepository. As you can see I also have factory pattern class and GetRepository method to get specific object on demand. I do not think this is right place to pass that information from CsvRepository method because it's not the duty of factory method. Could you help me out where this should be passed correctly?

 public enum DataSource
    {
        SqlServer,
        Csv,
        Oracle
    }

    public interface IRepo
    {
    }

    public interface IRepository<T> : IRepo where T : class
    {
        IEnumerable<T> GetRecords();
    }

    public class RepositoryFactory
    {
         // here I would need to pass all information for all repositories - bad idea perhaps
         public static IRepo GetRepository(DataSource repositoryType)
         {
             IRepo repo;

             switch (repositoryType)
             {
                  case DataSource.Csv:
                     repo = new CsvRepository("path"); //perhaps path shouldn't be right passed right here - so where?
                     break;

                  case DataSource.SqlServer:
                     repo = new SqlRepository();
                     break;

                  default:
                     throw new ArgumentException("Invalid Repository Type");
             }

             return repo;
        }
    }

    public class CsvRepository : IRepository<InputData>
    {
          private string _path;

          public CsvRepository(string path)
          {
               _path = path;
          }

          public IEnumerable<IBasic> GetRecords()
          {
              return from line in File.ReadLines(_path)
                  select line.Split(',') into parts
                  where parts.Length == 3
                  select new InputData { Name = parts[0], X = Convert.ToInt32(parts[1]), Y = Convert.ToInt32(parts[2]) };
          }
    }

    // other repository
    public class OracleRepository : IRepository<InputData>
    {
        // to be implemented
    }

    class Form1
    {  
         private void FetchData(DataSource repositoryType)
         {
              ClearListBox();

              var repository = RepositoryFactory.GetRepository(repositoryType);
              var people = repository.GetRecords();
         }

         private void CSVFetchButton_Click(object sender, RoutedEventArgs e)
         {
              FetchData(DataSource.Csv);
         }

    private void SQLFetchButton_Click(object sender, RoutedEventArgs e)
    {
        FetchData(DataSource.SqlServer);
    }

    public class InputData : IBasic
    {
        public string Name { get; set; }
        public int X { get; set; }
        public int Y { get; set; }
    }

public interface IBasic
{
}
  • 1
    consider using an ioc container – Daniel A. White Apr 15 '18 at 17:25
  • 2
    Using IOC would be ideal - inject all the repository types and let the container resolve their dependencies, but you could start simpler with a `Settings` class that contains all the App settings (implementing `ISettingsSql`, `ISettingsCsv`, ...) and pass it into your factory so that each repo can get its paths. – Ian Mercer Apr 15 '18 at 17:28
  • @IanMercer Could you post as an answer solution based on my example using mentioned IoC? – StudentOfLifeTMistakeTResult Apr 15 '18 at 17:49
  • @IanMercer Let's say i want to pass path and filename into CsvRepository and for SQLrepository: connectionstring, user and password and RepositoryX which will not require any information. Of course there will be more repositories either requiring some information or not. – StudentOfLifeTMistakeTResult Apr 15 '18 at 17:53

2 Answers2

0

Usually, when your factory needs parameters for concrete implementations - you split factories in two. One with additional parameters and one without it. One of good options is to define interface for factory and create two methods:

public interface IRepoFactory
{
    IRepo GetRepository(DataSource repositoryType);
    IRepo GetRepository(DataSource repositoryType, /*a ton of informations here*/);
}

On the other hand, I always prefer to split interfaces. Read bellow as to why. BUT, lets first look at what kind of parameters you can pass.

Parameters binded to lifetime of IRepo instance, should be passed in Create method and usually inside constructor, there is no exceptions, for example if you have multiple SQL servers:

public interface IRepoFactory
{
    IRepo GetRepository(string connectionString); //you can specify here concrete SQL server
    IRepo GetTestRepository(string folder); //for example you want to unit test on different folders
}

Parameters binded to lifetime of IRepoFactory, should be passed in factory constructor, because they will be active during entire lifetime of factory (usually it is also lifetime of Application, because factories often used as singletons). For example, you have single SQL server, and don't care what repository is created and to where it is connected:

public interface IRepoFactory
{
    IRepo GetRepository(); //you don't care about SQL server at all
    IRepo GetTestRepository(); //you don't care where test environment allocated
}

public class MyRepoFactory : IRepoFactory
{
    public MyRepoFactory(string connectionString)
    {
       //...
    }

    //implementation
}

So, I split them, because of parameter lifetimes. At one point you will clearly see, that CsvRepository don't care about SQL connection string but it care about path it is located at. These functionality created dependand on different things and these things have their own lifetimes and so should be split into different factories.

How to manage a ton of those interfaces? Well, factory pattern will not help with these. But IoC pattern will do, and I insist to read it because you can call it somewhat "mainstream" of production development, which has many aplications at testing/refactoring/dependencies management. Read more here.

As to your question, do these steps:

  1. Choose lifetime for you additional parameters
  2. Choose first or second or both approach described
  3. Split your factory in two, if you see they become too overloaded with functionality
  4. Use one of the two IoC pattern implementations to manage your lifetimes and dependencies of those parameters: Service Locator or Dependency Injection (I prefer DI, it is more strict approach)

EDIT realted to IoC usage:

Here is example of how it usually done in Ninject:

public interface IRepo { }
public interface IRepository<T> : IRepo where T : class {}

public interface ICsvRepo<T> : IRepository<T> where T : class { }
public interface ISqlRepo<T> : IRepository<T> where T : class { }
public interface IOracleRepo<T> : IRepository<T> where T : class { }

public interface IRepoX : IRepo { }

public interface ICsvSettings
{
    string Path { get; }
    string FileName { get; }
}

public interface ISqlSettings
{
    string ConnectionString { get; }
    string Username { get; }
    string Password { get; }
}

internal class CsvSettings : ICsvSettings
{
    public string Path { get; set; }
    public string FileName { get; set; }
}

internal class SqlSettings : ISqlSettings
{
    public string ConnectionString { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
}


internal class CsvRepo<T> : ICsvRepo<T> where T : class
{
    private readonly ICsvSettings _settings;
    public CsvRepo(ICsvSettings settings)
    {
        _settings = settings;
    }
}

internal class SqlRepo<T> : ISqlRepo<T> where T : class
{
    private readonly ISqlSettings _settings;
    private readonly IRepoX _repoX;

    public SqlRepo(ISqlSettings settings, IRepoX repoX)
    {
        _settings = settings;
        _repoX = repoX;
    }
}

internal class OracleRepo<T> : IOracleRepo<T> where T : class
{
    private readonly ISqlSettings _settings;
    private readonly IRepoX _repoX;

    public OracleRepo(ISqlSettings settings, IRepoX repoX)
    {
        _settings = settings;
        _repoX = repoX;
    }
}

internal class RepoX : IRepoX { }

public class RepoModule : NinjectModule
{
    private readonly string _username;
    private readonly string _password;
    public RepoModule(string username, string password)
    {
         _username = username;
         _password = password;
    }
    public override void Load()
    {
        Bind<ICsvSettings>().ToConstant(new CsvSettings
        {
            FileName = "foo",
            Path = "bar"
        }).InSingletonScope();

        Bind<ISqlSettings>().ToConstant(new SqlSettings
        {
            ConnectionString = "foo",
            Password = _password,
            Username = _username
        }).InSingletonScope();

        Bind<IRepoX>().To<RepoX>();

        Bind(typeof(ICsvRepo<>)).To(typeof(CsvRepo<>));
        Bind(typeof(ISqlRepo<>)).To(typeof(SqlRepo<>));
        Bind(typeof(IOracleRepo<>)).To(typeof(OracleRepo<>));
    }
}

And then in your application you do this:

class Program
{
    static void Main(string[] args)
    {
        var kernel = new StandardKernel(new RepoModule("foo", "bar"), /*some other modules here maybe?*/);

        //thousand of code lines later...

        var csvRepo = kernel.Get<ICsvRepo<MyEntity>>();
        var data = FetchData(csvRepo);

        var sqlRepo = kernel.Get<ISqlRepo<MyEntity>>();
        data = FetchData(sqlRepo);

        var oracleRepo = kernel.Get<IOracleRepo<MyEntity>>();
        data = FetchData(oracleRepo);
    }

    static T[] FetchData<T>(IRepository<T> repo)
    {
        throw new NotImplementedException();
    }
}

You can wrap it into factory if you want, but at this point it is not neccesary (you implemented factory on IoC container)

eocron
  • 6,885
  • 1
  • 21
  • 50
  • Hi, would it be possible to give more code for two options. LLet's say i want to pass path and filename into CsvRepository and for SQLrepository: connectionstring, user and password and RepositoryX which will not require any information. If you could i would much appreciate for better understanding. (if possible already with IoC. Just as simple as possible. – StudentOfLifeTMistakeTResult Apr 15 '18 at 17:52
  • Great ! I am not sure still how to pass those path, filename connection string , shouldn;t be it from Program's Main ? P.S what is StandardKernel ? – StudentOfLifeTMistakeTResult Apr 15 '18 at 19:51
  • StandardKernel is service locator, Load method is just place to configure dependencies. You can pass any parameters you like into RepoModule constructor or initialize those setting directly from App.config file or whatever way you like. – eocron Apr 15 '18 at 19:55
  • other question: in SqlRepo by IRepoX variable you wanted to show how to pass some repo to other repo right? – StudentOfLifeTMistakeTResult Apr 15 '18 at 19:57
  • Yes, you can inject anything. Be it ILogger, IRepo or whatever you need to implement this particular class - Ninject will find it across all NinjectModules you registered and provide it to you. – eocron Apr 15 '18 at 19:59
  • I see, one more thing, can you show if i would like to pass those information like from Prorgam's main and not from Load for instance just for this: Bind().ToConstant(new CsvSettings .. – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:01
  • I see, so if i were to pass more from Program's Main i need to add a bunch properties inside RepoModule? If that case don't you think it could be a bit mess if i would have a big bunch of properties because i would need to add to ctor all of that information or just many ctors... Hmm – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:14
  • In production we store it inside *App.config* file. And access it from inside Load method and *nowhere else*. It can become a mess, but usually it doesn't if it all aggregated in one place. For example, you could write JSON file and deserialize it directly into CsvSettings. Your module is just configurator - it doesn't contain implementation, so it is safe to overload it to your heart content, just write comments and group settings. – eocron Apr 15 '18 at 20:16
  • And what about simple Singleton class with static members containing all informations that every repo could read? Singleton could be fulfilled from config file an every repo could read like MySIngkleton.Path etc..? – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:21
  • It's true, but this will lead to developer nightmare called "I need to set environment...agh and dont forget about those static field which I forgot to initialize, agh another one, aaand another one". You can try it. This is main reason of developer migration to OOP, lead by tons of pain in C and Cobolt. But yes, Main method is static entry point, so is options of your application, but NOT your implementations, they shouldn't care about way they retrieve your options, they depend on them and should fail if none provided (so static is bassically banned in IoC). – eocron Apr 15 '18 at 20:28
  • but if you store your information in App.config - it's also very similar i would say to Static singleton class because all information is there as well and repos read from there as they would from singleton static class. Isn't it? – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:30
  • Yes, but this should be the only place you use static in common scenario. So, for example you can later run your app in tests, which doesn't even have App.config or Main method, and some classes are mocked with stubs. Static in Ninject is done through InSingletonScope() method. You can see it in my example. – eocron Apr 15 '18 at 20:32
  • Exactly therefore i am thinking that it would be good to have one class (not static) but singleton and keep all information there. And information comming to that singleton instance could be loaded from either xml config file or anywhere else. Then repositories will read singleton class information (paths, connectionstrings etc..) afterwords. – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:37
  • Yes, you can define interface, implementation and bind it InSingletonScope() in load method. It will live as static for all dependand classes for as long as live Kernel (just save it in Program in real static variable). – eocron Apr 15 '18 at 20:38
  • Got the point, have two more question because your solution is very kind and i like it: 1st one: if everything is in one RepoModule class - when would you create RepoModule2 ..RepoModule3 etc...? Let's say in our - Yours example current code – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:39
  • One module for one DLL and as much as you like in Test DLLs (depends on what you testing). – eocron Apr 15 '18 at 20:40
  • Got it. 2nd question is more to be written ;/ : In my oryginal code i have some repository which were using more generic interface like this: public interface IRepository : IRepo where T : class then i used it like: public class CsvRepository : IRepository – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:43
  • Would it break the code (i still haven't tested your code) if i would make for instance in your code change like instead this: public interface ICsvRepo : IRepo { } to this: ICsvRepo : IRepo where T : class and then consume it: public class CsvRepository : ICsvRepo How this change could be reflected in further code of yours? I mean what changes has to be made or it would be just working? If possible to add it in your scenario that would be just enough for me about this topic. Many thanks anyhow ! – StudentOfLifeTMistakeTResult Apr 15 '18 at 20:43
  • It is perfectly fine: https://stackoverflow.com/questions/4370515/ninject-bind-generic-repository – eocron Apr 15 '18 at 20:45
  • not sure if you still there but have a question, should those repositories be just like they are or things like GetAll, GetById, Deletr,Add can be implemented here because to me those repositories looks only for connectivity/choose one of them/ – StudentOfLifeTMistakeTResult Apr 24 '18 at 21:07
0

Using IOC would be ideal - inject all the repository types and let the container resolve their dependencies, but you could start simpler with a Settings class that contains all the App settings (implementing ISettingsSql, ISettingsCsv, ...) and pass it into your factory so that each repo can get its paths. –

e.g.

Constructor for CsvRespository:

  public CsvRepository(ISettingsCsv settings)
  {
       _path = settings.CsvPath;
  }

Constructor for SqlRepository:

  public SqlRepository(ISettingsSql settings)
  {
       _connectionString = settings.SqlConnectionString;
  }

Next, wrap all these up in an overall settings interface:

  public interface ISettings : ISettingsSql, ISettingsCsv, ...
  {
  }

And now you can implement:

  public class Settings : ISettings
  {
     public string CsvPath {get; set;}
     public string SqlConnectionString {get; set;}
     ...
  }

Create an instance of that class with all your settings in it:

  var settings = new Settings { CsvPath = "...", ... };

And now you can inject that into your factory where it can be used by the factory method:

 public class RepositoryFactory
 {
    ISettings settings;
    public RepositoryFactory (ISettings settings)
    {
       this.settings = settings;
    }

    ...

   public IRepo GetRepository(DataSource repositoryType)
   {
      ...
          return new CsvRepository(this.settings);
   }

The next step would be to use a container to register all repositories and then resolve them as a collection IEnumerable<IRepo> allRepos and change the factory to find the repo that can match the enum (expose the enum on each repo class) and use a loop or a dictionary. The switch statement can then go away. When you do that, each repo will try to resolve its own ISettingsXXX dependency and so on.

Ian Mercer
  • 38,490
  • 8
  • 97
  • 133