【问题标题】:Inject two instances of object注入两个对象实例
【发布时间】:2011-12-05 18:55:00
【问题描述】:

简介

开始很简单:假设我有一个基本控制器,它使用数据访问对象(内部使用实体框架)来获取实体:

public class SomeController : Controller
{
  private readonly DataAccess dataAccess;

  public SomeController(DataAccess dataAccess)
  {
    this.dataAccess = dataAccess;
  }

  public ActionResult Index(int id)
  {
    var model = new Model();
    model.Customer = this.dataAccess.Get(id);

    return View(model);
  }
}

问题

现在我想在我的数据访问类中执行一个异步任务,它可能运行 5 分钟,甚至更长。我不想重用注入的数据访问类,因为我喜欢为每个 HttpRequest 保留一个实体框架上下文。所以我想这样做:

public class SomeController : Controller
{
  private readonly DataAccess dataAccess;
  private readonly DataAccess dataAccessForAsyncTask;

  public SomeController(DataAccess dataAccess, DataAccess dataAccessForAsyncTask)
  {
    this.dataAccess = dataAccess;
    this.dataAccessForAsyncTask = dataAccessForAsyncTask;
  }

  public ActionResult Index(int id)
  {
    var model = new Model();
    model.Customer = this.dataAccess.Get(id);

    this.dataAccessForAsyncTask.ExecuteAsyncTask();

    return View(model);
  }
}

问题 2

我的数据访问类在 .InstancePerHttpRequest() 中注册,因为我喜欢为每个 HttpRequest 保留一个实体框架上下文。

问题

这甚至可能吗?还是我应该完全不同? 如果可能的话,我如何使用 Autofac 完成此任务?

更新

根据 Dennis Palmer 的回答,我调整了我的代码。我的解决方案是创建一个必须执行的服务的新实例,这次不使用 Autofac。 (因为必须异步执行的任务运行时间很短,所以我选择不使用队列。)

// This controller just calls a method on my Service Layer. Nothing special.
public class SomeController : Controller
{
  private readonly Service service;

  public SomeController(Service service)
  {
    this.service = service;
  }

  public ActionResult Index(int id)
  {
    var model = new Model();
    model.Customer = this.service.Get(id);

    return View(model);
  }
}

// This is the Service where the magic happens.
Public class Service
{

  private readonly DataAccess dataAccess;

  // This constructor is used if we want to create a new
  // instance without Autofac
  public Service()
  {
    this.dataAccess = new DataAccess();
  } 

  // This constructor is used if we want to let Autofac
  // create a new instance
  public Service(DataAccess dataAccess)
  {
    this.dataAccess = dataAccess;
  }

  public Customer Get(long id)
  {
    // Get the customer in a Synchronous way.
    var customer = dataAccess.Get(id);

    // Now we have to do something Async.
    // Solution: create a new instance by hand, of the
    // class that holds the method we want to call Async.
    var serv = new Service();

    // Execute the call in a async way.
    new Action<long, long>(serv.DoSomething).BeginInvoke(null, null);

    return customer;
  }

  // This is the method we want to execute async.
  public void DoSomething()
  {
    // Do something short or long running.
  }
}

