2017-09-15 79 views
1

我试图实现UOW &回购模式一起EF和ASP.net API项目。异步不工作+回购的EF + UINT

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

问题

我注意到,如果我叫异步回购方式从我的服务,什么也没有发生,方法被调用,但似乎等待永远不会触发。如果我同步调用方法,一切都很好。 (实施例方法的是计数/ CountAsync)。

什么是更奇怪(我想不通为什么),这同样的方法调用由于某种原因,在一个服务方法的工作,但不是在另一个。

我将在更多的细节后,我提出我的代码解释。

PROJCET STRUCTRUE

我的项目是这样构成:

  • 我有API在服务层注入
  • 在服务注入UOW &回购
  • 在回购注射UOW
  • 最后UOW调用数据库上下文工厂,其职责是CREA我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(); 
    } 
} 

工作单位

​​

通用库

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

我想指出的是,我有对夫妇更回购(4总共包括保存和用户)在SavedService注入,但我不相信他们是相关因为它们与SavedRepo几乎相同,但如果需要,我也可以添加它们。这也只是目前实现这种模式和方法的服务。

因此,这里所发生的事情,当我打电话SaveFromGalery

  1. UOW构造函数被调用
  2. DatabaseContextFactory MasterDbContext()被调用
  3. MasterRepository构造函数被调用
  4. SavedMoviesRepository构造函数被调用
  5. UoW构造函数被调用(再次第二次)
  6. DatabaseContextFactory MasterDbContext()被调用(第二次)
  7. MasterRepository调用构造函数(又第二次)
  8. UserRepository叫
  9. MasterRepository构造函数被调用
  10. MasterRepository调用构造函数(再次第四届(又第3次)时间)
  11. SavedService构造函数被调用
  12. HTTP GET SaveFromGalery叫
  13. 用户是成功
  14. 用户获取回购
  15. _savedWorkoutsRepository。CountForUserAsync叫
  16. 程序进入方法打的await但从来没有返回结果

就当GetNumberOfSavedForUser被称为另一方面,会出现以下情况:

  1. 1 - 11步骤是相同的
  2. HTTP GET GetNumberOfSavedForUser称为
  3. 用户是从用户回购成功
  4. _savedWorkou取出tsRepository.CountForUserAsync被称为SUCCESSFULLY
  5. UOW调用Dispose
  6. UOW调用Dispose

而且如前所述,如果,_savedWorkoutsRepository.CountForUserAsync由同步一切进行罚款。

有人可以请我帮忙看看究竟发生了什么?

+3

也许可能会有一些死锁。尝试使用“.ConfigureAwait(false)”和个人,我会重构方法,如'异步任务 SomeMethod(){返回等待someService.MethodAsync();}'任务 SomeMethod(){return someService.MethodAsync );}' – Fildor

+0

@Fildor感谢您的回答和重构提示。不幸的是,'.ConfigureAwait(false)'没有帮助:( – hyperN

+0

@Fildor我的错误!它实际上**确实有帮助**。请接受我的道歉,我快速回答,我把'.ConfigureAwait(false)'都回购和服务,这有助于你知道也许有什么可以使用这种僵局和'.ConfigureAwait(false)'如何帮助?谢谢! – hyperN

回答

3

您可能正在使用WaitResult在您的真实世界的代码(而不是这里发布的代码,这是不完整的)。这将在ASP.NET经典的cause a deadlock

具体来说,当你将任务传递给await时,默认情况下它将捕获“当前上下文”并使用它在该任务完成时恢复异步方法。然后,任务上的代码块(即,WaitResult)。问题在于,ASP.NET经典的上下文一次只允许在一个线程中。所以只要该线程在任务中被阻塞,它就“接近”上下文,这实际上阻止了任务的完成。因此,僵局。

请注意,ConfigureAwait(false)是不是一个修复;它至多是一种解决方法。正确的解决办法是用await替换Wait/Result

+0

谢谢你的回答,你是对的,事实上我已经完全忘记了在我的API方法中写下“await”这个关键字,因为我非常疲倦:/我在代码中找遍各处找到罪魁祸首,但没有注意到那些愚蠢的错误。谢谢! – hyperN

+0

在附注中,请确保您注意编译器警告。 Visual Studio很擅长发现缺少的'await's - 它不能*总是*找到它们,但它会花费很多时间。 –

+0

谢谢你的提示,我没有意识到这一点。事实上,我确实有Reshaper,它也很好地发现了这样的东西,但它并没有警告我。我怀疑是因为在实际的代码中,整个事情被包装进Ok()中;响应 – hyperN