Async无法使用EF + Uint of Work + Repo

时间:2017-09-15 09:36:45

标签: c# entity-framework async-await repository unit-of-work

我试图实施UoW&与EF和ASP.net API项目一起使用的回购模式。

首先,我想指出我知道DbContext& DbSets是UoW& amp;的实现。回购模式,但我正在玩,看看什么适合我的项目。

问题

我注意到如果我从我的服务调用异步repo方法,没有任何反应,方法被调用,但似乎等待永远不会被触发。如果我同步调用方法,一切都很好。 (方法的示例是Count / CountAsync)。

更奇怪的是(我无法理解为什么)是由于某种原因,同一方法调用在一种服务方法中起作用,而在另一种服务方法中起作用。

在我提交代码后,我会详细解释。

PROJCET STRUCTRUE

我的项目以这种方式构建:

  • 我有API,其中注入了服务层
  • 在服务中注入UoW&回购
  • 在Repo注入UoW
  • 最后,UoW调用数据库上下文工厂,其职责是创建我的DbContex的新实例

--- CODE ---

这是当前的实现,当然为了简洁省略了一些代码部分。

数据库上下文工厂

/// <summary>
///     Interface for factory which is in charge of creating new DbContexts
/// </summary>
/// <autogeneratedoc />
public interface IDatabaseContextFactory
{
    /// <summary>
    /// Creates new Master Database Context.
    /// </summary>
    /// <returns>newly created MasterDatabaseContext</returns>
    /// <autogeneratedoc />
    DbContext MasterDbContext();
}


/// <inheritdoc />
/// <summary>
/// This is factory which is in charge of creating new DbContexts
/// It is implemented as Singleton as factory should be implemented (according to Gang of four) 
/// </summary>
/// <seealso cref="T:Master.Domain.DataAccessLayer.IDatabaseContextFactory" />
/// <autogeneratedoc />
public class DatabaseContextFactory : IDatabaseContextFactory
{
    /// <summary>
    /// This is implementation of singleton
    /// </summary>
    /// <remarks>
    /// To read more, visit: http://csharpindepth.com/Articles/General/Singleton.aspx (Jon skeet)
    /// </remarks>
    /// <autogeneratedoc />
    private static readonly DatabaseContextFactory instance = new DatabaseContextFactory();

    // Explicit static constructor to tell C# compiler
    // not to mark type as beforefieldinit (more about this: http://csharpindepth.com/Articles/General/Beforefieldinit.aspx)
    static DatabaseContextFactory()
    {

    }

    //so that class cannot be initiated 
    private DatabaseContextFactory()
    {
    }


    /// <summary>
    /// Instance of DatabaseContextFactory
    /// </summary>
    public static DatabaseContextFactory Instance => instance;

    /// <inheritdoc />
    /// <summary>
    /// Creates new MasterDatabaseContext
    /// </summary>
    /// <returns></returns>
    public DbContext MasterDbContext()
    {
        return new MasterDatabaseContext();
    }
}

工作单元

 /// <inheritdoc />
/// <summary>
/// Unit of work interface
/// Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
/// </summary>
/// <seealso cref="T:System.IDisposable" />
/// <autogeneratedoc />
public interface IUnitOfWork : IDisposable
{
    /// <summary>
    /// Gets the database context. DatabaseContext is part of EF and itself is implementation of UoW (and repo) patterns
    /// </summary>
    /// <value>
    /// The database context.
    /// </value>
    /// <remarks> 
    /// If true  UoW was implemented this wouldn't be here, but we are exposing this for simplicity sake. 
    /// For example so that repository  could use benefits of DbContext and DbSet <see cref="DbSet"/>. One of those benefits are Find and FindAsnyc methods
    /// </remarks>
    /// <autogeneratedoc />
    DbContext DatabaseContext { get; }
    /// <summary>
    /// Commits the changes to database
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    void Commit();

    /// <summary>
    /// Asynchronously commits changes to database.
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    Task CommitAsync();

}

 /// <inheritdoc />
/// <summary>
/// This is implementation of UoW pattern
/// </summary>
/// <remarks>
/// Martin Fowler: "Maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems."
/// According to P of EEA, Unit of work should have following methods: commit(), registerNew((object), registerDirty(object), registerClean(object), registerDeleted(object)
/// The thing is DbContext is already implementation of UoW so there is no need to implement all this
/// In case that we were not using ORM all these methods would have been implemented
/// </remarks>
/// <seealso cref="T:Master.Domain.DataAccessLayer.UnitOfWork.IUnitOfWork" />
/// <autogeneratedoc />
public class UnitOfWork : IUnitOfWork
{
    /// <summary>
    /// Is instance already disposed
    /// </summary>
    /// <remarks>
    /// Default value of bool is false
    /// </remarks>
    /// <autogeneratedoc />
    private bool _disposed;

