Опитах се да внедря модели на единица работа и репо заедно с Entity Framework и ASP.net API проект.
Първо, исках да отбележа, че знам, че DbContext & DbSets са имплементации на UoW & Repo модели, но си играя, за да видя какво ще пасне на моя проект.
ПРОБЛЕМ
Забелязах, че ако извикам async repo методи от моята услуга, нищо не се случва, методът се извиква, но изглежда, че изчаква никога не се задейства. Ако извикам метода синхронно, всичко е наред. (пример за метод е Count / CountAsync).
Още по-странното (и не мога да разбера защо) е, че същото извикване на метод по някаква причина работи в един метод на услугата, но не и в друг.
Ще обясня по-подробно, след като представя моя код.
СТРУКТУРА НА ПРОЕКТА
Моят проект е структуриран по следния начин:
- Имам API, в който се инжектира слоя на услугата
- In-Service са инжектирани UoW & Repos
- В Repo се инжектира UoW
- Накрая UoW извиква Database Context Factory, чието задължение е да създаде ново копие на моя 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 контролер
[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, включително Saved и User), инжектирани в SavedService, но не вярвам, че са подходящи, тъй като са почти същите като SavedRepo, но ако е необходимо, Мога и да ги добавя. Също така, това е единствената услуга, която в момента прилага този модел и методи.
И така, ето какво се случва, когато се обадя на SaveFromGalery
:
- Извиква се UoW конструктор
- Извиква се DatabaseContextFactory MasterDbContext().
- Извиква се конструктор MasterRepository
- Конструкторът SavedMoviesRepository се извиква
- Извиква се UoW конструктор (отново втори път)
- DatabaseContextFactory MasterDbContext() се извиква (втори път)
- Извиква се конструктор MasterRepository (отново втори път)
- Извиква се UserRepository
- Конструкторът на MasterRepository се извиква (отново 3-ти път)
- Извиква се конструктор MasterRepository (отново 4-ти път)
- Извиква се конструктор SavedService
- Извиква се HTTP GET SaveFromGalery
- потребителят е извлечен от потребителското репо успешно
- Извиква се _savedWorkoutsRepository.CountForUserAsync
- програмата въвежда метода, удря чакането, но никога не връща резултата
От друга страна, когато се извика GetNumberOfSavedForUser
, ето какво се случва:
- 1 - 11 стъпки са еднакви
- Извиква се HTTP GET GetNumberOfSavedForUser
- потребителят е извлечен от потребителското репо успешно
- _savedWorkoutsRepository.CountForUserAsync се извиква УСПЕШНО
- Извиква се UoW Dispose
- Извиква се UoW dispose of
Освен това, както беше посочено по-рано, ако _savedWorkoutsRepository.CountForUserAsync е направен синхронен, всичко върви добре.
Може ли някой да ми помогне да разбера какво точно се случва?
async Task<T> SomeMethod(){ return await someService.MethodAsync();}
доTask<T> SomeMethod(){ return someService.MethodAsync();}
- person Fildor   schedule 15.09.2017.ConfigureAwait(false)
не помогна :( - person hyperN   schedule 15.09.2017.ConfigureAwait(false)
както в репо, така и в услуга и това помогна. Знаете ли може би какво може да използва тази безизходица и как.ConfigureAwait(false)
помогна? Благодаря! - person hyperN   schedule 15.09.2017