Проблемы с удалением DbContext после нескольких обращений в сервис - PullRequest
0 голосов
/ 19 июня 2019

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

Фактическая ошибка, которую я получаю: «Не удается получить доступ к удаленному объекту».

Я знаю, что я могу вытащить код взаимодействия и создания контекста БД из сервиса и в метод контроллера здесь (это упрощенный пример), но мне нужно будет использовать больше сервисов в других частях приложения и столкнулся с проблема там также, поэтому я хотел бы попытаться определить причину моей проблемы в этом примере, чтобы я мог применить исправление в другом месте.

Здесь приведены упрощенные классы.

public class UserController : Controller
    {
        private readonly IUserService userService;

        public UserController(IUserService userService)
        {
            this.userService = userService;
        }

        [HttpPost]
        [ActionName("PostUserDetails")]
        public async Task<IActionResult> PostUserDetails([FromBody]UserDetailsContract userDetailsContract)
        {
            // this call is fine
            var user = await userService.GetUserByCode(userDetailsContract.Code);

            if (user == null)
            {
                return BadRequest("User not found");
            }

            // this call fails with the object disposed error 
            var userDetails = await userService.GetUserDetailsByCode(userDetailsContract.Code);

            if (userDetails != null)
            {
                return BadRequest("UserDetails already exists");
            }

            // .. go on to save new entity

            return Ok();
        }
    }
public class UserService : IUserService
{
    private readonly IDatabaseFactory databaseFactory;

    public UserService(IDatabaseFactory databaseFactory)
    {
        this.databaseFactory = databaseFactory;
    }

    public async Task<User> GetUserByCode(string code)
    {
        using (var db = databaseFactory.Create())
        {
            return await db.Users.GetByCode(code);
        }
    }

    public async Task<IEnumerable<UserDetail>> GetUserDetailsByCode(string code)
    {
        using (var db = databaseFactory.Create())
        {
            return await db.UserDetails.GetByCode(code);
        }
    }   
}
public class ApiDbContext : DbContext, IApiDbContext
{
    public DbSet<User> Users { get; set; }

    public DbSet<UserDetail> UserDetails { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=192.168.1.1;Database=dbname;User Id=user; Password=pwd; MultipleActiveResultSets=True;");
    }
}
public class DatabaseFactory : IDatabaseFactory
{
    public IApiDatabase Create()
    {
        return new ApiDatabase(new ApiDbContext());
    }
}
public class ApiDatabase : RepositoriesBase, IApiDatabase
{
    private IUserRepository userRepository;
    private IUserDetailsRepository userDetailsRepository;

    public ApiDatabase(ApiDbContext context) : base(context)
    {
    }

    public IUserRepository Users => userRepository ?? (userRepository = new UserRepository(context));

    public IUserDetailsRepository UserExchange => userDetailsRepository ?? (userDetailsRepository = new UserDetailsRepository(context));
}
public abstract class RepositoriesBase : IRepositories
{
    internal readonly ApiDbContext context;

    private bool isDisposing;

    protected RepositoriesBase(ApiDbContext context)
    {
    }

    public void Dispose()
    {
        if (!isDisposing)
        {
            isDisposing = true;
            context?.Dispose();
        }
    }

    public Task SaveChanges() => context.SaveChangesAsync();
}

public class UserRepository : Repository<User>, IUserRepository
{
    public UserRepository(ApiDbContext context) : base(context)
    {
    }

    public async Task<User> GetByCode(string code)
    {
        return Filter(x => x.code == code).Result.FirstOrDefault();
    }
}
public class UserDetailsRepository : Repository<UserDetail>, IUserDetailRepository
{
    public UserExchangeRepository(ApiDbContext context) : base(context)
    {
    }

    public async Task<IEnumerable<UserDetail>> GetByUserId(int userId)
    {
        return await Filter(x => x.UserId == userId);
    }
}
public class Repository<T> : IRepository<T> where T : class, IEntity
{
    private readonly ApiDbContext context;

    public Repository(ApiDbContext context) => this.context = context;

    public async Task Add(T entity)
    {
        context.Set<T>().Add(entity);
    }

    public async Task Add(IEnumerable<T> entities)
    {
        foreach (var entity in entities)
        {
            context.Set<T>().Add(entity);
        }
    }

    public async Task Delete(T entity)
    {
        context.Set<T>().Remove(entity);
    }

    public async Task Delete(IEnumerable<T> entities)
    {
        foreach (var entity in entities)
        {
            context.Set<T>().Remove(entity);
        }
    }

