views:

212

answers:

6

Firstly, I'm starting to use StructureMap, but an example in any DI framework will do.

I have a class as so,

public class GeoData
{
   public List<Country> GetCountries()
   {
      IDbConnection con = new SqlConnection(ConfigurationManager.ConnectionString["GeoDataConnection"])    
      //Sql stuff to return countries from a database
   }
}

It's a simplistic view of what the class actually looks like, but essentially, that's it.

Now, I have a new requirement. I need to be able to change connectionstring either on class initialization or on method. E.g.

public void Do()
{
   var geoData = new GeoData();

   if(x)
   {
      geoData.ConnectionString = ConfigurationManager.ConnectionString["LIVEGeoDataConnection"]);
   }
   else
   {
      geoData.ConnectionString = ConfigurationManager.ConnectionString["STAGINGGeoDataConnection"]);
   }

   geoData.GetCountries();
}

Is there a better solution for this using dependency injection? How would you you do this using a DI framework of your choice?

+3  A: 

Simple, you don't need a framework. Just have an overloaded constructor for your GeoData class.

GeoData geo = new GeoData(yourConnString);

The string is your dependency. As it is not a complex type, you got your dependency injection right there.

DI is no rocket science, though some may like you to believe that.

Wim Hollebrandse
Yea, thats another soltuion, just wondering if there was a better way.
Jaimal Chohan
That *is* dependency injection. Just without using an elaborate framework.
Wim Hollebrandse
@Wim - you're correct that you don't have to use a DI framework, but the string is **not** your dependency; the data access is. Passing in a string does not allow you to unit test business logic in GeoData without a database. A quick fix might be to pass in IDbConnection (also permits reusing connections). A better option might be: http://martinfowler.com/eaaCatalog/repository.html
TrueWill
Yes, but in this instance the data access dependency constitutes the connection string. That's what the OP wanted. Nothing was mentioned about multiple DB support. If you want to allow for multiple DB platforms, then yes, IDbConnection *might* be the correct interface to inject. Personally not so sure about using IDbConnection for that, as the responsibility to now Close the connection has been shifted to the container. Or has it? That's the point.
Wim Hollebrandse
I think this was one of those brain dead moments for me, becuase whilst sitting on the loo (where all great revelations occur) I remembered I already have my own interface driven data access classes, and all I have todo was pass in a custom instance of my IDbFactory class.
Jaimal Chohan
Nice, that's indeed what you'd want. And it's your own custom DI - just inject it in the ctor of your GeoData class. You're right about the loo though. ;-)
Wim Hollebrandse
A: 

Martin Fowler has an article on the subject here, that explains the various approaches. On a personal note, I prefer the interface injection but that is a question of taste.

Kasper
You wouldn't create an IConnectionString interface with one string property though. If the dependency is a complex type, than interface injection in the ctor is something I prefer too. But again, not even necessary to use a framework.
Wim Hollebrandse
Agree, but I doubt that the only difference will be the connectionstring, that is most likely just the tip of the iceberg. If the connectionstring really is the only property that differs then a setter method will do quite nicely
Kasper
A: 

What I would do is to create a new class to contain the connection string selection logic and then use that to get the connection string for a GeoData instance:

public class ConnectionStringManager
{
    public string GeoDataConnectionString
    {
     get
     {
      return x 
       ? ConfigurationManager.ConnectionString["LIVEGeoDataConnection"])
       : ConfigurationManager.ConnectionString["STAGINGGeoDataConnection"]);
     }
    }
}

You can then inject that into the class containing the Do method to setup GeoData instances like this:

public class Blah(ConnectionStringManager connManager)
{
    public void Do()
    {
     var geoData = new GeoData { ConnectionString = connManager.GeoDataConnectionString };
     geoData.GetCountries();
    }
}
Lee
+1  A: 

I'd create a factory that creates an instance of the GeoData class, which, in turn, implements an interface with the Do method (say, IDoCommand).

It's the factory's responsibility to either use a global context to determine which connection string to inject to the GeoData instance (constructor is my preferred technique), or have it in its Create method as an argument.

Ron Klein
A: 

The first question to ask yourself is what is GeoData? In other words, what is the class's responsibility?

It appears to be part of the Domain Layer, and as such might contain business logic. It may be used by (coupled to) other classes.

If that's the case, what are the dependencies? One way of determining this is by attempting to write unit tests that test GeoData in isolation. If testing requires significant setup, the class under test is either tightly coupled to other classes or has multiple responsibilities (low cohesion).

Let's say we change the class so that the constructor takes a connection string parameter. How can we test the public GetCountries method? Well, first we set up a database with known test data...

That's time-consuming and fragile (what if someone updates the data?), and the test will run relatively slowly (it has to connect to the database).

Well, we could pass in an object implementing IDbConnection to the constructor (constructor injection). Note that dependency injection normally involves passing in interfaces or abstract classes. To test it, we'd have to create a fake IDbConnection. We could use an isolation (mocking) framework. But then we'd need it to create a fake IDbCommand when CreateCommand was called...

To quote Jeremy Miller (author of StructureMap) "It’s too much effort for too little gain." See his article Best and Worst Practices for Mock Objects.

One possibility is to use the Repository Pattern. You would pass in an interface to the specific repository to the GeoData constructor. This would be easy to fake (manually or with a mocking library) for testing. The concrete repository would handle all the data access. It could be combined with an ORM framework to further abstract the data access. Connection string management would be done through the ORM or through the repository (preferably in another dependency or a base class).

If this sounds complex it's because it is. You picked one of the most difficult cases for Dependency Injection (which, unfortunately, is also one of the most common).

Dependency Injection by itself is a fairly simple concept. If your class is calling a Web Service, you can place the Web Service code in a separate class that does nothing else, implement an interface, and pass that interface to your original class. DI/IoC container frameworks can make this easier to do if you have a lot of classes and/or dependencies, but they aren't a requirement.

EDIT: Just to be clear, Dependency Injection is not the complex part. Separating the data access is.

TrueWill
+1  A: 

Technically, Wim Hollebrandse has already answered your question, but I just wanted to point out that I would personally do it another way because I don't like having to pass the connection string in each time I instantiate the class. I realize that you have a default constructor, but I think we could make this a little cleaner, still.

First, I'd create a static class for getting your connection, as follows:

internal static class ConnectionUtility
{
    internal static IDbConnection GetConnection()
    {
        return GetConnection(ConfigurationManager.ConnectionString["GeoDataConnection"]);
    }

    internal static IDbConnection GetConnection(string connectionString)
    {
        return new SqlConnection(connectionString);
    }
}

Then, I'd use it like this:

public class GeoData
{
   public List GetCountries()
   {
      IDbConnection con = ConnectionFactory.GetConnection();
      //Sql stuff to return countries from a database
   }
}

With this approach, if the default connection string changes, you need only change one line of code, rather than going to each line that referenced the connection string from your configuration file. It does, however, provide you with the ability to override the connection string, if necessary.

Hope that helps...

senfo