    /// <summary>
    /// Initializes a new instance of the <see cref="UnitOfWork"/> class.
    /// </summary>
    /// <param name="dbContextfactory">The database context factory.</param>
    /// <exception cref="ArgumentNullException">
    /// dbContextfactory
    /// or
    /// MasterDbContext - Master database context cannot be null
    /// </exception>
    /// <autogeneratedoc />
    public UnitOfWork(IDatabaseContextFactory dbContextfactory)
    {
        if (dbContextfactory == null)
        {
            throw new ArgumentNullException(nameof(dbContextfactory));
        }

        var MasterDbContext = dbContextfactory.MasterDbContext();

        if (MasterDbContext == null)
        {
            throw new ArgumentNullException(nameof(MasterDbContext), @"Master database context cannot be null");
        }

        DatabaseContext = MasterDbContext;
    }

    /// <summary>
    /// Gets the database context. DatabaseContext is part of EF and itself is implementation of UoW (and repo) patterns
    /// </summary>
    /// <value>
    /// The database context.
    /// </value>
    /// <remarks>
    /// If true  UoW was implemented this wouldn't be here, but we are exposing this for simplicity sake.
    /// For example so that repository  could use benefits of DbContext and DbSet <see cref="DbSet" />. One of those benefits are Find and FindAsnyc methods
    /// </remarks>
    /// <autogeneratedoc />
    public DbContext DatabaseContext { get; }

    /// <inheritdoc />
    /// <summary>
    /// Commits the changes to database
    /// </summary>
    /// <autogeneratedoc />
    public void Commit()
    {
         DatabaseContext.SaveChanges();
    }

    /// <inheritdoc />
    /// <summary>
    /// Asynchronously commits changes to database.
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    public async Task CommitAsync()
    {
        await DatabaseContext.SaveChangesAsync();
    }


    /// <inheritdoc />
    /// <summary>
    /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
    /// </summary>
    /// <autogeneratedoc />
    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    /// <summary>
    /// Releases unmanaged and - optionally - managed resources.
    /// </summary>
    /// <param name="disposning"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
    /// <autogeneratedoc />
    protected virtual void Dispose(bool disposning)
    {
        if (_disposed)
            return;


        if (disposning)
        {
            DatabaseContext.Dispose();
        }


        _disposed = true;
    }

    /// <summary>
    /// Finalizes an instance of the <see cref="UnitOfWork"/> class.
    /// </summary>
    /// <autogeneratedoc />
    ~UnitOfWork()
    {
        Dispose(false);
    }
}

通用存储库

/// <summary>
/// Generic repository pattern implementation
/// Repository  Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
/// </summary>
/// <remarks>
/// More info: https://martinfowler.com/eaaCatalog/repository.html
/// </remarks>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <autogeneratedoc />
public interface IMasterRepository<TEntity, in TKey> where TEntity : class
{
    /// <summary>
    ///     Gets entity (of type) from repository based on given ID
    /// </summary>
    /// <param name="id">The identifier.</param>
    /// <returns>Entity</returns>
    /// <autogeneratedoc />
    TEntity Get(TKey id);

    /// <summary>
    /// Asynchronously gets entity (of type) from repository based on given ID
    /// </summary>
    /// <param name="id">The identifier.</param>
    /// <returns></returns>
    /// <autogeneratedoc />
    Task<TEntity> GetAsnyc(TKey id);

    /// <summary>
    ///     Gets all entities of type from repository
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    IEnumerable<TEntity> GetAll();

    /// <summary>
    ///  Asynchronously gets all entities of type from repository
    /// </summary>
    /// <returns></returns>
    /// <autogeneratedoc />
    Task<IEnumerable<TEntity>> GetAllAsync();

    /// <summary>
    ///     Finds all entities of type which match given predicate
    /// </summary>
    /// <param name="predicate">The predicate.</param>
    /// <returns>Entities which satisfy conditions</returns>
    /// <autogeneratedoc />
    IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate);
}


//Note to self: according to P of EAA Repo plays nicely with QueryObject, Data mapper and Metadata mapper - Learn about those !!!



/// <summary>
/// Generic repository pattern implementation
/// Repository  Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
/// </summary>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TKey">The type of the key.</typeparam>
/// <seealso cref="Master.Domain.DataAccessLayer.Repository.Generic.IMasterRepository{TEntity, TKey}" />
/// <inheritdoc cref="IMasterRepository{TEntity,TKey}" />
public class MasterRepository<TEntity, TKey> : IMasterRepository<TEntity, TKey>
    where TEntity : class 
{

    /// <summary>
    /// DbSet is part of EF, it holds entities of the context in memory, per EF guidelines DbSet was used instead of IDbSet 
    /// </summary>
    /// <remarks>
    /// <para>
    /// Even though we are not 100% happy about this, 
    /// We decided to go with this instead of (for example) IEnumerable so that we can use benefits of <see cref="DbSet"/>
    /// Those benefits for example are Find and FindAsync methods which are much faster in fetching entities by their key than for example Single of First methods
    /// </para>
    /// </remarks>
    /// <autogeneratedoc />
    private readonly DbSet<TEntity> _dbSet;


    /// <summary>
    /// Initializes a new instance of the <see cref="MasterRepository{TEntity, TKey}"/> class.
    /// </summary>
    /// <param name="unitOfWork">The unit of work.</param>
    /// <exception cref="ArgumentNullException">unitOfWork - Unit of work cannot be null</exception>
    /// <autogeneratedoc />
    public MasterRepository(IUnitOfWork unitOfWork)
    {
        if (unitOfWork == null)
        {
            throw new ArgumentNullException(nameof(unitOfWork), @"Unit of work cannot be null");
        }

        _dbSet = unitOfWork.DatabaseContext.Set<TEntity>();
    }

    /// <inheritdoc />
    /// <summary>
    /// Gets entity with given key
    /// </summary>
    /// <param name="id">The key of the entity</param>
    /// <returns>Entity with key id</returns>
    public TEntity Get(TKey id)
    {
        return _dbSet.Find(id);
    }

    /// <inheritdoc />
    /// <summary>
    /// Asynchronously gets entity with given key
    /// </summary>
    /// <param name="id">The key of the entity</param>
    /// <returns>Entity with key id</returns>
    public async Task<TEntity> GetAsnyc(TKey id)
    {
         return await _dbSet.FindAsync(id);
    }

    /// <inheritdoc />
    /// <summary>
    /// Gets all entities
    /// </summary>
    /// <returns>List of entities of type TEntiy</returns>
    public IEnumerable<TEntity> GetAll()
    {
        return _dbSet.ToList();
    }

    public async Task<IEnumerable<TEntity>> GetAllAsync()
    {
        return await _dbSet.ToListAsync();

    }

    public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate)
    {
        return _dbSet.Where(predicate).ToList();
    }

}

已保存的电影资料库

 /// <inheritdoc />
/// <summary>
/// Repository for dealing with <see cref="T:Master.Domain.Model.MovieAggregate.SavedMovie" /> entity
/// </summary>
/// <seealso cref="!:Master.Domain.DataAccessLayer.Repository.Generic.IMasterRepository{Master.Domain.Model.MovieAggregate.SavedMovie,System.Guid}" />
/// <autogeneratedoc />
public interface ISavedMoviesRepository : IMasterRepository<SavedMovie, Guid>
{
    /// <summary>
    /// Asynchronously Gets number of saved Movies for the user
    /// </summary>
    /// <param name="user">The user.</param>
    /// <returns>Number of saved Movies</returns>
    /// <autogeneratedoc />
    Task<int> CountForUser(Model.UserAggregate.User user);
}


/// <inheritdoc cref="ISavedMoviesRepository" />
/// />
/// <summary>
///     Repository for dealing with <see cref="T:Master.Domain.Model.MovieAggregate.SavedMovie" /> entity
/// </summary>
/// <seealso cref="!:Master.Domain.DataAccessLayer.Repository.Generic.MasterRepository{Master.Domain.Model.MovieAggregate.SavedMovie, System.Guid}" />
/// <seealso cref="T:Master.Domain.DataAccessLayer.Repository.SavedMovies.ISavedMoviesRepository" />
/// <autogeneratedoc />
public class SavedMovieRepository : MasterRepository<SavedMovie, Guid>, ISavedMoviesRepository
{
    /// <summary>
    ///     Ef's DbSet - in-memory collection for dealing with entities
    /// </summary>
    /// <autogeneratedoc />
    private readonly DbSet<SavedMovie> _dbSet;
    private readonly IUnitOfWork _unitOfWork;



