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!