ObjectDisposedException при возврате AsAsyncEnumerable () - PullRequest
0 голосов
/ 17 марта 2020

В моем. NET Core 3 WebAPI проекте у меня есть следующий простой вызов метода:

[HttpGet("ViewerRoles")]
public IAsyncEnumerable<ViewerRole> GetViewList() {
    using var db = new MpaContext();

    return db.ViewerRoles.AsAsyncEnumerable();
}

Это вызывает исключение ObjectDisposedException. AsAsyncEnumerable() является относительно новым, и я не могу найти подходящих примеров того, как использовать его в таких ситуациях. Стоит ли просто удалить ключевое слово using, и соединение с базой данных Entity Framework волшебным образом удаляется? Или есть еще один трюк?

Ответы [ 5 ]

1 голос
/ 18 марта 2020

Я вижу, что все ответы имеют свою точку зрения, и все они верны. Поэтому я могу лишь дать некоторые разъяснения по ним.

Вам следует выбрать момент утилизации объекта в зависимости от того, как вы его выставили. Например, ваш исходный код неявно предоставляет MpaContext db для конвейера As pNet, и вы не можете распоряжаться db, пока netcore не выполнит свою работу с ним. Таким образом, вы можете зарегистрировать утилизацию на Response.RegisterForDispose(), как вы упомянули. Но это необычно, потому что у вас обычно нет доступа к Response - вы можете сделать это только внутри Контроллера или если вы поделитесь им с зависимостями Контроллера, но это увеличит сложность кода.

Вот почему вы можно избежать этого, полагаясь на срок службы контроллера. Поскольку он находится в объеме запроса, он будет действовать до отправки ответа. Таким образом, вы можете создать свой db как зависимость контроллера и сохранить его в свойстве. Также вы должны реализовать IDisposable на контроллере.

public class RoleController : IDisposable
{
    private MpaContext DbContext { get; }

    public RoleController()
    {
        DbContext = new MpaContext();
    }

    [HttpGet( "ViewerRoles" )]
    public IAsyncEnumerable<ViewerRole> GetViewList()
    {
        return DbContext.ViewerRoles.AsAsyncEnumerable();
    }

    public void Dispose()
    {
        DbContext.Dispose();
    }
}

В этом случае вы можете придерживаться этого паттерна, даже если вы переместите свои логи c в какой-то другой класс (как это должно быть, я верить). Но все же, если вы вручную создаете одноразовые предметы, вам следует позаботиться об их утилизации. Это тот момент, когда DI приходит на помощь.

Используя DI, вы можете забыть об удалении объектов, созданных DI. DI вызовет Dispose() для любой зависимости, когда закончится ее жизненный цикл. Зарегистрируйте свой MpaContext, позвонив по номеру AddDbContextPool<MpaContext>() или AddDbContext<MpaContext>(), если вы используете EntityFramework в MpaContext. При таком подходе вы получите чистый код вашего контроллера.

public class RoleController
{
    private MpaContext DbContext { get; }

    public RoleController( MpaContext dbContext )
    {
        DbContext = dbContext;
    }

    [HttpGet( "ViewerRoles" )]
    public IAsyncEnumerable<ViewerRole> GetViewList()
    {
        return DbContext.ViewerRoles.AsAsyncEnumerable();
    }
}

Если вы не хотите выставлять MpaContext для контроллера и хотите создать его вручную внутри GetViewList(), вы все равно можете перечислить результат в методе действия и распоряжаться контекстом, как ответил Теодор Зулиас. Но зачем, если вы можете просто поручить эту работу DI.

1 голос
/ 18 марта 2020

Я также нашел подход к использованию метода Response.RegisterForDispose(). Но я до сих пор не знаю, какой подход является наиболее перспективным.

[HttpGet("ViewerRoles")]
public IAsyncEnumerable<ViewerRole> GetViewList() {
    MpaContext db = new MpaContext();

    Response.RegisterForDispose(db);

    return db.ViewerRoles.AsAsyncEnumerable();
}

1 голос
/ 17 марта 2020

Вы должны реализовать IDisposable на вашем контроллере и использовать DbContext в методе Dispose контроллера

1 голос
/ 18 марта 2020

У вас есть два варианта. Либо перечислите IAsyncEnumerable внутри вашего GetViewList метода:

[HttpGet("ViewerRoles")]
public async IAsyncEnumerable<ViewerRole> GetViewList()
{
    using var db = new MpaContext();
    await foreach (var item in db.ViewerRoles.AsAsyncEnumerable().ConfigureAwait(false))
    {
        yield return item;
    }
}

... или установите пакет System.Interactive.Async и используйте метод stati c AsyncEnumerableEx.Using:

[HttpGet("ViewerRoles")]
public IAsyncEnumerable<ViewerRole> GetViewList()
{
    return AsyncEnumerableEx.Using(() => new MpaContext(),
        db => db.ViewerRoles.AsAsyncEnumerable());
}

Вот подпись метода AsyncEnumerableEx.Using:

public static IAsyncEnumerable<TSource> Using<TSource, TResource>(
    Func<TResource> resourceFactory,
    Func<TResource, IAsyncEnumerable<TSource>> enumerableFactory)
    where TResource : IDisposable;

К сожалению, похоже, что для этой библиотеки нет онлайн-документации.

1 голос
/ 17 марта 2020
В этом случае оператор

using ограничивает ваш dbContext областью действия функции, поэтому правильным способом будет перечисление перед возвратом из действия, в противном случае вы возвращаете что-то, что не может быть правильно оценено позже (после того, как функция вернулась и контекст был удален)

в качестве альтернативы, вы можете переместить создание dbContext в область контроллера, которая может быть областью запроса (это не то, что hart для реализации через DI фреймворка, и DI позаботится обо всем с помощью IDisposable и сможет запросить «магически»)

...