    /// <inheritdoc />
    /// <summary>
    ///     Initializes a new instance of the
    ///     <see cref="T:Master.Domain.DataAccessLayer.Repository.SavedMovies.SavedMovieRepository" /> class.
    /// </summary>
    /// <param name="unitOfWork">The unit of work.</param>
    /// <exception cref="T:System.ArgumentNullException"></exception>
    /// <autogeneratedoc />
    public SavedMovieRepository(UnitOfWork.UnitOfWork unitOfWork) : base(unitOfWork)
    {
        if (unitOfWork == null)
            throw new ArgumentNullException();
        _dbSet = unitOfWork.DatabaseContext.Set<SavedMovie>();
        _unitOfWork = unitOfWork;

    }

    /// <inheritdoc />
    /// <summary>
    ///     Asynchronously Gets number of saved Movies for the user
    /// </summary>
    /// <param name="user">The user.</param>
    /// <returns>
    ///     Number of saved Movies
    /// </returns>
    /// <exception cref="T:System.ArgumentNullException">user - User cannot be null</exception>
    /// <autogeneratedoc />
    public async Task<int> CountForUser(Model.UserAggregate.User user)
    {
        if (user == null)
            throw new ArgumentNullException(nameof(user), @"User cannot be null");

        return await _dbSet.CountAsync(r => r.UserWhoSavedId == user.Id);
    }
}

已保存的电影服务

/// <inheritdoc />
/// <summary>
///     This is service for handling saved Movies!
/// </summary>
/// <seealso cref="T:Master.Infrastructure.Services.SavedMovieService.Interfaces.ISavedMovieService" />
/// <autogeneratedoc />
public class SavedMovieService : ISavedMovieService
{
    /// <summary>
    /// The saved Movies repository <see cref="ISavedMoviesRepository"/>
    /// </summary>
    /// <autogeneratedoc />
    private readonly ISavedMoviesRepository _savedMoviesRepository;

    /// <summary>
    /// The unit of work <see cref="IUnitOfWork"/>
    /// </summary>
    /// <autogeneratedoc />
    private readonly IUnitOfWork _unitOfWork;

    /// <summary>
    /// The user repository <see cref="IUserRepository"/>
    /// </summary>
    /// <autogeneratedoc />
    private readonly IUserRepository _userRepository;

    /// <summary>
    /// Initializes a new instance of the <see cref="SavedMovieService"/> class.
    /// </summary>
    /// <param name="savedMoviesRepository">The saved Movies repository.</param>
    /// <param name="userRepository">The user repository.</param>
    /// <param name="unitOfWork">The unit of work.</param>
    /// <autogeneratedoc />
    public SavedMovieService(ISavedMoviesRepository savedMoviesRepository, IUserRepository userRepository,
        IUnitOfWork unitOfWork)
    {
        _savedMoviesRepository = savedMoviesRepository;
        _userRepository = userRepository;
        _unitOfWork = unitOfWork;
    }

    public Task<int> CountNumberOfSavedMoviesForUser(string userId)
    {
        if (string.IsNullOrEmpty(userId))
            throw new ArgumentNullException(nameof(userId), @"User id must not be empty");


        var user = _userRepository.Get(userId);
        return _savedMoviesRepository.CountForUser(user);
    }

      public async Task<Guid> SaveWorkoutFromLibraryAsync(string userWhoIsSavingId, Guid galleryId,
        bool isUserPro)
    {
        if (string.IsNullOrEmpty(userWhoIsSavingId))
            throw new ArgumentNullException(nameof(userWhoIsSavingId), @"User id cannot be empty");

        if (galleryId == Guid.Empty)
            throw new ArgumentException(@"Id of gallery cannot be empty", nameof(galleryId));

            //get user who is saving from DB
            var userWhoIsSaving = _userRepository.Get(userWhoIsSavingId);


            if (userWhoIsSaving == null)
                throw new ObjectNotFoundException($"User with provided id not found - id: {userWhoIsSavingId}");

            //how many movies has this user saved so far
            var numberOfAlreadySavedMoviesForUser = await _savedWorkoutsRepository.CountForUserAsync(userWhoIsSaving);

            // more code here
    }

}

网络Api控制器

[Authorize]
[RoutePrefix("api/Saved")]
[ApiVersion("2.0")]
public class SavedController : ApiController
{
    private readonly ISavedMovieService _savedMovieService;



