在Controller LINQ中过滤子实体

时间:2016-02-19 12:15:57

标签: c# entity-framework linq

我试图过滤掉我在控制器中传递给我的视图的项目,虽然我有一个解决方案,但我很想知道正确的方法,因为我&#39 ;我确定它不是最好的。

我有一份工作订单表和每个工单的相关任务。在我的网站中,我不删除行,但使用WhenDeleted日期标记它们,因此在填充我的工作订单视图(单个,而不是列表)时,我想要一个带有子任务的WorkOrder实体,但我想过滤掉标记为的任务删除。

这是我的代码,它有效,但似乎我应该能够通过初始调用来保存旅行。

if (db.tblWorkOrder.Any(x => x.ID == id))
{
    tblWorkOrder model = db.tblWorkOrder.First(x => x.ID == id);
    model.tblTask = model.tblTask.Where(x => x.WhenDeleted == null).ToList()
}

解决方案: 这里标出的重复问题有答案。还有其他人在这里有更强大的google-fu,我认为这有助于了解我试图做的事情的术语。对于其他首先发现这个问题的人来说,这是其中的答案:

db.Configuration.ProxyCreationEnabled = false; // disable lazy-loading
if (db.tblWorkOrder.Any(x => x.ID == id))
{
    tblWorkOrder model = db.tblWorkOrder.Where(b => b.ID == id)
        .Select(b => new
        {
            b,
            tblTask = b.tblTask.Where(p => p.WhenDeleted == null).OrderBy(x => x.Position)
        })
        .AsEnumerable()
        .Select(x => x.b)
        .First();
}

4 个答案:

答案 0 :(得分:0)

这应该可以保存到db的行程,因为查询仅在命中SingleOrDefault()时执行。

           tblWorkOrder model =  (from p in db.tblWorkOrder
            where p.ID == id
            select new tblWorkOrder
            {
              // other tblWorkOrder properties
               tblTask = from q in p.Tasks where q.WhenDeleted == null select q 
            }).SingleOrDefault();

答案 1 :(得分:0)

不幸的是,一旦将父实体读入内存,EF就不支持显式加载部分子实体集。读取tblTask​​属性后,所有相关任务都将读入内存。一种选择是使用您想要的数据将匿名对象读入内存:

db.tblWorkOrder.Where(x => x.ID == id)
   .Select(x => new { x.ID, ..., tasks = x.tblTask.Where(t => t.WhenDeleted == null) })
   .First();

另一种方法是对dbContext使用两个显式查询:

tblWorkOrder model = db.tblWorkOrder.First(x => x.ID == id);
model.tblWorkOrder = db.tblTask.Where(t => t.WhenDeleted == null).ToList();

然而,这种风险(与您的原始方法一样)是通过修改"活跃"实体对象(在这种情况下是模型),在上下文中调用SaveChanges()现在会尝试更新数据库,这不是你想要的。

另一种方法是为任务表创建一个视图,过滤掉已删除的任务,并为该视图创建单独的EF模型和导航属性。

一般来说,我发现尝试直接使用EF实体类型作为视图模型并不能很好地工作,因为实体类型与数据库结构紧密相关,而视图模型经常需要分歧(就像这里的情况一样)。我认为最好的长期方法是为视图模型创建单独的类,只使用实体查询数据库并填充模型。

答案 2 :(得分:0)

除了其他答案之外,另一种方法是为您的实体创建构造函数:

public TblWorkOrder(TblWorkOrder tbl, IEnumerable<Task> tasks)
{
   this = tbl;
   this.tbltask = tasks;
}

然后您可以在Select中使用该构造函数:

if (db.tblWorkOrder.Any(x => x.ID == id))
{
   tblWorkOrder model = db.tblWorkOrder.Where(x => x.ID == id).AsEnumerable()
     .Select(x => new TblWorkOrder(x,x.tblTask.Where(x => x.WhenDeleted == null))).First();
}

由于对AsEnumerable()的调用,结果集将在内存中处理,这使您可以在select中使用构造函数。这不会是性能问题,因为你只会获取1个实体(就像你现在这样)。 此外,由于延迟加载,最好Include()任务保存另一次数据库之旅。

答案 3 :(得分:0)

有多种方法可以实现这一目标,其中一种方法可以限制您对数据库的干扰:

var result = db.tblTask.Where(x => x.WhenDeleted == null && x.WorkOrderId = id).GroupBy(x => x.WorkOrder).FirstOrDefault();
if(result != null){
   return new {WorkOrder = result.Key, Tasks = result.ToList()};
}

另一个我认为更干净的approuch将使用EntityFramework.Filters,您可以在上下文中配置这样的过滤器

DbInterception.Add(new FilterInterceptor());
modelBuilder.Conventions.Add(FilterConvention.Create<tblTask>("OnlyActive", (task) => task.WhenDeleted == null));

并在您的查询之前应用它:

db.EnableFilter("OnlyActive");
tblWorkOrder model = db.tblWorkOrder.First(x => x.ID == id);
db.DisableFilter("OnlyActive");