    public async Task Delete(int id)
    {
        var entityToDelete = context.Set<T>().FirstOrDefault(e => e.Id == id);
        if (entityToDelete != null)
        {
           context.Set<T>().Remove(entityToDelete);
        }
    }

    public async Task Update(T entity)
    {
        context.Set<T>().Update(entity);
    }

    public async Task Edit(T entity)
    {
        var editedEntity = context.Set<T>().FirstOrDefault(e => e.Id == entity.Id);
        editedEntity = entity;
    }

    public async Task<IEnumerable<T>> GetAll(Expression<Func<T, bool>> predicate = null)
    {
        var query = context.Set<T>().Include(context.GetIncludePaths(typeof(T)));

        if (predicate != null)
        {
            query = query.Where(predicate);
        }

        return await query.ToListAsync();
    }

    public async Task<T> GetById(int id)
    {
        return context.Set<T>().FirstOrDefault(e => e.Id == id);
    }

    public async Task<IEnumerable<T>> Filter()
    {
        return context.Set<T>();
    }

    public virtual async Task<IEnumerable<T>> Filter(Func<T, bool> predicate)
    {
        return context.Set<T>().Where(predicate);
    }

    public async Task SaveChanges() => context.SaveChanges();
}

В моей конфигурации DI у меня есть DatabaseFactory и UserService, определенные как одиночные.

Ошибка: «Не удается получить доступ к удаленному объекту».

Дополнительные сведения об ошибке: "в Microsoft.EntityFrameworkCore.DbContext.CheckDisposed () в Microsoft.EntityFrameworkCore.DbContext.get_DbContextDependencies ()
в Microsoft.EntityFrameworkCore.DbContext.get_Model () в Microsoft.EntityFrameworkCore.Internal.InternalDbSet 1.get_EntityType() at Microsoft.EntityFrameworkCore.Internal.InternalDbSet 1.get_EntityQueryable () в Microsoft.EntityFrameworkCore.Internal.InternalDbSet 1.System.Collections.Generic.IEnumerable<TEntity>.GetEnumerator() at System.Linq.Enumerable.WhereEnumerableIterator 1.MoveNext () в System.Linq.Enumerable.Any [TSource] (IEnumerable 1 source, Func 2 предикат) в App.Api.Controllers.UserController.PostUserDetail (UserDetailContract userDetailContract) в D: \ Хранилища \ приложения \ SRC \ App \ Api \ Контроллеры \ UserController.cs: линия 89"

Спасибо

Ответы [ 2 ]

0 голосов
/ 20 июня 2019

Ваша проблема заключается в подписи этого метода:

public async Task<IEnumerable<UserDetail>> GetUserDetailsByCode(string code)
{
  using (var db = databaseFactory.Create())
  {
    return await db.UserDetails.GetByCode(code);
  }
}   

IEnumerable<T> - это перечислимое число, которое обычно лениво оценивается. Между тем, Task<T> считается завершенным, когда перечислимое значение определено (а не когда оно завершено). И контекст удаляется, когда перечислимое значение определено . У вас была бы та же проблема, если бы код был синхронным.

Исправление состоит в том, чтобы «преобразовать» (оценить) перечисляемое значение перед удалением контекста:

public async Task<IReadOnlyCollection<UserDetail>> GetUserDetailsByCode(string code)
{
  using (var db = databaseFactory.Create())
  {
    return await db.UserDetails.GetByCode(code).ToList();
  }
}   
0 голосов
/ 20 июня 2019

Я думаю, что вы можете быть жертвой отсроченной казни.Следующий фрагмент кода создает экземпляр ApiDatabase, который, в свою очередь, создает новый ApiDbContext:

public IApiDatabase Create() //in DatabaseFactory
{
    return new ApiDatabase(new ApiDbContext());
}

Кстати, здесь обнаруживается запах кода, так как ApiDbContext одноразово, поэтомувы должны отслеживать эту ссылку и правильно ее утилизировать.

В любом случае, ApiDatabase является одноразовым, поскольку он заключен в оператор using, поэтому я думаю, что контекст удаляется после вызова GetByUserId:

public async Task<IEnumerable<UserDetail>> GetByUserId(int userId)
{
    return await Filter(x => x.UserId == userId);
}

Обратите внимание, что вы возвращаете перечисление.Я думаю, что это может не материализоваться к тому времени, когда вы его используете, отсюда и ошибка.Добавьте приведение к массиву, чтобы форсировать материализацию:

return await Filter(x => x.UserId == userId).ToArray();
...