DI / IoC,NHibernate并帮助他们一起工作

时间:2008-12-22 23:37:08

标签: c# nhibernate dependency-injection inversion-of-control

我正试图了解DI / IoC,NHibernate,让他们在我正在开发的应用程序中很好地协同工作。我对NHibernate和DI / IoC都很陌生,所以不太确定我所做的是否是合理的方式。这是场景:

该应用程序为用户提供了计算特定金融交易的特定值(称为保证金)的能力。每个事务的marging值的计算是通过抽象MarginCalculator类的具体实现来执行的,并且要使用的具体实现取决于特定事务的产品类型(由产品对象的某个字段给出)。具体的计算器类可通过产品类的属性访问。即。

public class Transaction
{
    private double _margin;
    private Product _product;
    private Client _client;

    public double Margin { get; }
    public Product Product { get; }
    public Client Client { get; }

    public Transaction(Product p, Client c)
    {
        _product = p;
        _client = c;
    }

    public void CalculateMargin()
    {
        _margin = _product.MarginCalculator.CalculateMargin();
    }
}

public class Product
{
    private string _id;
    private string _productType;
    ... Other fields

    public string Id { get; }
    public string ProductType { get; }
    public MarginCalculator MarginCalculator
    {
        get { return MarginCalculatorAssembler.Instance.CreateMarginCalculatorFor(this.ProductType); }
    }
}

public class MarginCalculatorAssembler
{
    public static readonly MarginCalculatorAssembler Instance = new MarginCalculatorAssembler();

    private MarginCalculatorAssembler ()
    {
    }

    public MarginCalculator CreateMarginCalculatorFor(string productType)
    {
        switch (productType)
        {
            case "A":
                return new ConcreteMarginCalculatorA();
            case "B":
                return new ConcreteMarginCalculatorB();
            default:
                throw new ArgumentException();
        }
    }
}

public abstract class MarginCalculator
{
    public abstract double CalculateMargin();
}

public class ConcreteMarginCalculatorA : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

public class ConcreteMarginCalculatorB : MarginCalculator
{
    public override double CalculateMargin
    {
        // Perform actual calculation
    }
}

用户从下拉列表中选择特定客户端和产品,并将相应的clientId和productId传递给存储库,然后使用NHibernate在将产品和客户端对象注入事务对象之前填充它们。在我当前的设置中,事务通过构造函数依赖注入(尚未使用IoC容器)接收其产品和客户端依赖性,即

public class ProductRepository : IRepository<Product>
{
    public Product GetById(string id)
    {
        using (ISession session = NHibernateHelper.OpenSession())
            return session.Get<Product>(id);
    }
}

/* Similar repository for Clients */

IRepository<Client> clientRepository = new ClientRepository();
IRepository<Product> productRepository = new ProductRepository();
Client c = clientRepository.GetById(clientId);
Product p = productRepository.GetById(productId);

Transaction t = new Transaction(p, c);

以下是我希望得到的想法:

