多次调用服务后DbContext被处置的问题

时间:2019-06-19 20:09:53

标签: c# entity-framework .net-core entity-framework-core

我正在使用API​​,并且在对服务进行多次调用时遇到问题,而且方法不同,我让每个方法都创建并使用了新的DBContext(或者至少是这样做的目的),但是在第一个服务调用之后其他人则抱怨说DBContext已经被处理掉了,我希望您能为我指明正确的方向,因为据我所知,我正在为每个调用创建一个新的上下文-显然我在这里做错了,没有任何帮助将不胜感激。

我得到的实际错误是“无法访问已处置的对象。”

我知道我可以将db交互和上下文创建代码从服务中拉出,并放到此处的controller方法中(这是一个简化的示例),但是将需要在应用程序的其他部分中使用更多的服务,并且遇到了也存在问题,因此想尝试找出导致此问题的原因,以便可以将修复程序应用于其他地方。

这是其中涉及的简化类。

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:\ Repositories \ application \ src \ App \ Api \ Controllers \ UserController.cs:line   89英寸

谢谢

2 个答案:

答案 0 :(得分:0)

我认为您可能是延迟执行的受害者。下面的代码创建了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();

答案 1 :(得分:0)

您的问题是此方法的签名:

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();
  }
}