    /// <inheritdoc />
    /// <summary>
    ///     Initializes a new instance of the <see cref="T:Master.Infrastructure.Api.V2.Controllers.SavedController" /> class.
    /// </summary>
    /// <param name="savedMovieService">The saved Movie service.</param>
    /// <autogeneratedoc />
    public SavedController(ISavedMovieService savedMovieService)
    {        
        _savedMovieService = savedMovieService;
    }

    public async Task<IHttpActionResult> GetNumberOfSavedForUser()
    {
        var cnt = await _savedMovieService.CountNumberOfSavedMoviesForUser(User.Identity.GetUserId());

        return Ok(cnt);
    }

    public async Task<IHttpActionResult> SaveFromGalery(SaveModel model)
    {
        await _savedMovieService.SaveWorkoutFromGalleryAsync(User.Identity.GetUserId(), model.Id, model.IsPro);

        return Ok();
    }
}

Ninject配置

(仅限重要部分)

        kernel.Bind<MasterDatabaseContext>().ToSelf().InRequestScope();
        kernel.Bind<IDatabaseContextFactory>().ToMethod(c => DatabaseContextFactory.Instance).InSingletonScope();
        kernel.Bind<IUnitOfWork>().To<UnitOfWork>().InRequestScope();

        kernel.Bind(typeof(IMasterRepository<,>)).To(typeof(MasterRepository<,>));

        kernel.Bind<ISavedMoviesRepository>().To<SavedMovieRepository>();
        kernel.Bind<IUserRepository>().To<UserRepository>();
        kernel.Bind<ISavedMovieService>().To<SavedMovieService>();

我想指出我在SavedService中注入了更多的repos(总共4个包括Saved和User),但我不相信它们是相关的,因为它们与SavedRepo非常相似,但是如果需要的话,我也可以添加它们。此外,这只是目前实现此模式和方法的服务。

所以这就是我打电话给SaveFromGalery时发生的事情:

  1. 称为UoW构造函数
  2. DatabaseContextFactory调用MasterDbContext()
  3. 调用MasterRepository构造函数
  4. SavedMoviesRepository构造函数被称为
  5. 调用UoW构造函数(第二次)
  6. 调用DatabaseContextFactory MasterDbContext()(第二次)
  7. 调用MasterRepository构造函数(再次第二次)
  8. 称为UserRepository
  9. 调用MasterRepository构造函数(再次第3次)
  10. 调用MasterRepository构造函数(再次第4次)
  11. 调用SavedService构造函数
  12. HTTP GET SaveFromGalery称为
  13. 用户从用户仓库成功获取
  14. _savedWorkoutsRepository.CountForUserAsync被称为
  15. 程序进入方法点击等待但永远不会返回结果
  16. 另一方面,当调用GetNumberOfSavedForUser时,会发生以下情况:

    1. 1 - 11步骤相同
    2. HTTP GET GetNumberOfSavedForUser被称为
    3. 用户从用户仓库成功获取
    4. _savedWorkoutsRepository.CountForUserAsync被称为SUCCESSFULLY
    5. UoW Dispose称为
    6. UoW dispose被称为
    7. 同样如前所述,如果使_savedWorkoutsRepository.CountForUserAsync同步,则一切正常。

      有人可以帮我解决究竟发生了什么事吗?

1 个答案:

答案 0 :(得分:4)

您可能在您的真实代码中使用 @Bean public CommonsMultipartResolver commonsMultipartResolver() { final CommonsMultipartResolver commonsMultipartResolver = new CommonsMultipartResolver(); commonsMultipartResolver.setMaxUploadSize(-1); return commonsMultipartResolver; } @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA, produces = MediaType.APPLICATION_JSON) public Response incluir(MultipartHttpServletRequest http) { } Wait(不是此处发布的代码,这是不完整的)。这将是ASP.NET经典的cause a deadlock

具体来说,当您将任务传递给Result时,默认情况下它将捕获&#34;当前上下文&#34;并在该任务完成时使用它来恢复异步方法。然后代码阻塞任务(即awaitWait)。问题是ASP.NET经典上下文一次只允许在一个线程中。因此,只要该线程在任务中被阻止,它就会占用&#34;该上下文实际上阻止了任务的完成。因此,僵局。

请注意Result不是修复程序;它充其量只是一种解决方法。正确的解决方法是将ConfigureAwait(false) / Wait替换为Result