A。 通过Product域对象访问MarginCalculator(本质上是一项服务)是否可以,或者应该按照此处的建议({{3重新构造代码以便从域对象中删除服务依赖性,而是创建一个新的TransactionProcessor类,它将抽象的MarginCalculator作为依赖项(沿着这里描述的行(http://stackoverflow.com/questions/340461/dependency-injection-with-nhibernate-objects),即

public class TransactionProcessor
{
    private readonly MarginCalculator _marginCalculator;

    public TransactionProcessor(MarginCalculator marginCalculator)
    {
        _marginCalculator = marginCalculator;
    }

    public double CalculateMargin(Transaction t)
    {
        return _marginCalculator.CalculateMargin(Transaction t);
    }
}

public abstract class MarginCalculator
{
    public abstract double CalculateMargin(Transaction t);
}

B 是否可以使用IoC容器来获取带有NHibernate填充/生成的产品和客户端依赖项的Transaction对象? 即,给定由用户提供的productId和clientId,是否可能具有如下内容:

// pseudocode
Transaction t = IoC.Resolve<Transaction>(productId, clientId);

这样容器解析了Transaction对象的Product和Client依赖关系,NHibernate用于根据productId和clientId填充Product和Client,然后将填充的Product和Client注入到Transaction中?

C。在典型的DI场景中,如果A类依赖于接口B,则可能会执行以下操作:

IInterfaceB b = new ClassB();
A a = new A(b);

interface IInterfaceB
{
}

class B : IInterfaceB
{
}

public class A
{
    private IIntefaceB _b;

    public A(IInterfaceB b)
    {
        _b = b;
    }
}

然而,这实际上是如何显示DI的所有示例,假设IInterfaceB的实现者(在这种情况下是B类)在设计时是已知的。 有没有办法以运行时确定实现者的方式使用DI?

非常感谢

马修

6 个答案:

答案 0 :(得分:1)

A)如果您要通过Product域对象访问MarginCalculator,您可能会切断中间人并让DI / IOC容器为您注入MarginCalculator。你甚至可以摆脱MarginCalculatorAssembler,因为大多数DI / IOC容器为你做了大部分对象构造的样板代码。

B和C )这很可能。实际上,如果您使用LinFu

,这就是您的代码的样子

// No need to change the Transaction class
public class Transaction
{
    private double _margin;
    private Product _product;
    private Client _client;

    public double Margin { get; }
    public Product Product { get; }
    public Client Client { get; }

    public Transaction(Product p, Client c)
    {
        _product = p;
        _client = c;
    }

    public void CalculateMargin()
    {
        _margin = _product.MarginCalculator.CalculateMargin();
    }
}

如果你能让DI / IOC将产品和客户端实例注入构造函数中会很好 - 但在我们这样做之前,你需要在容器中注册依赖项。以下是LinFu.IOC的使用方法:

// Next, you'd have to tell LinFu to automatically register your product class:
[Factory(typeof(Product))]
public class ProductFactory : IFactory
{
     object CreateInstance(IServiceRequest request)
     {
          // Grab a copy of the IRepository from the container
          var repository = container.GetService>();

          // Get the id (this assumes that your id is an Int32)
          var id = (int)request.Arguments[0];

          // Return the product itself
          return repository.GetById(id);
     }
}

// Do the same thing with the Client class
// (Note: I did a simple cut and paste to keep things simple--please forgive the duplication)
[Factory(typeof(Client))]
public class ClientFactory : IFactory
{
     object CreateInstance(IServiceRequest request)
     {
          // Grab a copy of the IRepository from the container
          var repository = container.GetService>();

          // Get the id (this assumes that your id is an Int32)
          var id = (int)request.Arguments[0];

          // Return the client itself
          return repository.GetById(id);
     }
}

[Factory(typeof(Transaction))]
public class TransactionFactory : IFactory
{
     object CreateInstance(IServiceRequest request)
     {
        // Note: Argument checking has been removed for brevity
        var container = request.Container;
        var arguments = request.Arguments;
        var productId = (int)arguments[0];
        var clientId = (int)arguments[1];

        // Get the product and the client
        var product = container.GetService(productId);
        var client = container.GetService(clientId);

        // Create the transaction itself
        return new Transaction(product, client);
     }
}

// Make this implementation a singleton
[Implements(typeof(MarginCalculator), LifecycleType.Singleton)]
public class ConcreteMarginCalculatorA : MarginCalculator
{
    public override double CalculateMargin()
    {
        // Perform actual calculation
    }
}

一旦你在其中一个程序集中编译了所有代码,这里只需要将它加载到容器中:

var container = new ServiceContainer();
container.LoadFrom(AppDomain.CurrentDomain.BaseDIrectory, "YourAssembly.dll");

...现在为有趣的部分。为了使用给定的产品和客户端ID创建您的事务对象,这是您需要对LinFu.IOC的容器进行的调用:

int productId = 12345;
int clientId = 54321;
string serviceName = null;

// Not pseudocode :)
var transaction = container.GetService(serviceName, productId, clientId);

令人感兴趣的是,尽管您可能拥有多少依赖项,LinFu的IOC容器将为您处理90%的样板代码,因此您无需自己完成所有这些工作。 最好的部分是上面的所有实现都将在运行时确定/解决

您可以在程序运行时实际交换实现,甚至可以在不重新编译应用程序的情况下替换实现。您可以在这里找到更多信息:

http://www.codeproject.com/KB/cs/LinFu_IOC.aspx

HTH:)

答案 1 :(得分:1)

这是我对你的问题的第二次看法:

答:就最佳实践而言,只要确保依赖于接口类型,就可以将服务依赖项保留在域对象中。大多数(如果不是全部)容器都可以为您执行这种类型的注入,并且模拟每个服务依赖项非常简单,因此您可以测试具体类中的每个行为。如果您想重构特定接口实现的样板实现,我建议使用抽象类,例如使用基类来执行通用CRUD持久性工作。

B和C:

  

很高兴知道这种功能可用。我想一个更重要的问题是,我想要做的事实上是否是普遍的做法,以及它是否被视为良好做法。即。

     
      
  1. 使用持久性框架(例如NHibernate)和
  2. 让容器解析并注入预先填充的依赖项&gt;   
  3. 让容器注入抽象依赖项的具体实现,其中具体实现是在运行时确定的。
  4.         

    另外,在IoC / DI / NHibernate术语中,我所说的是什么,有一个特定的名字?例如,它是此比较中列出的功能之一还是.net IoC框架的比较?我想了解其他IoC框架(如Castle Windsor)是否包含像LinFu这样的功能,但我不知道我所描述的是否有一个特定的名称所以我不知道该搜索什么:)

我相信你实际上指的是this link发布的比较。

1)AFAIK,它是执行服务注入的标准做法,但是您要引用的注入类型对于某些其他框架很难做,因为您必须使用域对象ID来解决这些依赖关系。运行时,并非所有容器都支持这种类型的动态解析(也称为“上下文绑定”)。在所有条件相同的情况下(假设这可以通过其他容器完成),唯一适用于DI / IoC的“最佳实践”是您必须使用接口来实现服务依赖性。

这些依赖关系应该如何最终构建和解决应该完全取决于你,在你的情况下,只要容器本身能够消除,如果从持久性框架中填充这些依赖关系真的无关紧要大部分样板解析代码都适合您。

2)具体的服务注入是DI / IOC框架的标准,大多数都可以在运行时解决依赖关系;然而,这些框架在注射的方式和地点方面存在差异。

仅供参考,您应该注意的两个功能是构造函数注入属性注入。根据您的代码示例,我会说您更倾向于使用构造函数注入,因此您可能需要留意每个相应框架如何为您执行此类注入。 HTH:)

答案 2 :(得分:0)

菲利普,

感谢您的回答!

B和C

很高兴知道这种功能可用。我认为一个更重要的问题是 我正在尝试做的事实上是否是常见做法以及它是否被视为良好做法。

  1. 让容器解析并注入使用持久性框架预先填充的依赖项(例如NHibernate)和
  2. 让容器注入抽象依赖项的具体实现,其中具体实现是在运行时确定的。
  3. 此外,在IoC / DI / NHibernate术语中,我正在谈论的是什么,有一个特定的名称? 它是否是,例如,其中一个功能在.net IoC框架的this comparisonthis comparison中列出的内容?我想了解其他IoC框架(如Castle Windsor)是否包含像LinFu这样的功能,但我不知道我所描述的是否有一个特定的名称所以我不知道该搜索什么:)

    <强> A:

    就最佳实践而言(即松耦合,测试等......),最好是从域对象中删除服务依赖项还是将其保留在那里?

    由于

    马修

答案 3 :(得分:0)

根据“域名驱动设计”,您的服务将是“域名服务”,您的域名可以直接调用或依赖它。

