Base solution for your next web application
Open Closed

ODataController and UnitOfWork #115


User avatar
0
radek stromský created

Hi,

I want to implement support for querying data using OData protocol v4. I have working example with fake data. Now I am trying to read data from database, but I don't see how I could possibly use combination of repository's IQueryable<T> GetAll() and UnitOfWork attribute.

I would like to have ODataController to look like this :

public class UsersController : ODataController
	{
		private readonly IUserRepository _userRepo;

		public UsersController(IUserRepository userRepo)
		{
			_userRepo = userRepo;
		}

		[EnableQuery]
		[UnitOfWork] // this will not work
		public IQueryable<User> Get()
		{
			return _userRepo.GetAll(); // this is the problem - DbContext will be disposed before it is needed.
		}
	}

I was able to get things done by using folowing alternative code:

[EnableQuery]
		public IEnumerable<User> Get(ODataQueryOptions<User> query)
		{
			return _userRepo.Query( q => query.ApplyTo(q).Cast<User>().ToList());
		}

But former solution is much better and cleaner only if it would work. Is there any possibility to keep DbContext alive to the point when Controller itself is disposed? E.g. something like UnitOfWork placed on entire controller not only its action?

Thanks of any suggestions.


7 Answer(s)
  • User Avatar
    0
    hikalkan created
    Support Team

    Hi,

    As a short answer: Your action "public IQueryable<User> Get()" must be virtual, like: "public virtual IQueryable<User> Get()".

    For logner answer: <a class="postlink" href="http://www.aspnetboilerplate.com/Pages/Documents/Unit-Of-Work#DocUowRestrictions">http://www.aspnetboilerplate.com/Pages/ ... strictions</a>

  • User Avatar
    0
    radek stromský created

    Unfortunately this doesn't fix the problem. DbContext is still disposed before ODataController is able to apply query options to IQueryable object.

    InvalidOperationException: The operation cannot be completed because the DbContext has been disposed.

  • User Avatar
    0
    hikalkan created
    Support Team

    Hmm... I think now, yes it can not work. Because, ABP closes database connection at the end of a unit of work (when your Get method ends). So, what can we do? We can inject MyDbContext easily and return context.Users. But, do you know when returned IQuerayble is disposed? I don't know OData. If DbContext is not disposed, it may cause memory leak.

  • User Avatar
    0
    radek stromský created

    I don't know when OData is done with IQueryable either. Only working solution I can think of is disposing DbContext inside Dispose(bool) method of the controller. That is what I am doing now. But I don't like an idea of using DbContext inside WebApi project instead of repositories.

  • User Avatar
    0
    hikalkan created
    Support Team

    One option can be:

    • Inject IUnitOfWorkManager and call Begin() in your constructor, get return value to a class field (which is IUnitOfWorkCompleteHandle).
    • Inject and user repository as normally
    • Call IUnitOfWorkCompleteHandle.Complete() in Dispose method.

    If this works, you can handle UOW tasks in a base class, thus you don't repeat it always.

  • User Avatar
    0
    hikalkan created
    Support Team

    This code worked for me:

    public class PersonsController : ODataController
        {
            private readonly IUnitOfWorkManager _unitOfWorkManager;
            private readonly IRepository<Person> _personRepository;
            private IUnitOfWorkCompleteHandle _unitOfWorkCompleteHandler;
    
            public PersonsController(
                IUnitOfWorkManager unitOfWorkManager,
                IRepository<Person> personRepository)
            {
                _unitOfWorkManager = unitOfWorkManager;
                _personRepository = personRepository;
            }
    
            public override async Task<HttpResponseMessage> ExecuteAsync(HttpControllerContext controllerContext, CancellationToken cancellationToken)
            {
                _unitOfWorkCompleteHandler = _unitOfWorkManager.Begin();
                return await base.ExecuteAsync(controllerContext, cancellationToken);
            }
    
            [EnableQuery]
            public IQueryable<Person> Get()
            {
                return _personRepository.GetAll();
            }
    
            [EnableQuery]
            public SingleResult<Person> Get([FromODataUri] int key)
            {
                return SingleResult.Create(_personRepository.GetAll().Where(p => p.Id == key));
            }
    
            private bool _isDisposed;
            protected override void Dispose(bool disposing)
            {
                if (!_isDisposed)
                {
                    _unitOfWorkCompleteHandler.Complete();
                    _unitOfWorkCompleteHandler.Dispose();
                }
    
                _isDisposed = true;
    
                base.Dispose(disposing);
            }
        }
    

    I just wanted to share.

  • User Avatar
    0
    hikalkan created
    Support Team

    Added to Github: <a class="postlink" href="https://github.com/aspnetboilerplate/sample-odata">https://github.com/aspnetboilerplate/sample-odata</a>