依赖注入 SQL 连接?

10

首先,我开始使用StructureMap,但任何DI框架的示例都可以。

我有一个类如下所示,

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

这只是对类实际外观的简单概述,但基本上就是这样。

现在,我有一个新的需求。我需要能够在类的初始化或方法上更改连接字符串。例如:

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

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

   geoData.GetCountries();
}

使用依赖注入有更好的解决方案吗?您会如何使用所选的 DI 框架来实现此操作?

6个回答

13

从技术上讲,Wim Hollebrandse已经回答了你的问题,但我想指出我个人会用另一种方式来做,因为我不喜欢每次实例化类时都要传递连接字符串。虽然我知道你有一个默认构造函数,但我认为我们可以使它更加简洁。

首先,我会创建一个静态类来获取你的连接,如下所示:

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

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

那么,我会像这样使用它:

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

采用这种方法,如果默认连接字符串更改,您只需更改一行代码,而不是去修改从配置文件引用连接字符串的每一行代码。但它确实为您提供了必要时覆盖连接字符串的能力。

希望对你有所帮助...


1
今天的恶意评论 - 你如何关闭和处理连接? - Oskar Austegard
@OskarAustegard 我会把它放在 using 语句中。IDbConnection 实现了 IDisposable 接口,这是最简单的方法。 - senfo
建议更新示例以表明 - Oskar Austegard
我真的不太明白这有什么帮助,看起来你现在对ConnectionFactory有了一个隐藏的依赖。请参考这个非常详细的答案,了解如何处理类似情况:https://dev59.com/8mkw5IYBdhLWcg3wbqGZ#9915056 - neilt17

7

很简单,你不需要一个框架。只需为你的GeoData类编写一个重载构造函数。

GeoData geo = new GeoData(yourConnString);

字符串是您的依赖项。由于它不是复杂类型,您可以在这里正确地进行依赖注入。

依赖注入并不是什么难以理解的东西,尽管有些人可能会让你相信这一点。


是的,那是另一种解决方案,只是想知道是否有更好的方法。 - Jaimal Chohan
这就是依赖注入,只是没有使用复杂的框架。 - Wim
2
@Wim - 你说得对,你不必使用DI框架,但是字符串不是你的依赖项;数据访问才是。传递一个字符串并不能让你在没有数据库的情况下单元测试GeoData中的业务逻辑。一个快速的解决方法可能是传递IDbConnection(也允许重用连接)。更好的选择可能是:http://martinfowler.com/eaaCatalog/repository.html - TrueWill
是的,但在这种情况下,数据访问依赖项构成了连接字符串。这就是OP想要的。没有提到多个DB支持。如果您想允许多个DB平台,则IDbConnection 可能是正确的接口进行注入。个人对于使用IDbConnection并不确定,因为现在关闭连接的责任已经转移到容器中。还是吗?这就是重点。 - Wim
1
我认为这是我那些脑抽的时刻之一,因为当我坐在马桶上(所有伟大的启示都发生在那里)时,我想起我已经有了自己的接口驱动数据访问类,而我所要做的就是传入我的IDbFactory类的自定义实例。 - Jaimal Chohan
不错,这确实是你想要的。而且这是你自己定制的 DI - 只需将其注入到 GeoData 类的构造函数中即可。不过你对厕所也是正确的。;-) - Wim

5

首先要问自己的问题是什么是 GeoData?换句话说,这个类的责任是什么?

它似乎是 域层 的一部分,因此可能包含业务逻辑。它可能被 (耦合 到) 其他类中。

如果是这样的话,有哪些依赖关系?确定依赖关系的一种方法是尝试编写可以独立测试 GeoData 的 单元测试。如果测试需要大量设置,则被测试的类要么与其他类紧密耦合,要么具有多个职责 (内聚性 低)。

假设我们更改该类,使构造函数接受连接字符串参数。我们如何测试公共的 GetCountries 方法呢?嗯,首先我们要使用已知的测试数据设置数据库...

那会耗费时间且容易出错(如果有人更新数据怎么办?),而且测试运行速度相对较慢(需要连接到数据库)。
好的,我们可以将实现IDbConnection接口的对象传递给构造函数(构造函数注入)。请注意,依赖注入通常涉及传递接口或抽象类。为了进行测试,我们必须创建一个虚拟的IDbConnection。我们可以使用隔离(模拟)框架。但是然后我们需要在调用CreateCommand时创建一个虚假的IDbCommand...
引用Jeremy Miller(StructureMap的作者)的话:“这是太大的努力换取了太小的收益。”请参阅他的文章Mock Objects的最佳和最差实践
一种可能是使用仓储模式。您将接口传递给GeoData构造函数中的特定存储库。这将很容易进行测试(手动或使用模拟库)。具体存储库将处理所有数据访问。它可以与ORM框架结合使用,以进一步抽象数据访问。连接字符串管理将通过ORM或存储库完成(最好在另一个依赖项或基类中)。
如果听起来很复杂,那是因为确实如此。您选择了依赖注入中最困难的情况之一(不幸的是,这也是最常见的情况之一)。
依赖注入本身是一个相当简单的概念。如果您的类正在调用Web服务,则可以将Web服务代码放置在仅执行此操作的单独类中,实现接口,并将该接口传递给原始类。如果您有许多类和/或依赖项,则DI / IoC容器框架可以使此过程更加容易,但它们不是必需品。
编辑:仅为明确起见,依赖注入不是复杂的部分。分离数据访问是。

1

我会创建一个工厂,用于创建 GeoData 类的实例,该类实现了一个带有 Do 方法的接口(例如 IDoCommand)。

工厂的责任是使用全局上下文来确定要注入到 GeoData 实例中的连接字符串(构造函数是我首选的技术),或者将其作为参数传递给其 Create 方法。


最好您可以附上示例代码,让我们更好地讨论如何实现。谢谢。 - Mou
@Mou 我同意,但我时间不够。有些人可能会把我的答案看作是完整解决方案的指南/方向,并对此感到满意。其他人可能会把我的答案看作是更大帖子的介绍。在我看来,我仍然认为提供指南类型的答案比不回答要好。 - Ron Klein
@RonKlein 五年了还是时间不够用吗?:))) - knoxgon
@VolkanGüven 疯狂吧,是不是? :D - Ron Klein

0
我会创建一个新的类来包含连接字符串选择逻辑,然后使用它来获取GeoData实例的连接字符串。
public class ConnectionStringManager
{
    public string GeoDataConnectionString
    {
        get
        {
            return x 
                ? ConfigurationManager.ConnectionString["LIVEGeoDataConnection"])
                : ConfigurationManager.ConnectionString["STAGINGGeoDataConnection"]);
        }
    }
}

然后,您可以将其注入到包含Do方法的类中,以设置GeoData实例,如下所示:

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

0
Martin Fowler在这篇文章这里中讲解了不同的方法。就个人而言,我更喜欢接口注入,但这是一个品味问题。

1
你不会创建一个只有一个字符串属性的 IConnectionString 接口。如果依赖关系是一个复杂类型,那么我更喜欢在构造函数中使用接口注入。但同样,并不需要使用框架。 - Wim
同意,但我怀疑唯一的区别可能只是连接字符串,这很可能只是冰山一角。如果连接字符串确实是唯一不同的属性,那么一个setter方法就足够了。 - Kasper

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接