【问题讨论】:

    标签: c# asp.net-mvc dependency-injection inversion-of-control autofac


    【解决方案1】:

    我认为您不需要单独的 dataAccess 实例。即使您的请求立即返回,正在执行异步代码的线程也需要保持活动状态,直到该代码完成执行。因此,每个请求的一个上下文应该可以正常工作。

    视图将立即返回给客户端,但为该请求提供服务的线程应继续运行,直到异步任务完成。如果这没有发生,那么您应该问一个问题,即如何使该线程保持足够长的时间以使其发生,并且对线程和异步操作有更多了解的人可以提供更好的答案。

    编辑:(回应评论)因此,如果数据上下文正在被释放,那么您就会遇到线程问题。不管你如何实例化或注入数据上下文对象,如果线程没有足够长的时间让异步任务完成运行,那么它们就会被中断。

    如果它是一个长时间运行的后台任务,我会考虑使用消息队列并实现一个后台任务,该任务在独立于 MVC 应用程序的自己的进程上运行。类似于 Windows Azure 中的 Worker 角色。

    【讨论】:

    • 更新:刚刚尝试过,但正在处理实体框架对象上下文。所以这并不像看起来那么容易。
    【解决方案2】:

    这都是关于生命周期范围的。当您使用InstancePerHttpRequest 时,Autofac 创建的所有组件的生命周期范围是单个 Http 请求。完成后,Autofac 会处理所有组件。

    解决方案非常简单:如果您有一个跨越多个 Http 请求的异步任务,那么此任务决定了它需要的组件的生命周期范围。因此,您只需要开始一个新的生命周期范围,然后使用它来解析异步任务所需的所有组件,并在任务完成时处置范围。诀窍是:您需要从 root 范围创建一个生命周期范围,否则它将在 HTTP 请求结束时被释放。这可以通过多种方式实现。

    假设你使用 MVC3 并关注the autofac integration instructions,你可以这样做:

    public class App : HttpApplication
    {
      public ILifetimeScope RootLifetimeScope { get; private set; }
    
      protected void Application_Start()
      {
        var builder = new ContainerBuilder();
        builder.RegisterControllers(typeof(MvcApplication).Assembly);
        var container = builder.Build();
        DependencyResolver.SetResolver(new AutofacDependencyResolver(container)); 
        // save the reference to be able to access it later
        this.RootLifetimeScope = container;
    
        //...
      }
    }
    
    public class SomeController : Controller
    {
      private readonly Service service;
    
      public SomeController(Service service)
      {
        this.service = service;
      }
    
      public ActionResult Index(int id)
      {
        var model = new Model();
        model.Customer = this.service.Get(id);
    
        return View(model);
      }
    }
    
    Public class Service
    {
    
      private readonly DataAccess dataAccess;
      readonly ILifetimeScope _OwnScope;
    
      public Service(DataAccess dataAccess)
      {
        this.dataAccess = dataAccess;
      }
    
      public Customer Get(long id)
      {
        // Get the customer in a Synchronous way.
        var customer = dataAccess.Get(id);
    
        // Now we have to do something Async.
        // Solution: create a separate lifetime scope that survives longer than HTTP request
        Debug.Assert(HttpContext.Current != null)
        var newScope = ((App)HttpContext.Current.ApplicationInstance)
           .RootLifetimeScope.BeginLifetimeScope();
    
        // DO NOT use using statement or you'll have your original troubles
        try
        {
          var serv = newScope.Resolve<Service>();
          // the serv instance now will have its own DataAccess
          // which will be diposed only when newScope is disposed
    
          // Execute the call in a async way.
          new Action<long, long>(serv.DoSomething)
            .BeginInvoke(ar => 
              {
                // finish the action if required
    
                // DO NOT FORGET to dispose the scope, or you'll have a memory leak
                newScope.Dispose();              
              }, null);
        }
        catch
        {
          // dispose the scope only if something goes wrong.
          // if the code succeeds, you need to dipose the scope in the callback
          newScope.Dispose();
          throw;
        }
    
        return customer;
      }
    
      // This is the method we want to execute async.
      public void DoSomething()
      {
        // Do something short or long running.
      }
    }
    

    我想应该有一种更优雅的方式,它不使用服务定位器模式(即访问ApplicationInstance)而只使用依赖注入。但我无法快速制作它。

    更新

    另一个选项是使用owned instances。以下是重写原始代码的方法:

    public class SomeController : Controller
    {
      private readonly DataAccess dataAccess;
      private readonly Func<Owned<DataAccess>> dataAccessFactory;
    
      public SomeController(DataAccess dataAccess, Func<Owned<DataAccess>> dataAccessFactory)
      {
        this.dataAccess = dataAccess;
        this.dataAccessFactory = dataAccessFactory;
      }
    
      public ActionResult Index(int id)
      {
        var model = new Model();
        model.Customer = this.dataAccess.Get(id);
    
        Owned<DataAccess> dataAccessForAsyncTaskHolder = null;
        try
        {
          dataAccessForAsyncTaskHolder = dataAccessFactory();
          dataAccessForAsyncTaskHolder.Value.ExecuteAsyncTask(() =>
            // you'll need a completion callback
            {
              // finish the task if required
    
              // dipose the owned instance
              dataAccessForAsyncTaskHolder.Dispose();
            });
        }
        catch
        {
          if (dataAccessForAsyncTaskHolder != null)
            dataAccessForAsyncTaskHolder.Dispose();
    
          throw;
        }
    
        return View(model);
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-01
      • 2012-05-03
      • 1970-01-01
      • 2016-10-05
      • 1970-01-01
      • 2011-01-06
      • 1970-01-01
      相关资源
      最近更新 更多