如何改造基于非通用的 EF Core 代码以使用通用 IQueryable
Posted
技术标签:
【中文标题】如何改造基于非通用的 EF Core 代码以使用通用 IQueryable【英文标题】:How to retrofit Non Generic based EF Core code to use Generic IQueryable 【发布时间】:2022-01-17 08:13:41 【问题描述】:我正在将旧的 EF 解决方案更新到 EF Core,我想删除大部分不使用泛型 (T) 的代码,但我必须分阶段进行。原始代码经历了 10 多年学习实体框架的有机增长,并且混合了“哲学”和代码中的模式,例如从 Microsoft 到 Repository 模式的教程。
理想的解决方案是允许我为特定实体建立查询,然后打开连接并运行“ToList()”。然后我可以将部分代码分阶段移动到更干净的地方。
//logic to figure out what is needed with no connection or dbconnection yet
public void GetOrder()
var query = new List<Order>().AsQueryable();
query = query.Where(x => x.Id > 100);
var orders = repo.GetItems<Order>(query);
public IEnumerable<T> GetItems<T>(IQueryable<T> query, int page = 0, int maxcount = 0)
using (MyEntities context = new MyEntities(getConnectionString()))
context.Attach(query); //This does not seem to work
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query.ToList();
在原始旧代码中,EF 存储库在代码/请求构造函数中初始化实体,然后调用上述方法 GetItems。看起来正在对数据库进行两个不同的连接,并且正在发生两个不同的实体初始化以向数据库发出 1 个请求。
我正在使用 EF Core 6 和 .NET 6(Core 6)。
【问题讨论】:
EF Core 不应打开连接,直到您实现查询(即运行ToList
或任何其他“模拟”)。
EF Core 根据定义是通用的。 DbSetIQueryable<T>
。你想做什么?实现分页?为此,您甚至不需要对 DbContext 的引用。你为什么要使用context.Attach(query);
?这用于附加数据类,而不是执行查询IQueryable<T>
或@987654326 @ 可能?即便如此,无论如何实现存储库,获取 DbContext 实例都是其的工作。顺便说一句,DbSet 已经实现了存储库。 DbContext 实现工作单元
【参考方案1】:
您使用context.Set<T>()
获取DbSet<T>
(已经是IQueryable<T>
)。
context.Attach
将实体附加到上下文的更改跟踪器,这不是您想要的。
所以你想要这样的东西:
public void GetOrders()
List<Order> orders;
using(MyEntities context = new MyEntities(getConnectionString()))
var query = new List<Order>().AsQueryable();
orders = context.GetItems<Order>().Where(x => x.Id > 100).ToList();
// Do whatever with "orders", which is already materialized and doesn't need the dbcontext instance anymore
public static class DbContextExtensions
// On some extension static class
public static IQueryable<T> GetItems<T>(this MyEntities context, int page = 0, int maxcount = 0)
where T: class
var query = context.Set<T>().AsQueryable();
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query;
请注意,这与您现在应该使用 dbcontext 的方式不同(有依赖注入、异步等),但我基于您在问题上发布的代码
【讨论】:
【参考方案2】:您可以构建Func<IQueryable<T>, IQueryable<T>>
并使用context.Set<T>()
方法。类似这样的事情:
public void GetOrder()
Func<IQueryable<Order>, IQueryable<Order>> queryProvider =
query => query.Where(x => x.Id > 100);
var orders = repo.GetItems<Order>(query);
public IEnumerable<T> GetItems<T>(Func<IQueryable<T>, IQueryable<T>> queryProvider, int page = 0, int maxcount = 0)
using (MyEntities context = new MyEntities(getConnectionString()))
var query = queryProvider(context.Set<T>());
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query.ToList();
请注意,手动创建 DbContext
不再是惯用的方法,通常由 DI 处理。
另外我建议只创建一个Paginate
扩展方法。像这样的:
public static class QueryableExt
public static IQueryable<T> Paginate<T>(this IQueryable<T> query, int page = 0, int maxcount = 0)
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query;
【讨论】:
完全没有理由使用 DbContet。该分页方法(就是这样)可以简单地构建在顶部IQueryable<>
@PanagiotisKanavos 是的,当然。只是添加代码。【参考方案3】:
代码的问题不是泛型 - 根据定义,EF Core 是泛型的。 DbSet<T>
和 IQueryable<T>
是通用的。所有运算符都是通用的。
问题在于混淆了两件事。对 IQueryable 进行分页并创建特定的 IQueryable 实现。如果你把这两个分成不同的部分,问题就变得很简单了。
分页方式可以是:
public static IQueryable<T> GetPage<T>(this IQueryable<T> query,int page,int maxcount)
if (page > 1)
query = query.Skip(page * maxcount);
if (maxcount > 0)
query = query.Take(maxcount);
return query;
这可以与任何IQueryable<T>
实现一起使用,无论是实现IEnumerable<T>
的容器(如List<T>
)还是EF Core 查询:
var myList=new List<Order>();
...
var query=myList.AsQueryable()
.Where(...)
.GetPage(2,10);
var pageOrders=query.ToList();
或者
var query=context.Orders
.Where(...)
.GetPage(2,10);
var pageOrders=query.ToList();
没有理由创建GetItems
方法,因为它所做的只是创建一个特定 DbContext 并使用一个特定 DbSet。无需隐藏context.Orders
或创建context
的代码。事实上,在像 ASP.NET Core 这样的依赖注入环境中,DbContext 将由 DI 容器注入
【讨论】:
以上是关于如何改造基于非通用的 EF Core 代码以使用通用 IQueryable的主要内容,如果未能解决你的问题,请参考以下文章
如何首先将 EF Core 代码与 azure synapse 一起使用
非托管代码中的 ASP Net Core 1.1 和 EF 6 异常?