简体   繁体   English

确保应用程序层不返回IQueryable

[英]Make Sure Application Layer does not Return IQueryable

We have a Data Layer, Repository, and then Application layer. 我们有一个数据层,存储库,然后是应用程序层。 Is there way to prevent an Application layer project from returning any IQueryable? 有没有办法防止应用程序层项目返回任何IQueryable?

Currently we code review all return methods, review proper architecture, and ensure they tag ToList() at end when needed . 当前,我们对所有返回方法进行代码审查,审查适当的体系结构,并确保在需要时它们最终标记ToList()。

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? Visual Studio Builds,Xunit选项或Post execute Design方法中是否有一个选项可确保所有调用都不是IQueryable,而是转换为IEnumerable,IList等?

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? 有没有办法防止应用程序层项目返回任何IQueryable?

Assuming application layer refers to ASP.NET Core controllers and the problem is that queryable source is disposed before actual query is executed. 假设应用程序层引用ASP.NET Core控制器,并且问题在于在执行实际查询之前已放置了可查询源。

Option 1: 选项1:

Do not return IQueryable<T> from repository. 不要从存储库返回IQueryable<T> IMHO , repository suppose to return materialized query, IEnumerable<T> . 恕我直言 ,存储库假定返回IEnumerable<T>化查询IEnumerable<T>

In case you are dealing with large project and repository might become huge mess, you may consider different approach. 如果您正在处理大型项目,并且存储库可能变得一团糟,则可以考虑使用其他方法。

Option 2: 选项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. 继承ControllerBase和/或Controller并重写每个接受模型的方法,并确保传递的模型不可IQueryable,否则执行查询并将结果传递给基本方法。 20 methods in ControllerBase and 4 methods in Controller class. ControllerBase 20种方法, Controller类中有4种方法。

API example: API示例:

[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: MVC示例:

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 ToList方法调用将阻塞线程
  • 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. 当前,我们对所有返回方法进行代码审查,审查适当的体系结构,并确保在需要时它们最终标记ToList()。

I would suggest instead of reviewing code to sit down and write custom Roslyn Analyzers to avoid repeated tasks that can be solved once. 我建议不要查看代码来坐下来编写自定义的Roslyn分析器,以避免重复的任务只能一次解决。 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. 您可以在GitHub上找到已完成的分析器和代码修复。

.NET Analyzers .NET分析器

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? Visual Studio Builds,Xunit选项或Post execute Design方法中是否有一个选项可确保所有调用都不是IQueryable,而是转换为IEnumerable,IList等?

Yes, it is possible to use Roslyn Syntax Transformation . 是的,可以使用Roslyn语法转换

Hope it helps! 希望能帮助到你!

If you want to make sure your application layer does not return any IQueryable you have 2 options. 如果要确保应用程序层不返回任何IQueryable,则有2个选项。

1- Create an interface to force all implementations to return an IEnumerable if you want. 1-创建一个接口,以强制所有实现返回一个IEnumerable(如果需要)。

2- Create an abstract class to force all subclasses to implement your abstract methods. 2-创建一个抽象类,以强制所有子类实现您的抽象方法。

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. 我看到这种模式在不同的项目中重复出现,在这些项目中,您有一个与您的存储库一起使用并返回IQueryable的DataAccessLayer。 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. 因此,您可以轻松地在数据库端执行过滤器,但是在处理完数据并将其传递给ApplicationLayer(BusinessLayer)时,您将返回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. 因此,如果您要在数据库侧使用filter执行查询,或者可以将Entities放入内存中执行ToList()并在内存中进行过滤,则可以控制。 But it is up to you where you want to stop bubbling your IQueryable to a higher layer. 但这取决于您要停止将IQueryable冒泡到更高层的情况。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM