Lambda选择基础列表中的前1项

时间:2017-11-02 17:13:55

标签: c# linq lambda entity-framework-6

我有2个关系表

  

客户

     
    

Id,Nbr,姓名

  
     

分配

     
    

Id,CustomerId,Location,AssigmentTime

  

Customer.Id = Assigments.CustomerId

之间存在关系

每个客户都可以有很多作业,但我只对根据DateTime字段AssigmentTime的最后一个作业感兴趣

在SQL中,它应该是一个类似的查询:

Select Top 1 From Customer c
Inner Join Assigments a On c.Id = a.CustomerId
Where c.Nbr = 1234
Order By AssigmentTime Desc

构建正确的Lambda查询时遇到问题。

此代码有效,但效果不是很好:

var customerNbr = 1234:
var cst = context.Customers.FirstOrDefault(x => x.Nbr == customerNbr);
if (cst != null && cst. Assigments.Count > 1)
{
  cst. Assigments = new List<Assigments>
  {
     cst.Assigments.OrderByDescending(x => x.AssigmentTime).FirstOrDefault()
  };
}

如何在Customer.Assigments List属性中仅获得1个顶级Assigments?

3 个答案:

答案 0 :(得分:0)

例如:

var lastAssignment = customers.Where(x => x.Nbr == customerNbr)
                              .SelectMany(x => x.Assignments)
                              .OrderByDescending(x => x.AssignTime)
                              .FirstOrDefault();

答案 1 :(得分:0)

如果您已根据proper coding conventions设置实体框架,那么您已经设计了one-to-many relation as follows:

class Customer
{
    public int Id {get; set;} // primary key

    // a Customer has zero or more Assignments
    public virtual ICollection<Assignment> Assignments {get; set;}

    public int Nbr {get; set;}
    ... // other properties
}

class Assignment
{
    public int Id {get; set;} // primary key

    // every Assignment belongs to one Customer via foreign key
    public int CustomerId {get; set;}
    public virtual Customer Customer {get; set;}

    public DateTime AssignmentTime {get; set;}
    ... // other properties
}

public MyDbContext : DbContext
{
    public DbSet<Customer> Customers {get; set;}
    public DbSet<Assignment> Assignments {get; set;}
}

如果您已经设置了这样的一对多,那么这就是所有实体框架需要知道您设计的一对多关系。如果您不想遵循命名约定,则可能使用了流畅的API或属性来配置一对多。

以最后一次(最新)作业获得Nbr = 1234的客户

using (var dbContext = new MyDbContext())
{
    var result = dbContext.Customers
        .Where(customer => customer.Nbr == 1234)
        .Select(customer => new
        {
            // select the customer properties you will use, for instance
            CustomerId = customer.Id,
            CustomerName = customer.Name,

            // you only want the newest assignment:
            NewestAssignment = customer.Assignments
               .OrderByDescending(assignment => assignment.AssignmentTime)
               .Select(assignment => new
               {   // take only the Assignment properties you will use:
                   Location = assignment.Location,
                   AssignmentTime = assignment.AssignmentTime,
               }
               .FirstOrDefault(),
        });
    }
}

如果您确定最多只有一个客户的Nbr = 1234,那么您可以以SingleOrDefault结束;否则您的结果将是具有此Nbr。

的客户序列

每个客户只会拥有您将使用的客户属性,以及您将使用的最新分配的属性。高效!

答案 2 :(得分:0)

感谢您的建议Harald。我和同一个人一样,但我发现匿名对象有点臃肿。在我的例子中,我使用EF.Reverse.POCO Generator,因此每个对象都严格映射到DB。客户和作业实际上是别的东西 - 包含大量列的表格。我不能将匿名对象作为此函数的返回。

我仍然可以这样做:

using (var dbContext = new MyDbContext())
{
    var result = dbContext.Customers
    .Where(customer => customer.Nbr == 1234)
    .Select(customer => new Customer
    {
        // select the customer properties you will use, for instance
        Id = customer.Id,
        Nbr = customer.Nbr,
        Name = customer.Name,
        //…and lot of other property mapping
        // you only want the newest assignment:
        Assignments = new Collection<Assignments>
            {
                 customer.Assignments.OrderByDescending(assignment => assignment.AssignmentTime)
                 .FirstOrDefault()
            }
        });
    }
}

匿名的Customer代将导致大量的属性映射。这是次要问题。

即使我跳过Assignments属性,Select中包含类型对象的解决方案也会在结果中生成异常:

Message =“无法在LINQ to Entities查询中构造实体或复杂类型'MyNamespace.Customer'。”

如果我使用匿名对象,相同的代码工作正常,但正如我上面所写 - 我需要输入类型的对象。