Ext*_*wat 7 c# asp.net entity-framework asp.net-core
这里有一点混乱.我不确定我是否正确地在整个WebApi中处理我的DbContext.我确实有一些控制器在我的数据库上执行某些操作(使用EF插入/更新),在执行这些操作后,我确实触发了一个事件.在我的EventArgs中(我有一个继承自的自定义类EventArgs)我传递了我DbContext,我在事件处理程序中使用它来记录这些操作(基本上我只记录经过身份验证的用户API请求).
在事件处理程序中,当我尝试提交我的更改(await SaveChangesAsync)时,我收到一个错误:"使用已处置的对象......等等"基本上注意到我第一次使用await我的async void(火灾和忘记)我通知调用者处置Dbcontext对象.
不使用async作品,我已经修改的唯一解决方法是通过获取EventArgs的SQLConnectionString传递DbContext来创建另一个DbContext实例.
在发布之前,我做了一个基于我的问题的小型研究, 实体框架在Web api/MVC中使用异步控制器进行处理
这就是我将参数传递给我的方法 OnRequestCompletedEvent
OnRequestCompleted(dbContext: dbContext,requestJson: JsonConvert.SerializeObject);
Run Code Online (Sandbox Code Playgroud)
这是OnRequestCompleted()声明
protected virtual void OnRequestCompleted(int typeOfQuery,PartnerFiscalNumberContext dbContext,string requestJson,string appId)
{
RequestCompleted?.Invoke(this,new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContext = dbContext,
RequestJson = requestJson,
AppId = appId
});
}
Run Code Online (Sandbox Code Playgroud)
这就是我处理和使用我的方式 dbContext
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = miningResultEventArgs.DbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery).Result;
var apiUserRequester = miningResultEventArgs.DbContext.ApiUsers.FirstAsync(x => x.AppId == appId).Result;
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
miningResultEventArgs.DbContext.ApiUserRequests.Add(apiRequest);
await miningResultEventArgs.DbContext.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)
通过使用SaveChanges而不是SaveChangesAsync一切工作.我唯一的想法是通过传递前一个DbContext的SQL连接字符串来创建另一个dbContext
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContext.Database.GetDbConnection().ConnectionString);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
Run Code Online (Sandbox Code Playgroud)
后面的代码摘录只是一个小测试来检查我的假设,基本上我应该传递SQL连接字符串而不是DbContext对象.
我不确定(在最佳实践方面)我是否应该传递一个连接字符串并创建一个新的dbContext对象(并通过使用一个using子句来处置它),或者我是否应该使用/拥有另一种心态来解决这个问题.
据我所知,使用DbContext应该针对一组有限的操作而不是出于多种目的.
编辑01
我将详细介绍下面我一直在做的事情.
我想我知道为什么会发生这种错误.
我有2个控制器一个接收JSON并且在反序列化之后我将JSON返回给调用者,另一个控制器获取JSON,该JSON封装了我以异步方式迭代的对象列表,返回Ok()状态.
控制器被声明为,async Task<IActionResult>并且都具有async2个类似方法的执行.
第一个返回JSON的方法执行此方法
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext);
Run Code Online (Sandbox Code Playgroud)
第二个(触发此错误的那个)
foreach (string t in requestFiscalBulkView.FiscalNoList)
await ProcessFiscalNo(t, dbContext);
Run Code Online (Sandbox Code Playgroud)
两种方法(之前定义的方法)都会启动一个事件.OnOperationComplete()
在该方法中,我从帖子的开头执行代码.在ProcessFiscalNo方法中,我不使用任何使用上下文,也不处理dbContext变量.在这个方法中,我只提交了2个主要操作,要么更新现有的sql行,要么插入它.对于编辑上下文,我选择行并使用修改后的标签标记行
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
Run Code Online (Sandbox Code Playgroud)
或插入行
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
Run Code Online (Sandbox Code Playgroud)
最后我执行了一个 await dbContext.SaveChangesAsync();
错误总是在EventHandler(一个详细的@开头的线程)中被触发,await dbContext.SaveChangedAsync()
这是非常奇怪的,因为之前的2行我等待用EF读取我的数据库.
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
dbContext.ApiUserRequests.Add(new ApiUserRequest() { ApiUser = apiUserRequester, RequestJson = requestJson, RequestType = requestType });
//this throws the error
await dbContext.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)
出于某种原因,在事件处理程序中调用await会通知调用者处置该DbContext对象.此外,通过重新创建DbContext而不是重新使用旧的,我看到访问的巨大改进.不知何故,当我使用第一个控制器并返回信息时,DbContext对象似乎被CLR标记为处理但由于某些未知原因它仍然起作用.
编辑02 对于随后的批量内容很抱歉,但我已经放置了所有使用dbContext的区域.
这就是我将dbContext传播给我请求它的所有控制器的方法.
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddMemoryCache();
// Add framework services.
services.AddOptions();
var connection = @"Server=.;Database=CrawlerSbDb;Trusted_Connection=True;";
services.AddDbContext<PartnerFiscalNumberContext>(options => options.UseSqlServer(connection));
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("PowerUser",
policy => policy.Requirements.Add(new UserRequirement(isPowerUser: true)));
});
services.TryAddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IAuthorizationHandler, UserTypeHandler>();
}
Run Code Online (Sandbox Code Playgroud)
在配置中我使用dbContext作为我的自定义MiddleWare
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
var context = app.ApplicationServices.GetService<PartnerFiscalNumberContext>();
app.UseHmacAuthentication(new HmacOptions(),context);
app.UseMvc();
}
Run Code Online (Sandbox Code Playgroud)
在自定义MiddleWare中,我只将它用于查询.
public HmacHandler(IHttpContextAccessor httpContextAccessor, IMemoryCache memoryCache, PartnerFiscalNumberContext partnerFiscalNumberContext)
{
_httpContextAccessor = httpContextAccessor;
_memoryCache = memoryCache;
_partnerFiscalNumberContext = partnerFiscalNumberContext;
AllowedApps.AddRange(
_partnerFiscalNumberContext.ApiUsers
.Where(x => x.Blocked == false)
.Where(x => !AllowedApps.ContainsKey(x.AppId))
.Select(x => new KeyValuePair<string, string>(x.AppId, x.ApiHash)));
}
Run Code Online (Sandbox Code Playgroud)
在我的控制器的CTOR中,我传递了dbContext
public FiscalNumberController(PartnerFiscalNumberContext partnerContext)
{
_partnerContext = partnerContext;
}
Run Code Online (Sandbox Code Playgroud)
这是我的帖子
[HttpPost]
[Produces("application/json", Type = typeof(PartnerFiscalNumber))]
[Consumes("application/json")]
public async Task<IActionResult> Post([FromBody]RequestFiscalView value)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var partnerFiscalNo = await _fiscalNoProcessor.ProcessFiscalNoSingle(value, _partnerContext);
}
Run Code Online (Sandbox Code Playgroud)
在ProcessFiscalNoSingle方法中,我有以下用法,如果那个伙伴存在,那么我会抓住他,如果没有,创建并返回他.
internal async Task<PartnerFiscalNumber> ProcessFiscalNoSingle(RequestFiscalView requestFiscalView, PartnerFiscalNumberContext dbContext)
{
var queriedFiscalNumber = await dbContext.FiscalNumbers.FirstOrDefaultAsync(x => x.FiscalNo == requestFiscalView.FiscalNo && requestFiscalView.ForceRefresh == false) ??
await ProcessFiscalNo(requestFiscalView.FiscalNo, dbContext, TypeOfQuery.Single);
OnRequestCompleted(typeOfQuery: (int)TypeOfQuery.Single, dbContextConnString: dbContext.Database.GetDbConnection().ConnectionString, requestJson: JsonConvert.SerializeObject(requestFiscalView), appId: requestFiscalView.RequesterAppId);
return queriedFiscalNumber;
}
Run Code Online (Sandbox Code Playgroud)
在代码中,ProcessFiscalNo我使用dbContext 的方法
var existingItem =
dbContext.FiscalNumbers.FirstOrDefault(x => x.FiscalNo == partnerFiscalNumber.FiscalNo);
if (existingItem != null)
{
var existingGuid = existingItem.Id;
partnerFiscalNumber = existingItem;
partnerFiscalNumber.Id = existingGuid;
partnerFiscalNumber.ChangeDate = DateTime.Now;
dbContext.Entry(partnerFiscalNumber).State = EntityState.Modified;
}
else
dbContext.FiscalNumbers.Add(partnerFiscalNumber);
//this gets always executed at the end of this method
await dbContext.SaveChangesAsync();
Run Code Online (Sandbox Code Playgroud)
此外,我有一个名为OnRequestCompleted()的事件,我传递了我的实际dbContext(如果我更新/创建它,最后会使用SaveChangesAsync())
我发起事件的方式是args.
RequestCompleted?.Invoke(this, new MiningResultEventArgs()
{
TypeOfQuery = typeOfQuery,
DbContextConnStr = dbContextConnString,
RequestJson = requestJson,
AppId = appId
});
Run Code Online (Sandbox Code Playgroud)
这是通知程序类(发生错误的地方)
internal class RequestNotifier : ISbMineCompletionNotify
{
public async void UploadRequestStatus(object source, MiningResultEventArgs miningResultArgs)
{
await RequestUploader(miningResultArgs);
}
/// <summary>
/// API Request Results to DB
/// </summary>
/// <param name="miningResultEventArgs">EventArgs type of a class that contains requester info (check MiningResultEventArgs class)</param>
/// <returns></returns>
private async Task RequestUploader(MiningResultEventArgs miningResultEventArgs)
{
//ToDo - fix the following bug : Not being able to re-use the initial DbContext (that's being used in the pipeline middleware and controller area),
//ToDo - basically I am forced by the bug to re-create the DbContext object
var dbOptions = new DbContextOptionsBuilder<PartnerFiscalNumberContext>();
dbOptions.UseSqlServer(miningResultEventArgs.DbContextConnStr);
using (var dbContext = new PartnerFiscalNumberContext(dbOptions.Options))
{
var appId = miningResultEventArgs.AppId;
var requestJson = miningResultEventArgs.RequestJson;
var typeOfQuery = miningResultEventArgs.TypeOfQuery;
var requestType = await dbContext.RequestType.FirstAsync(x => x.Id == typeOfQuery);
var apiUserRequester = await dbContext.ApiUsers.FirstAsync(x => x.AppId == appId);
var apiRequest = new ApiUserRequest()
{
ApiUser = apiUserRequester,
RequestJson = requestJson,
RequestType = requestType
};
dbContext.ApiUserRequests.Add(apiRequest);
await dbContext.SaveChangesAsync();
}
}
}
Run Code Online (Sandbox Code Playgroud)
不知何故,当dbContext到达事件处理程序时,CLR会收到处理dbContext对象的通知(因为我正在使用await?)而没有重新创建对象,当我想使用它时,我有很大的滞后.
在写这篇文章时我有一个想法,我确实将我的解决方案升级到1.1.0,我将尝试查看它是否表现相似.
关于你为何得到错误
正如@ set-fu DbContext的评论所指出的那样,它不是线程安全的.
除此之外,由于DbContext 没有明确的生命周期管理,因此当垃圾收集器认为合适时,DbContext将被释放.
从您的上下文来看,以及您对Request scoped DbContext的提及, 我想您在控制器的构造函数中DI你的DbContext.因为您的DbContext是请求范围,所以一旦您的请求结束,它就会被处理掉,
但由于您已经解雇并忘记了OnRequestCompleted事件,因此无法保证不会丢弃您的DbContext.
从那以后,我们的一个方法成功而另一个失败的事实我认为是先见" 运气 ".一种方法可能比另一种方法更快,并在垃圾收集器处理DbContext 之前完成.
您可以做的是更改事件的返回类型
async void
Run Code Online (Sandbox Code Playgroud)
至
async Task<T>
Run Code Online (Sandbox Code Playgroud)
这样,您就可以等待你的RequestCompleted 任务控制器内完成,这将保证你,直到你的RequestCompleted任务完成后你的控制器/的DbContext不会得到弃置.
关于 正确处理DbContexts
微软有两个相互矛盾的建议,许多人以完全不同的方式使用DbContexts.
那些相互矛盾,因为如果你的请求与Db的东西做了很多无关,那么你的DbContext就会无缘无故地保留下来.因此,当您的请求只是等待随机的东西完成时,保持您的DbContext活着是浪费...
许多遵循规则1的人在他们的"存储库模式"中都有他们的DbContexts,并为每个数据库查询创建一个新的实例
public User GetUser(int id)
{
User usr = null;
using (Context db = new Context())
{
usr = db.Users.Find(id);
}
return usr;
}
Run Code Online (Sandbox Code Playgroud)
他们只是尽快获取数据并处理上下文.许多人认为这是可以接受的做法.虽然这有利于在最短的时间内占用您的数据库资源,但它明显牺牲了EF提供的所有UnitOfWork和"缓存"糖果.
因此,微软建议每个请求使用1 Db Context,这显然是基于UnitOfWork在1个请求范围内的事实.
但在许多情况下,我相信你的情况也不是这样.我认为记录一个单独的UnitOfWork,因此为你的Post-Request Logging提供一个新的DbContext是完全可以接受的(这也是我也使用的做法).
我的项目中的一个示例我在3个工作单元的请求中有3个DbContexts.
| 归档时间: |
|
| 查看次数: |
2962 次 |
| 最近记录: |