WebApi注入类中的Singleton属性

时间:2015-02-12 16:40:16

标签: c# multithreading entity-framework asp.net-web-api dependency-injection


我有一个Web Api控制器。控制器有一个构造函数,它需要一个给定的接口。接口通过依赖注入(DI)注入。

public class MyController : ApiController
{
    private readonly IMyManager _myManager;

    public FileController(IMyManager myManager)
    {
        _myManager = myManager
    }
}

注入的接口(IMyManager)实现使用Entity Framework存储库的方法。有一些方法可以不断访问永不改变的表。由于控制器使用频繁,我注意到由于这些连续调用DB而产生的大量内存流量以及实现/处理对象的成本。上下文(ISomeContext)也是通过DI创建的。

我创建了仅实例化一次的静态私有成员,但这种方法工作正常,因为它是一个多线程进程,我需要锁定对象以确保实例只生成一次。通过这种方法,我摆脱了那些数据库和实现/处置​​成本,并且锁定等待时间的惩罚。

public class MyManager : IMyManager
{
    protected ISomeContext _someContext;

    private static IList<MyPOCO> _myTableList;
    private static readonly object MyTableListLock = new object();

    public FileUploadManager(ISomeContext someContext)
    {
        _someContext = someContext;

        //I want to avoid using this lock... A lazy implementation perhaps?
        lock (MyTableListLock)
        {
            if (_myTableList == null)
            {
                _myTableList = _someContext.MyPOCO.ToList();
            }
        }
    }
}

您是否有任何想法如何在没有锁的情况下实现上一代码的相同结果?我正在考虑一个懒惰的实现,但是由于存储库是非静态的,我有点迷失。

提前致谢,
卡洛斯

2 个答案:

答案 0 :(得分:1)

轻量级锁定

通过使用ReaderWriterLockSlim将读锁定与写锁定区分到缓存,可以获得更好的多线程吞吐量。我根据one of my designs设计this article,将ReaderWriterLockSlim与惰性锁模式结合起来。

private ReaderWriterLockSlim synclock = new ReaderWriterLockSlim(LockRecursionPolicy.NoRecursion);

public T GetOrAdd(string key, Func<T> loadFunction)
{
    LazyLock lazy;
    bool success;

    synclock.EnterReadLock();
    try
    {
        success = this.cacheProvider.TryGetValue(key, out lazy);
    }
    finally
    {
        synclock.ExitReadLock();
    }

    if (!success)
    {
        synclock.EnterWriteLock();
        try
        {
            if (!this.cacheProvider.TryGetValue(key, out lazy))
            {
                lazy = new LazyLock();
                this.cacheProvider.Add(key, lazy);
            }
        }
        finally
        {
            synclock.ExitWriteLock();
        }
    }

    return lazy.Get(loadFunction);
}

private sealed class LazyLock
{
    private volatile bool got;
    private object value;

    public TValue Get<TValue>(Func<TValue> activator)
    {
        if (!got)
        {
            if (activator == null)
            {
                return default(TValue);
            }

            lock (this)
            {
                if (!got)
                {
                    value = activator();

                    got = true;
                }
            }
        }

        return (TValue)value;
    }
}

处置上下文

由于您没有报告除内存消耗之外的任何性能问题,因此很可能您没有清理实体框架上下文,这样做会释放内存。 WebApi有一个机制。覆盖控制器中的Dispose()方法。

public class MyController : ApiController
{
    private readonly IMyManager _myManager;

    public FileController(IMyManager myManager)
    {
        _myManager = myManager
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            _myManager.Dispose();
        }
        base.Dispose(disposing);
    }
}

当然,您需要确保您的经理设置为通过调用Dispose()来正确释放实体框架上下文。请注意,控制器始终被实例化并处理在单个请求的范围内。

或者,您始终可以确保在using语句(在IMyManager中)中使用您的Entity Framework上下文。

using (var context = new MyEFContext())
{
    // Run your db action here
}

有关其他可能的替代方案,请参阅this answer

答案 1 :(得分:1)

我遵循了Matt关于将责任委托给DI Container(Autofac)的建议。我正在分享用于解决锁定问题的代码片段。

1)我创建了一个处理静态数据的独立管理器,并由其他管理器在内部引用,它看起来像这样:

public sealed class DataManager : IDataManager
{
    static DataManager()
    {
        using (var context = new MyDataContext())
        {
            _queryableTable1 = context.StaticTable1.AsNoTracking()
                                      .ToList()
                                      .AsQueryable();
        }
    }

    private static readonly IQueryable<StaticTable1> _queryableTable1;
    public IQueryable<StaticTable1> QueryableTable1 
    {
        get { return _queryableTable1; } 
    }
}

界面如下所示:

public interface IDataManager
{
    IQueryable<StaticTable1> QueryableTable1 { get; }
}

2)有两个选项,通过构造函数或属性注入类。在我的例子中,我使用了属性注入,其属性为引用主管理器的控制器自动装配。两种方法都有效。代码如下所示:

var builder = new ContainerBuilder();

builder.RegisterType<DataManager>()
       .As<IDataManager>()
       .SingleInstance();

//The rest of the container registration go here...

var container = builder.Build();
GlobalConfiguration.Configuration.DependencyResolver = new AutofacWebApiDependencyResolver(container);

3)主要经理看起来像这样:

public class MainManager : IMainManager
{
    //Option A: Property injection with public property
    public IDataManager DataManager { get; set; }

    //Option B: Constructor injection with private member
    private IDataManager _dataManager;
    public MainManager (IDataManager dataManager)
    {
        _dataManager = dataManager;
    }

    //This method shows how to consume the DataManager.
    public void MyMethod()
    {
        var row = DataManager.QueryableTable1.FirstOrDefault();
        //Some more logic....
    }
}

4)在属性注入的情况下,可以选择将属性添加到界面,如下所示:

public interface IMyManager
{
    //This is optional.
    IDataManager DataManager { get; set; }

    //This method shows how to consume the DataManager.
    public void MyMethod();
}

DataManager是公共的,但是它有一个静态构造函数,它只在第一次实例化静态类型。实例的创建被委托给Autofac,因此可以保证实例只创建一次,并且通过指定&#34; Singleton&#34;来保证线程安全。在我的情况下,控制器为每个请求/每个生命周期范围创建实例,因此对于每个控制器,都有一个将在实例之间共享的DataManager实例。

注意:这不是完整的代码,但我测试了它并且工作正常。