gpt4 book ai didi

c# - 异步不适用于 EF + 工作单元 + Repo

转载 作者:行者123 更新时间:2023-11-30 20:29:20 25 4
gpt4 key购买 nike

我已经尝试与 Entity Framework 和 ASP.net API 项目一起实现工作单元和 repo 模式。

首先,我想指出,我知道 DbContext 和 DbSets 是 UoW 和 Repo 模式的实现,但我正在尝试看看哪些适合我的项目。

问题

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

更奇怪的是(我不知道为什么)这个相同的方法调用出于某种原因在一个服务方法中有效,但在另一个服务方法中无效。

我会在展示我的代码后进行更详细的解释。

项目结构

我的项目结构是这样的:

  • 我有一个 API,其中注入(inject)了服务层
  • 在服务中注入(inject) UoW 和 Repos
  • 在 Repo 中注入(inject) UoW
  • 最后,UoW 调用数据库上下文工厂,其职责是创建我的 DbContex 的新实例

--- 代码 ---

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

数据库上下文工厂

/// <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 Controller

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

注入(inject)配置

(仅重要部分)

        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 中注入(inject)了更多的存储库(总共 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. 程序进入方法命中等待但从未返回结果

另一方面,调用 GetNumberOfSavedForUser 时,会发生以下情况:

  1. 1 - 11步相同
  2. 调用 HTTP GET GetNumberOfSavedForUser
  3. 成功从用户仓库中获取用户
  4. _savedWorkoutsRepository.CountForUserAsync 调用成功
  5. 调用 UoW Dispose
  6. UoW 处置称为

同样如前所述,如果 _savedWorkoutsRepository.CountForUserAsync 同步,一切都会顺利进行。

有人可以帮我弄清楚到底发生了什么吗?

最佳答案

您可能在实际代码中使用了 WaitResult(不是此处发布的代码,它不完整)。这将 cause a deadlock在 ASP.NET 经典上。

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

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

关于c# - 异步不适用于 EF + 工作单元 + Repo,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46236424/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com