我今天更新了一个项目到ASP.NET Core 2,我收到以下错误:
不能从singleton IActiveUsersService消耗范围服务IMongoDbContext
我有以下注册:
services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddScoped<IMongoDbContext, MongoDbContext>();
services.AddSingleton(option =>
{
var client = new MongoClient(MongoConnectionString.Settings);
return client.GetDatabase(MongoConnectionString.Database);
})
public class MongoDbContext : IMongoDbContext
{
private readonly IMongoDatabase _database;
public MongoDbContext(IMongoDatabase database)
{
_database = database;
}
public IMongoCollection<T> GetCollection<T>() where T : Entity, new()
{
return _database.GetCollection<T>(new T().CollectionName);
}
}
public class IActiveUsersService: ActiveUsersService
{
public IActiveUsersService(IMongoDbContext mongoDbContext)
{
...
}
}
为什么DI无法使用该服务?一切都适用于ASP.NET Core 1.1。
答案 0 :(得分:39)
您无法使用寿命较短的服务。每个请求只存在范围内的服务,而单例服务只创建一次并且实例是共享的。
现在,应用中只存在IActiveUsersService
的一个实例。但它希望依赖于MongoDbContext
,它是Scoped,并且是按请求创建的。
您必须:
MongoDbContext
成为单身人士,或IActiveUsersService
Scoped,或MongoDbContext
作为函数参数传递给用户服务答案 1 :(得分:11)
Scoped和Singleton服务之间存在重要差异。警告可以解决这个问题,并将其关闭或不加选择地切换生命周期以使其消失将无法解决问题
范围服务是从IServiceScope
创建的。其最重要的目的之一是确保在范围本身时正确处理在该范围内创建的任何IDisposable
服务。
在ASP.NET Core中,会在每个传入请求中自动为您创建服务范围,因此您通常不必担心这一点。但是,您也可以创建自己的服务范围;你只需要自己处理它。
这样做的一种方法是:
IDisposable
,IServiceProvider
,IServiceProvider.CreateScope()
扩展程序IServiceScope
范围
Dispose
方法中。services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddScoped<IMongoDbContext, MongoDbContext>();
services.AddSingleton(option =>
{
var client = new MongoClient(MongoConnectionString.Settings);
return client.GetDatabase(MongoConnectionString.Database);
})
public class MongoDbContext : IMongoDbContext
{
private readonly IMongoDatabase _database;
public MongoDbContext(IMongoDatabase database)
{
_database = database;
}
public IMongoCollection<T> GetCollection<T>() where T : Entity, new()
{
return _database.GetCollection<T>(new T().CollectionName);
}
}
public class ActiveUsersService: IActiveUsersService, IDisposable
{
private readonly IServiceScope _scope;
public ActiveUsersService(IServiceProvider services)
{
_scope = services.CreateScope(); // CreateScope is in Microsoft.Extensions.DependencyInjection
}
public IEnumerable<Foo> GetFooData()
{
using (var context = _scope.ServiceProvider.GetRequiredService<IMongoDbContext>())
{
return context.GetCollection<Foo>();
}
}
public void Dispose()
{
_scope?.Dispose();
}
}
根据您使用这些以及您正在使用的范围服务的方式,您可以改为执行以下操作之一:
IServiceProvider
的引用,每次需要作用域服务时,使用它在IServiceScope
块内创建一个新的using
,让范围得到在街区退出时处理。请记住,从IDisposable
创建的任何IServiceScope
服务都会在范围本身时自动处理。
简而言之,不要只是改变服务的生命周期来“让它工作”;你还需要考虑那些并确保它们得到妥善处理。 ASP.NET Core自动处理最常见的情况;对于其他人,你只需要做更多的工作。
自从C#1.0以来,我们已经有using()
块来确保正确处理资源。但是当其他东西(DI服务)正在为您创建这些资源时,using()
块不起作用。这就是Scoped服务的用武之地,错误地使用它们会导致程序中的资源泄漏。
答案 2 :(得分:3)
您也可以添加
.UseDefaultServiceProvider(options =>
options.ValidateScopes = false)
在.Build()
文件中的Program.cs
之前以禁用验证。
尝试仅用于开发测试,ActiveUsersService是单例,并且具有比MongoDbContext更大的生命周期,MongoDbContext是作用域的,不会被处理掉。
答案 3 :(得分:1)
还有另一种解决此问题的方法,就是将MongoDbContext
添加到DI AddTransient
,如下所示:
services.AddSingleton<IActiveUsersService, ActiveUsersService>();
services.AddTransient<IMongoDbContext, MongoDbContext>();
使用此方法的含义是,对于您使用它的每个MongoDbContext
类,最终会得到Singleton
的实例。
例如,如果您使用MongoDbContext
有10个Singleton类,则它将有10个实例,但它不是为每个请求创建实例。
请参阅此参考:Cannot Consume Scoped Service From Singleton – A Lesson In ASP.net Core DI Scopes