代码之家  ›  专栏  ›  技术社区  ›  glenatron

实体框架代码优先-根据子TPH类的类型进行选择

  •  4
  • glenatron  · 技术社区  · 11 年前

    我目前正在使用EntityFramework4处理一个项目,该项目使用“每层次表”来使用单个表表示一组类。其工作方式是,该表表示状态,并且不同的状态与不同的其他类相关联。

    所以你可以想象它是这样的,忽略所有州共享的公共字段:

    InactiveState 
      has a -> StateStore
    
    ActiveState 
      has a -> ActivityLocation
    
    CompletedState
      has a -> CompletionDate
    

    每个状态都有一个属于它的所有InventoryItems的集合。

    现在,我的库存中的每个项目都有许多状态,其中历史上的最后一个状态是当前状态。要保存在列表中,我有一个指向库存当前状态的快捷方式字段:

    public class InventoryItem : Entity
    {
    
        // whole bunch of other properties
    
        public virtual ICollection<InventoryState> StateHistory { get; set; }
    
        public virtual InventoryState LastState { get; set; }
    
    }
    

    我遇到的第一个问题是,例如,当我想查找 Active 状态

    事实证明 Linq-To-Entities doesn't support GetType() 所以我不能用这样的语句 InventoryRepository.Get().Where( x => x.LastState.GetType() == someType ) 可以 使用 is 运算符,但这需要一个固定类型,因此不能有这样的方法:

     public ICollection<InventoryItem> GetInventoryItemsByState( Type state )
     {
         return inventoryRepository.Get().Where( x => x.LastState is state );
     }
    

    在进行Linq查询之前,我必须根据类型运行某种if语句,这感觉是错误的。InventoryItem列表可能会变大,所以我需要在EF级别执行此操作,例如,我无法将整个列表拉入内存并使用GetType()。

    在这种情况下,我有两个问题,联系得足够紧密,我认为它们可以结合起来,因为它们可能反映了我缺乏理解:

    • 是否可以使用Linq-to实体找到共享子表类型的项列表?
    • 考虑到我没有使用Lazy Loading,是否可以 Include 使用TPH的子表类型的相关项,以便,例如,如果我有一个InactiveState作为InventoryItem的子项,我可以为该InactiveState预加载StateStore?
    1 回复  |  直到 7 年前
        1
  •  3
  •   Slauma    11 年前

    是否可以找到共享子表类型的项目列表 使用Linq To实体?

    我认为除了使用if/switch来检查类型并使用 is T OfType<T> 。您可以将此逻辑封装到扩展方法中,例如,以便有一个单独的位置进行维护和一个可重用的方法:

    public static class Extensions
    {
        public static IQueryable<InventoryItem> WhereIsLastState(
            this IQueryable<InventoryItem> query, Type state)
        {
            if (state == typeof(InactiveState))
                return query.Where(i => i.LastState is InactiveState);
            if (state == typeof(ActiveState))
                return query.Where(i => i.LastState is ActiveState);
            if (state == typeof(CompletedState))
                return query.Where(i => i.LastState is CompletedState);
    
            throw new InvalidOperationException("Unsupported type...");
        }
    }
    

    要这样使用:

    public ICollection<InventoryItem> GetInventoryItemsByState(Type state)
    {
       return inventoryRepository.Get().WhereIsLastState(state).ToList();
    }
    

    我不知道是否有可能建造 i => i.LastState is XXX 使用.NET手动创建表达式 Expression API和基于 Type 传递到方法中。(老实说,我也会感兴趣,但我自己几乎不知道如何操纵表情来回答这个问题。)

    考虑到我没有使用Lazy Loading,是否可以包括 使用TPH的子表类型的相关项,例如,如果 我有一个InactiveState作为我可以预加载的InventoryItem的子项 该非活动状态的StateStore?

    我不确定我是否理解正确,但通常都很渴望 Include 不支持对包含的特定子项进行任何筛选或附加操作。

    绕过这一限制并在单个数据库往返中仍然获得结果的一种方法是使用如下投影:

    var result = context.InventoryItems
        .Select(i => new
        {
            InventoryItem = i,
            LastState = i.LastState,
            StateStore = (i.LastState is InactiveState)
                ? (i.LastState as InactiveState).StateStore
                : null
        })
        .AsEnumerable()
        .Select(x => x.InventoryItem)
        .ToList();
    

    如果查询是一个跟踪查询(在上面的例子中就是这样),并且关系不是多对多(在您的例子中不是这样),那么当实体加载到上下文中时,上下文将修复关系,也就是 InventoryItem.LastState InventoryItem.LastState.StateStore (如果 LastState 属于类型 InactiveState )将被设置为已加载的实体( 犹如 他们已经装载了大量的货物)。