WebApi 中的单例 属性 注入 class
Singleton property inside WebApi injected class
我有一个 Web Api 控制器。控制器有一个需要给定接口的构造函数。接口通过依赖注入 (DI) 注入。
public class MyController : ApiController
{
private readonly IMyManager _myManager;
public FileController(IMyManager myManager)
{
_myManager = myManager
}
}
注入的接口 (IMyManager) 实现了使用 Entity Framework 存储库的方法。有一些方法可以不断访问永不更改的 table。由于控制器使用频繁,我注意到由于这些对数据库的连续调用以及 materializing/disposing 对象的成本,它正在生成大量内存流量。上下文 (ISomeContext) 也是通过 DI 创建的。
我创建了只实例化一次的静态私有成员,这种方法工作正常,但是,由于它是一个多线程进程,我需要锁定对象以确保只生成一次实例。通过这种方法,我摆脱了那些 DB 和 materializing/disposing 成本以及锁等待时间的损失。
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();
}
}
}
}
你有什么想法可以在没有锁的情况下实现与之前代码相同的结果吗?我在考虑一个懒惰的实现,但是我有点迷路了,因为存储库是非静态的。
提前致谢,
卡洛斯
轻型锁定
您可以通过使用 ReaderWriterLockSlim to differentiate a read lock from a write lock to your cache. I based one of my designs on the design from 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;
}
}
配置上下文
由于您没有报告内存消耗以外的任何性能问题,因此您可能没有清理 Entity Framework 上下文,这样做会释放内存。 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()
正确释放 Entity Framework 上下文。请注意,控制器始终在单个请求的范围内实例化和处理。
或者,您始终可以确保您的 Entity Framework 上下文在 using 语句中使用(在您的 IMyManager 中)。
using (var context = new MyEFContext())
{
// Run your db action here
}
请参阅 this answer 了解一些其他可能的替代方案。
我听从了 Matt 的建议,将责任委托给 DI 容器 (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) 有两种选择,通过构造函数注入 class 或 属性。在我的例子中,我使用了 属性 注入,并为引用主管理器的控制器自动装配了属性。两种方法都有效。代码如下所示:
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 是 public,但是它有一个静态构造函数,它只在第一次实例化静态类型。实例的创建被委托给 Autofac,因此通过指定 "Singleton" 保证实例只创建一次并且是线程安全的。在我的例子中,控制器在每个 request/per 生命周期范围内创建实例,因此对于每个控制器都有一个 DataManager 实例,它将在实例之间共享。
注意:这不是完整的代码,但我测试了它并且工作正常。
我有一个 Web Api 控制器。控制器有一个需要给定接口的构造函数。接口通过依赖注入 (DI) 注入。
public class MyController : ApiController
{
private readonly IMyManager _myManager;
public FileController(IMyManager myManager)
{
_myManager = myManager
}
}
注入的接口 (IMyManager) 实现了使用 Entity Framework 存储库的方法。有一些方法可以不断访问永不更改的 table。由于控制器使用频繁,我注意到由于这些对数据库的连续调用以及 materializing/disposing 对象的成本,它正在生成大量内存流量。上下文 (ISomeContext) 也是通过 DI 创建的。
我创建了只实例化一次的静态私有成员,这种方法工作正常,但是,由于它是一个多线程进程,我需要锁定对象以确保只生成一次实例。通过这种方法,我摆脱了那些 DB 和 materializing/disposing 成本以及锁等待时间的损失。
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();
}
}
}
}
你有什么想法可以在没有锁的情况下实现与之前代码相同的结果吗?我在考虑一个懒惰的实现,但是我有点迷路了,因为存储库是非静态的。
提前致谢,
卡洛斯
轻型锁定
您可以通过使用 ReaderWriterLockSlim to differentiate a read lock from a write lock to your cache. I based one of my designs on the design from 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;
}
}
配置上下文
由于您没有报告内存消耗以外的任何性能问题,因此您可能没有清理 Entity Framework 上下文,这样做会释放内存。 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()
正确释放 Entity Framework 上下文。请注意,控制器始终在单个请求的范围内实例化和处理。
或者,您始终可以确保您的 Entity Framework 上下文在 using 语句中使用(在您的 IMyManager 中)。
using (var context = new MyEFContext())
{
// Run your db action here
}
请参阅 this answer 了解一些其他可能的替代方案。
我听从了 Matt 的建议,将责任委托给 DI 容器 (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) 有两种选择,通过构造函数注入 class 或 属性。在我的例子中,我使用了 属性 注入,并为引用主管理器的控制器自动装配了属性。两种方法都有效。代码如下所示:
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 是 public,但是它有一个静态构造函数,它只在第一次实例化静态类型。实例的创建被委托给 Autofac,因此通过指定 "Singleton" 保证实例只创建一次并且是线程安全的。在我的例子中,控制器在每个 request/per 生命周期范围内创建实例,因此对于每个控制器都有一个 DataManager 实例,它将在实例之间共享。
注意:这不是完整的代码,但我测试了它并且工作正常。