0

We have a Data Layer, Repository, and then Application layer. Is there way to prevent an Application layer project from returning any IQueryable?

Currently we code review all return methods, review proper architecture, and ensure they tag ToList() at end when needed .

Is there an option in Visual studio Builds, Xunit option, or Post execute Design method to ensure all calls are not IQueryable, but converted to IEnumerable, IList, etc?

  • 1
    Wouldn't ToList() make more sense than AsEnumerable() ? [See this](https://stackoverflow.com/questions/3389855/am-i-misunderstanding-linq-to-sql-asenumerable) – H H Aug 25 '19 at 08:08
  • What exactly you mean by application project? Can you share a sample of classes from each layer and where you need to avoid returning IQueryable? – dropoutcoder Aug 25 '19 at 09:48
  • hi @dropoutcoder we have separate projects for data layer and then application layer –  Aug 25 '19 at 09:50
  • @AliceDavis: I do understand, but we need to see some code to give a valid answer. Your question is a bit broad and to answer it is to write 3 page long answer :) – dropoutcoder Aug 25 '19 at 09:54

2 Answers2

0

Asnwers are based on questions without Minimal, Reproducible Example and may not be accurate.

We have a Data Layer, Repository, and then Application layer. Is there way to prevent an Application layer project from returning any IQueryable?

Assuming application layer refers to ASP.NET Core controllers and the problem is that queryable source is disposed before actual query is executed.

Option 1:

Do not return IQueryable<T> from repository. IMHO, repository suppose to return materialized query, IEnumerable<T>.

In case you are dealing with large project and repository might become huge mess, you may consider different approach.

Option 2:

Inherit ControllerBase and/or Controller and override every single method accepting model and make sure passed model is not IQueryable, otherwise execute query and pass result to base method. 20 methods in ControllerBase and 4 methods in Controller class.

API example:

[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
    public IActionResult Get() {
        using (var context = new MaterializeQueryContext()) {
            return Ok(context
                .MaterializeQuery
                .Where(x=> x.Id > 1));
        }
    }

    public override OkObjectResult Ok(object model) {
        if (model is IQueryable queryable) {
            var toList = typeof(Enumerable)
                .GetMethod(nameof(Enumerable.ToList))
                .MakeGenericMethod(new[] { queryable.ElementType });

            var result = toList.Invoke(null, new[] { queryable });

            return base.Ok(result);
        }

        return base.Ok(model);
    }
}

MVC example:

public class HomeController : Controller {
    public IActionResult Index() {
        using (var context = new MaterializeQueryContext()) {
            return View(context
                .MaterializeQuery
                .Where(x => x.Id > 1));
        }
    }

    public override ViewResult View(object model) {
        if (model is IQueryable queryable) {
            var toList = typeof(Enumerable)
                .GetMethod(nameof(Enumerable.ToList))
                .MakeGenericMethod(new[] { queryable.ElementType });

            var result = toList.Invoke(null, new[] { queryable });

            return base.View(result);
        }

        return base.View(model);
    }
}

Downsides:

  • Reflection will add some overhead
  • It may be buggy in more complex scenarios
  • ToList method invocation will be blocking thread
  • Adding async support is (probably) impossible or it will be too complex
  • Non covered scenarios (example below)

For example returning result directly.

public IActionResult Get() {
    using (var context = new MaterializeQueryContext()) {
        return new OkObjectResult(context
            .MaterializeQuery
            .Where(x=> x.Id > 1));
    }
}

Currently we code review all return methods, review proper architecture, and ensure they tag ToList() at end when needed.

I would suggest instead of reviewing code to sit down and write custom Roslyn Analyzers to avoid repeated tasks that can be solved once. Also you can write code fixes for those scenarios to make it even easier to fix.

You can find already made analyzers and code fixes on GitHub.

.NET Analyzers

awesome-analyzers

Is there an option in Visual studio Builds, Xunit option, or Post execute Design method to ensure all calls are not IQueryable, but converted to IEnumerable, IList, etc?

Yes, it is possible to use Roslyn Syntax Transformation.

Hope it helps!

dropoutcoder
  • 2,627
  • 2
  • 14
  • 32
0

If you want to make sure your application layer does not return any IQueryable you have 2 options.

1- Create an interface to force all implementations to return an IEnumerable if you want.

2- Create an abstract class to force all subclasses to implement your abstract methods.

In this case your application layer class will implement the interface or the abstract class.

You can have as many layers as you want on your architecture, the cons here is that it will introduce another abstraction concept. I am seeing this pattern repeated on different projects where you have a DataAccessLayer that works with your Repositories and return IQueryable. So you can execute filters on the database side easy, but when you are done with your data and want to pass it to your ApplicationLayer(BusinessLayer) you return an IEnumerable. So you have the control if you want to execute queries with filter on the database side or you can bring your Entities into memory executing the ToList( ) and filter them on memory. But it is up to you where you want to stop bubbling your IQueryable to a higher layer.

Zinov
  • 3,817
  • 5
  • 36
  • 70