如果您打算使用Nhibernate,请查看Spring.net,这是一个非常流行的DI框架,为您提供DAOS,已经注入会话。它还允许您使用声明性事务(使用属性标记方法)。该项目的文档非常好。

最后但并非最不重要,并且不要误会我的意思,我认为你正在使用这项技术只是因为(我没有看到你有DI的NEED),如果你这样做很酷学习东西,但在其他所有情况下都是错误的。

此致

答案 4 :(得分:0)

巴勃罗,

感谢您的评论。

也许如果我在一个我打算在项目中使用DI的区域(不仅如你所说,了解DI而且因为我认为这是必要的)更详细一点,然后可以进一步评论是否是使用DI的正确位置。

如原始帖子所述,该应用程序将使用MarginCalculator服务:

public abstract class MarginCalculator
{
    public abstract double CalculateMargin();
}

注意:该服务可能是抽象类或接口。

具体实现(DI术语中的组件?)如下:

public class ConcreteMarginCalculatorA : MarginCalculator
{
    private IDependencyService1 _dependencyService1;
    private IDependencyService2 _dependencyService2;

    // Constructor dependency injection
    public ConcreteMarginCalculatorA(
        IDependencyService1 dependencyService1,
        IDependencyService2 dependencyService2)
    {
        this._dependencyService1 = dependencyService1;
        this._dependencyService2 = dependencyService2;
    }

    public override double CalculateMargin
    {
        // _dependencyService1 and _dependencyService2 
        // required here to perform calcuation.
    }
}

public class ConcreteMarginCalculatorB : MarginCalculator
{
    private IDependencyService3 _dependencyService3;
    private IDependencyService4 _dependencyService4;

    // Constructor dependency injection
    public ConcreteMarginCalculatorB(
        IDependencyService3 dependencyService3,
        IDependencyService4 dependencyService4)
    {
        this._dependencyService3 = dependencyService3;
        this._dependencyService4 = dependencyService4;
    }

    public override double CalculateMargin
    {
        // _dependencyService3 and _dependencyService4 
        // required here to perform calcuation.
    }
}

具体的保证金计算器及其构造不是应该使用依赖注入的完美示例,以及如何使用IoC容器来处理依赖注入?

我认为我要做的事情与this onethis one等文章中的DI / IoC描述非常相似。

最后,我将使用工厂类,可能使用内部/子容器,以便根据参数值动态解析组件/实现者(ConcreteMarginCalculatorA,ConcreteMarginCalculatorB等...)。为实现这一目标,我倾向于Autofachttp://code.google.com/p/autofac/),它允许根据参数值选择实现者(http://code.google.com/p/autofac/wiki/ComponentCreation - “基于参数值选择实现者”一节):

public class MarginCalculatorFactory
{
    private readonly IContainer _factoryLevelContainer;

    public MarginCalculatorFactory(IContainer mainContainer)
    {
        _factoryLevelContainer = mainContainer.CreateChildContainer()
        _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorA>("ConcMC1");
        _factoryLevelContainer.RegisterType<MarginCalculator, ConcreteMarginCalculatorB>("ConcMC2");
}

public MarginCalculator CreateCalculator(string productType)
{
    return _factoryLevelContainer.Resolve<MarginCalculator>(productType);
}

}

所以最终我能做到:

marginCalculatorFactory.CreateCalculator(productType);

在客户端代码中获取完全解析的计算器。然后,计算器可以依赖注入到TransactionProcessor服务中:

public class TransactionProcessor
{
    private readonly MarginCalculator _marginCalculator;
    private readonly Transaction _transaction;

    public TransactionProcessor(MarginCalculator marginCalculator
        ,Transaction transaction)
    {
            _marginCalculator = marginCalculator;
            _transaction = transaction
    }

    public double CalculateMargin(Transaction t)
    {
            return _marginCalculator.CalculateMargin(transaction);
    }
}

我可能错了,因为我是整个IoC / DI游戏的新手,但在我看来,这正是正好 Di / IoC用于的场景。 其他人的想法是什么?

由于

马修

答案 5 :(得分:0)