asp.net-coreentity-framework-coreodatanolock

Entity Framework Core - Performing NOLOCK queries with OData


I am trying to perform EF Core queries using NOLOCK, there is an article which demonstrated how to write an extension method, but it is not working as expected with OData query.

Here is the link: https://stackoverflow.com/a/63603655

query.ToListAsync is running the query against the table and trying to fetch millions of records. Whereas I am trying to pass this query with filters on

https://localhost:60484/odata/Users?$filter=userId eq 123&$expand=Products,Orders

So basically, I am trying to fetch records from user table where user id = 123 and also want to expand the products and orders foreign key properties as well.

// Controller method
[HttpGet]
[EnableQuery]
public async Task<IQueryable<User>> Get()
{
    return await _repository.UserRepository.GetUserData();
}

// Repository base
public async Task<List<T>> FindAll() => await _dbContext.Set<T>().AsNoTracking().ToListWithNoLockAsync();

// Repository method
public async Task<IQueryable<User>> GetUserData()
{
    var data = await FindAll();
    return data.AsQueryable()
                .Include(a => a.Products)
                .Include(a => a.Orders);
}

// Extension method from the article
public static class EFCoreTransaction
{
    public static async Task<List<T>> ToListWithNoLockAsync<T>(
                                        this IQueryable<T> query,
                                        Expression<Func<T, bool>>? expression = null,
                                        CancellationToken cancellationToken = default)
    {
        List<T>? result = default;

        using (var scope = CreateTransaction())
        {
            if (expression != null)
            {
                query = query.Where(expression);
            }

            result = await query.ToListAsync(cancellationToken);

            scope.Complete();
        }

        return result!;
    }

    private static TransactionScope CreateTransaction()
    {
        return new TransactionScope(TransactionScopeOption.Required,
                                    new TransactionOptions()
                                    {
                                        IsolationLevel = IsolationLevel.ReadUncommitted
                                    },
                                    TransactionScopeAsyncFlowOption.Enabled);
    }
}

Solution

  • I have found a way to do this. Eventually I can leave the repository classes untouched and just create the extension class as mentioned in the article. Then, in the controller I made these changes by using ODataQueryOptions. As Iqueryable doesn't actually execute the DB query so it will fetch the records and then apply ToList() with read uncommitted. Any expert opinion is appreciated. I have tested this by checking the logs and SQL profiler. The generated query is being run under a transaction scope.

    [HttpGet]
    [EnableQuery]
    public async Task<IActionResult> Get(ODataQueryOptions<User> options)
    {
        IQueryable<User>? tempQuery = _repository.UserRepository.GetUserData();
        if (options.Filter != null)
        {
            tempQuery = options.Filter.ApplyTo(tempQuery, new ODataQuerySettings()) as IQueryable<User>;
        }
        if (options.SelectExpand != null)
        {
            Request.ODataFeature().SelectExpandClause = options.SelectExpand.SelectExpandClause;
        }
        IQueryable<User> result = (await tempQuery!.ToListWithNoLockAsync()).AsQueryable();
        return Ok(result);
    }