将一个完整的lambda表达式作为参数发送,包括:where、orderby、take、skip。
将一个完整的lambda表达式作为参数发送,包括:where、orderby、take、skip。
我正在编写一个分层的ASP.Net应用程序,其中包括业务层、存储库层、服务层...。在存储库层中,我使用EntityFramework作为ORM。在服务层中,我想以Lambda形式传递一个查询(包括OrderBy或OrderByDescending,take,skip等),并在DbSet上运行查询并返回结果实体。
简单地说:(我如何在ASP.Net C#中编写以下模拟代码)
public class Repository { public Listfindby(var query) { var dbcontext = DataContextFactory.GetDataContext(); //以下代码应该执行这样的操作:dbcontext.Books.Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10); List matchedBooks = RunQueryOnBooks(dbcontext.Books,query); return matchedBooks; } } public class Service { public List getTopNewBooks(Repository _repository) { var query = Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10); List matchedBooks = _repository.findby(query); return matchedBooks; } }
那么问题是:
- 我应该使用什么类型来代替var来声明查询(如果有的话)?
- 如何在dbcontext.Books上执行查询?
请给出一个像我这样的好的简单示例和更多的参考资料。提前谢谢。
如果你仔细思考一下,你说的是你想要将一些书籍的集合作为输入,并返回一些经过筛选/排序的书籍集合。因此,查询的适当类型应该是Func<IEnumerable<Books>, IEnumerable<Books>
。
基于这个想法,我可能会将查询作为一个函数(getTopNewBooks
)而不是一个lambda表达式,但两者都可以。无论哪种方式,你都可以将委托方法传递给findby
函数,例如:
public class Application { public static void Main() { var repo = new Repository(); foreach (var topBook in repo.findby(Service.getTopNewBooks)) { // This is a matching top book } } } public class Repository { public List<Book> findby(Func<IEnumerable<Book>,IEnumerable<Book>> query) { var dbcontext = DataContextFactory.GetDataContext(); List<Book> matchedBooks = query(dbcontext.Books).ToList(); return matchedBooks; } } public class Service { public static IEnumerable<Book> getTopNewBooks(IEnumerable<Book> input) { return input.Where(B=>B.New==true && B.Id>99).OrderBy(B=>B.Date).ThenBy(B=>B.Id).Skip(2).Take(10); } }
在这个例子中,我们定义了一个Application
类,其中包含一个Main
方法作为程序的入口点。我们还定义了一个Repository
类,其中包含了一个findby
方法,该方法接受一个Func<IEnumerable<Book>,IEnumerable<Book>>
类型的查询作为参数,并返回匹配的书籍集合。最后,我们还定义了一个Service
类,其中包含了一个名为getTopNewBooks
的静态方法,该方法接受一个IEnumerable<Book>
类型的输入,并返回经过筛选/排序的书籍集合。
通过将Service.getTopNewBooks
作为参数传递给repo.findby
方法,我们可以使用getTopNewBooks
方法中定义的lambda表达式来筛选和排序书籍集合。在这个例子中,lambda表达式使用Where
方法筛选出满足条件B.New==true && B.Id>99
的书籍,然后使用OrderBy
和ThenBy
方法按日期和Id进行排序,最后使用Skip
和Take
方法跳过2个元素并取出10个元素。
通过这种方式,我们可以将lambda表达式作为整体传递给findby
方法,并在Repository
类中使用它来过滤和排序书籍集合。这种方法的好处是可以将查询逻辑封装在一个可复用的方法中,使代码更加清晰和可维护。
问题的原因是LINQ使用Lambda表达式作为参数,但在某些情况下需要将整个Lambda表达式作为参数传递给另一个函数。在给定的示例中,需要将包含where、orderby、take和skip操作的完整Lambda表达式作为参数传递给Func。
要解决这个问题,可以创建一个Func,该Func返回适当的类型,并且接受适当的参数类型。然后,可以将包含所需操作的完整Lambda表达式作为参数传递给该Func,并调用该Func来执行操作。
在给定的示例中,创建了一个Func,该Func接受一个IEnumerable
整个Lambda表达式作为参数传递给Func的代码如下所示:
var myFunc = new Func<IEnumerable<Book>, IEnumerable<Book>>(book => book.Where(i => true /* rest of your code here */)); var test = Enumerable.Empty<Book>(); var result = myFunc(test);
问题的出现原因:
该问题是由于需要在服务层(service layer)向数据访问层(repository layer)发送查询(query)时遇到的困扰。服务层需要将包含where、orderby、take和skip的完整lambda表达式作为参数传递给数据访问层的方法。然而,最初的解决方法中,函数接受的参数类型为Func<IEnumerable<Book>, IEnumerable<Book>>
,而不是能够将lambda表达式转换为SQL并且只返回10条记录的IQueryable<Book>
类型。
解决方法:
为了解决这个问题,首先将函数接受的参数类型改为Func<IQueryable<Book>, IQueryable<Book>>
,并将query(set)
改为query(set.AsQueryable())
。这样做的好处是使用IQueryable
而不是IEnumerable
,可以将lambda表达式转换为SQL并且只返回10条记录,从而提高性能。
关于这种方法的适用性:
对于将查询从服务层传递到数据访问层的设计模式,这种方法是可行的。它允许在领域服务(domain service)中发送查询(Sql/Linq)到数据上下文/表,然后将查询转换为数据库语言并将结果返回到领域服务。然而,这种方法可能会破坏数据访问层的封装性,因为服务层可以运行任何Sql/Linq查询。另一方面,这种数据访问层对于其使用者来说是灵活的。
最后,虽然这个解决方案被接受,但在编辑答案时应根据评论修改代码。将Func<IEnumerable<Book>, IEnumerable<Book>>
改为Func<IQueryable<Book>, IQueryable<Book>>
,并将query(set)
改为query(set.AsQueryable())
。