0

我有一个托管服务,它通过 API 端点处理对象 PUT/POST,也就是说,一旦给出新实体或编辑现有实体,(a)托管服务开始处理它(一个长时间运行的过程), (b) 接收/修改的对象(作为 JSON 对象)返回给 API 调用者。

当 PUT/POST 一个实体时,我在这里和那里看到运行时错误(例如,在对象 JSON 序列化程序中)抱怨不同的问题,例如:

ObjectDisposedException:无法访问已处置的对象。此错误的一个常见原因是释放从依赖注入中解析的上下文,然后尝试在应用程序的其他地方使用相同的上下文实例。如果您在上下文上调用 Dispose() 或将上下文包装在 using 语句中,则可能会发生这种情况。如果你使用依赖注入,你应该让依赖注入容器负责处理上下文实例。

或者:

InvalidOperationException:在前一个操作完成之前在此上下文上启动了第二个操作。这通常是由使用相同 DbContext 实例的不同线程引起的。

最初我使用的是数据库上下文池,但据此,似乎池在托管服务方面存在已知问题。因此,我切换到了常规AddDbContext;但是,这都没有解决问题。

这就是我定义数据库上下文和托管服务的方式:

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCustomDbContext(Configuration);

        // This is the hosted service:
        services.AddHostedService<MyHostedService>();
    }
}

public static class CustomExtensionMethods
{
    public static IServiceCollection AddCustomDbContext(
        this IServiceCollection services,
        IConfiguration configuration)
    {
        services.AddDbContext<MyContext>(
            options =>
            {
                options
                .UseLazyLoadingProxies(true)
                .UseSqlServer(
                    configuration.GetConnectionString("DefaultConnection"),
                    sqlServerOptionsAction: sqlOptions => { sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name); });
            });

        return services;
    }
}

并且我按以下方式访问托管服务中的数据库上下文(如此处推荐):

using(var scope = Services.CreateScope())
{
    var context = scope.ServiceProvider.GetRequiredService<MyContext>();
}

编辑 1

如前所述,错误发生在代码周围。但是,由于我提到了序列化程序上发生的错误,因此我将在以下内容中共享序列化程序代码:

public class MyJsonConverter : JsonConverter
{
    private readonly Dictionary<string, string> _propertyMappings;

    public MyJsonConverter()
    {
        _propertyMappings = new Dictionary<string, string>
        {
            {"id", nameof(MyType.ID)},
            {"name", nameof(MyType.Name)}
        };
    }

    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        JObject obj = new JObject();
        Type type = value.GetType();

        foreach (PropertyInfo prop in type.GetProperties())
        {
            if (prop.CanRead)
            {
                // The above linked errors happen here. 
                object propVal = prop.GetValue(value, null);
                if (propVal != null)
                    obj.Add(prop.Name, JToken.FromObject(propVal, serializer));
            }
        }

        obj.WriteTo(writer);
    }
}

更新 2

示例 API 端点如下所示:

[Route("api/v1/[controller]")]
[ApiController]
public class MyTypeController : ControllerBase
{
    private readonly MyContext _context;
    private MyHostedService _service;

    public MyTypeController (
        MyContext context,
        MyHostedService service)
    {
        _context = context;
        _service = service
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<IEnumerable<MyType>>> GetMyType(int id)
    {
        return await _context.MyTypes.FindAsync(id);
    }

    [HttpPost]
    public async Task<ActionResult<MyType>> PostMyType(MyType myType)
    {
        myType.Status = State.Queued;
        _context.MyTypes.Add(myType);
        _context.MyTypes.SaveChangesAsync().ConfigureAwait(false);

        // the object is queued in the hosted service for execution.
        _service.Enqueue(myType);

        return CreatedAtAction("GetMyType", new { id = myType.ID }, myType);
    }
}
4

1 回答 1

1

以下几行最有可能导致ObjectDisposedException错误:

return await _context.MyTypes.FindAsync(id);

return CreatedAtAction("GetMyType", new { id = myType.ID }, myType);

这是因为您依赖于这个变量:

private readonly MyContext _context;

由于对象myType已附加到该上下文。

正如我之前提到的,发送上下文实体进行序列化不是一个好主意,因为当序列化程序有机会触发时,上下文可能已经被释放。改用模型(意味着Models文件夹中的类)并将所有相关属性从您的真实实体映射到它。例如,您可以创建一个名为的类,该类MyTypeViewModel仅包含您需要返回的属性:

public class MyTypeViewModel
{
    public MyTypeViewModel(MyType obj)
    {
        Map(obj);
    }

    public int ID { get; set; }

    private void Map(MyType obj)
    {
        this.ID = obj.ID;
    }
}

然后不要返回实体,而是使用视图模型:

var model = new MyTypeViewModel(myType);
return CreatedAtAction("GetMyType", new { id = myType.ID }, model);

至于InvalidOperationException,我有根据的猜测是,由于您没有等待该SaveChangesAsync方法,因此序列化程序正在触发,而原始操作仍在进行中,从而对上下文造成双重打击,从而导致错误。

使用awaitonSaveChangesAsync方法应该可以解决这个问题,但您仍然需要停止发送延迟加载的实体以进行序列化。

经过进一步审查,服务本身也可能会导致问题,因为您将其传递给对象的引用myType

_service.Enqueue(myType);

如果服务对对象执行某些操作导致调用现在处理的上下文,或者同时其他异步部分(例如序列化)尝试延迟加载内容,则可能会出现相同的两个问题。

于 2020-01-28T21:09:56.930 回答