联合多个类,按通用接口排序

时间:2013-08-24 01:37:10

标签: c# linq entity-framework interface covariance

这个问题主要是关于LINQ和可能的协方差。

我的两个实体实现了 IDatedItem 接口。我想联合,然后对它们进行排序,以列举为单个列表。我必须在枚举时保留特定于实体的属性。

通过举例说明,我尝试过的一种方法是:

Context.Table1.Cast<IDatedItem>().
Union(Context.Table2.Cast<IDatedItem>()).
SortBy(i => i.Date).
ForEach(u => CustomRenderSelector(u, u is Table1));

在试图以各种方式做到这一点时,我遇到了各种各样的错误。

  • LINQ to Entities仅支持转换EDM原语或枚举类型。
  • 无法处理类型'.IDatedItem []',没有已知的值到图层的映射
  • 无法创建“IDatedItem”类型的常量值。只有原始类型

更大的图景:

  • 此处显示的IDatedItem界面是对实际共享属性的简化。
  • 实际上,在联合之前过滤表格。
  • 特定于实体的属性将按顺序呈现在网页中。
  • 在并行功能中,它们将被序列化为JSON结果层次结构。
  • 我希望能够对结果执行LINQ聚合操作。

2 个答案:

答案 0 :(得分:1)

这需要比评论提供更多的空间。另一方面,这不是一个真正的答案,因为真的没有令人满意的答案。

要使Union成功,两个集合必须具有相同的类型(或者具有对常见类型的内在转换,这就是协方差的含义)。

因此,获得正确联盟的第一步可能是:

Context.Table1.Select(t1 => new {
                                    A = t1.PropA,
                                    B = t1.PropB,
                                    Date = t1.Date
                                })
.Union(
Context.Table1.Select(t2 => new {
                                    A = t2.PropC,
                                    B = t2.PropD,
                                    Date = t2.Date
                                }))
.OrderBy(x => x.Date)
.ToList();

将两个表都投影到同一个匿名类型。不幸的是,由于匿名类型,您无法执行.Cast<IDatedItem>()

因此,获取List<IDatedItem>的唯一方法是定义一个实现IDatedItem的类型并将两个表都投影到该类型:

Context.Table1.Select(t1 => new DateItem {
                                    A = t1.PropA,
                                    B = t1.PropB,
                                    Date = t1.Date
                                })
.Union(
Context.Table1.Select(t2 => new DateItem {
                                    A = t2.PropC,
                                    B = t2.PropD,
                                    Date = t2.Date
                                }))
.OrderBy(item => item.Date)
.AsEnumerable()
.Cast<IDatedItem>()

其中(我认为)非常详细。但只要EF不支持在linq查询中转换为接口,就可以了。

顺便说一句,与我在评论中所说的相反,排序将在SQL中完成。并且您可以在结果上使用后续聚合函数。

答案 1 :(得分:0)

这是工作代码。我的解决方案是确保所有数据都是本地的,以防止LINQ-to-EF尝试执行它知道不会导致许多不明错误的所有方式。然后,通用联盟上的简单类型声明可以保留。

这意味着,除了LINQ-to-EF的烦恼之外,这里的主要问题实际上是LINQ Union different types - dynamically casting to an interface?的重复。

public virtual ActionResult Index() {
    return View(StatusBoard().OrderBy(s => s.Status));
}

private IEnumerable<DefensiveSituationBoardMember> StatusBoard() {
    DateTime now = DateTime.UtcNow;
    DateTime historicalCutoff = now.AddDays(-1);
    IEnumerable<Member> activeMembers = Context.Members.Where(n => !n.Disabled).ToList();

    // IncomingAttack and Reinforcements both implement IDefensiveActivity
    IEnumerable<IncomingAttack> harm = Context.IncomingAttacks.Where(n => n.IsOngoingThreat || n.ArrivalOn > historicalCutoff).ToList();
    IEnumerable<Reinforcements> help = Context.Reinforcements.Where(n => !n.Returned.HasValue || n.Returned > historicalCutoff).ToList();

    // Here's the relevant fix
    IEnumerable<IDefensiveActivity> visibleActivity = help.Union<IDefensiveActivity>(harm);

    return from member in activeMembers
        join harmEntry in harm on member equals harmEntry.DestinationMember into harmGroup
        join activityEntry in visibleActivity on member equals activityEntry.DestinationMember into activityGroup
        select new DefensiveSituationBoardMember {
            Member = member,
            Posture = harmGroup.Max(i => (DefensivePostures?)i.Posture),
            Activity = activityGroup.OrderBy(a => a.ArrivalOn)
        };

}