简体   繁体   English

在ASP.NET Core中流式处理IEnumerable

[英]Streaming and disposing an IEnumerable in ASP.NET core

I am trying to implement a controller in ASP.NET core that returns a potentially huge amount of data queried from an SQL database. 我正在尝试在ASP.NET核心中实现一个控制器,该控制器返回从SQL数据库查询的潜在大量数据。 To achieve this I am executing the query via Dapper's Query() method with the buffered: false parameter. 为此,我通过Dapper的Query()方法使用buffered:false参数执行查询。 This means the SqlConnection object must be disposed after the query results have been enumerated. 这意味着在枚举查询结果之后必须处置SqlConnection对象。

There does not seem to be an easy way to achieve this. 似乎没有一个简单的方法来实现这一目标。 I have got it working by writing a class that wraps the IEnumerable, and reference counts any Enumerators it is asked to create. 我通过编写一个包装IEnumerable的类使它起作用,并且引用计数了要求创建的任何Enumerator。 When all Enumerators are disposed it disposes of the database connection. 处置所有枚举器后,它将处置数据库连接。

This seems to work but I'm worried there are cases where the database connection wouldn't be disposed (eg if the request is cancelled before it starts enumerating the query results). 这似乎可行,但是我担心在某些情况下无法处理数据库连接(例如,如果请求在开始枚举查询结果之前被取消了)。 Is there a better approach, short of writing to a temporary file and streaming that to the client? 是否有更好的方法,只需写入临时文件并将其流式传输到客户端?

This is what I came up with in the end. 这就是我最后想出的。 The lifetime of the connection is owned by the StreamedDatabaseObjectResult which is returned in place of the usual ObjectResult. 连接的生存期由StreamedDatabaseObjectResult拥有,它代替通常的ObjectResult返回。 The repository class returns the connection to dispose along with the results. 存储库类返回要处置的连接以及结果。 This seems to cover all cases and plays nicely with async methods. 这似乎涵盖了所有情况,并且可以很好地与异步方法配合使用。

public class StreamedDatabaseObjectResult : ActionResult
{
    private readonly Func<Task<DisposableEnumerable>> _getValuesFunc;

    public StreamedDatabaseObjectResult(Func<Task<DisposableEnumerable>> getValuesFunc)
    {
        _getValuesFunc = getValuesFunc;
    }

    public override async Task ExecuteResultAsync(ActionContext context)
    {
        using (var de = await _getValuesFunc())
        {
            var objectResult = new ObjectResult(de.Values);
            await objectResult.ExecuteResultAsync(context);
        }
    }
}

public class DisposableEnumerable : IDisposable
{
    public IEnumerable Values { get; }
    private readonly IDisposable _disposable;

    public DisposableEnumerable(IDisposable disposable, IEnumerable values)
    {
        Values = values;
        _disposable = disposable;
    }

    /// <inheritdoc />
    public void Dispose()
    {
        _disposable.Dispose();
    }
}

[HttpGet]
[ProducesResponseType(200)]
public ActionResult<IEnumerable<MyClass>> GetThings(CancellationToken cancellationToken)
{
    return new StreamedDatabaseObjectResult(() => _repo.GetThings(cancellationToken));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM