按两个不同类型的字段对不同型号的列表进行排序

3

我有两个模型:

public class CarRent
{
    public string CarName { get; set; }
    public string SystemId { get; set; }
    public DateTime RentEndDate { get; set; }
}

public class CarPurchase
{
    public string CarName { get; set; }
    public string SystemId { get; set; }
    public decimal Mileage { get; set; }
}

我需要将它们合并为一个列表,按CarName进行分组,然后在每个组内,我需要首先按SystemId对型号进行排序,但如果模型具有相同的SystemId,则需要按RentEndDate对CarRent模型进行排序,并按Mileage对CarPurchase进行排序。
我尝试过定义一个接口:
public interface ICarPurchaseOrdered
{
    string CarName { get; }
    string SystemId { get; }
    string Order { get; }
}

我让我的模型实现了它,Order属性只返回第二个排序条件的字符串表示形式,然后我定义了一个视图模型:

public class GroupedCardList
{
    public string CarName { get; set; }
    public IEnumerable<ICarPurchaseOrdered> Cars { get; set; }
}

然后我有一个分组器,只是对我的模型进行分组:
public class CarGrouper
{
    IEnumerable<GroupedCardList> Group(IEnumerable<ICarPurchaseOrdered> cars)
    {
        return cars.GroupBy(c => c.CarName)
                   .OrderBy(c => c.Key)
                   .Select(c => new GroupedCardList()
                   {
                       CarName = c.Key,
                       Cars = c.OrderBy(n => n.SystemId)
                               .ThenBy(n => n.Order)
                   });
    }
}

但是它的运行不正确,因为它对字符串进行排序,我先得到了里程为1200的汽车购买记录,然后才是里程为90的汽车。

我知道这个例子有点牵强,但它很好地代表了我目前遇到的问题。请给我一些建议。


你能为里程添加一个ThenBy吗?但如果它不是整数,使用Convert.ToInt32Convert.ToDouble - Grant H.
我做不到这一点,因为在订单字段中,有时会有来自RentEndDate字段的DateTime字符串表示。也许我应该将Order更改为十进制,你认为呢? - Oleksii Aza
“Order”属性是一种代码异味。您的领域对象不应引入字符串属性用于排序,这是您的表示层的关注点。您在哪里显示这些项目?在“DataGridView”中吗?此外,您是否可能具有具有相同“SystemId”的“CarRent”实例和“CarPurchase”实例?(在这种情况下如何进行排序?) - vgru
@Groo 不是的,我正在一个asp.net mvc项目中使用它,并在简单视图中显示它。问题是按照一个标准对模型进行分组,然后根据模型类型进行排序。 - Oleksii Aza
但在这种情况下,你如何进行比较呢?你还没有指定如何处理这种情况。如何比较 CarRent { Id=5, RentEndDate=05/05/2014 }CarPurchase { Id=5, Mileage=100000 } - vgru
显示剩余3条评论
3个回答

2

一种实现方法是使用自定义IComparer。如果你提取一个共同的基类:

public class Car
{
    public string CarName { get; set; }
    public string SystemId { get; set; }
}

public class CarRent : Car
{
    public DateTime RentEndDate { get; set; }
}

public class CarPurchase : Car
{
    public decimal Mileage { get; set; }
}

那么,一个 IComparer<Car> 的实现看起来可能像这样:

public class CarComparer : IComparer<Car>
{
    public int Compare(Car x, Car y)
    {
        // compare by system id first
        var order = string.Compare(x.SystemId, y.SystemId);
        if (order != 0)
            return order;

        // try to cast both items as CarRent
        var xRent = x as CarRent;
        var yRent = y as CarRent;
        if (xRent != null && yRent != null)
            return DateTime.Compare(xRent.RentEndDate, yRent.RentEndDate);

        // try to cast both items as CarPurchase
        var xPurc = x as CarPurchase;
        var yPurc = y as CarPurchase;
        if (xPurc != null && yPurc != null)
            return decimal.Compare(xPurc.Mileage, yPurc.Mileage);

        // now, this is awkward
        return 0;
    }
}

您可以将比较器实例传递给List.SortEnumerable.OrderBy


因此,为了澄清,您只需编写 Cars = c.OrderBy(x => x, new CarComparer()),而无需使用 ThenBy。如果有大量不同的类和属性,则可能有一种使用反射进行泛化的方法(以避免为每个派生类型手动进行管道),但这可能更适合另一个问题。 - vgru
谢谢,这正是我所需要的。+1 为“现在,这很尴尬”。 - Oleksii Aza

1
你可以使用 int.Parse 来对整数进行排序,而不是字符串。
c.OrderBy(n => n.SystemId)
  .ThenBy(n => int.Parse(n.Order))

是的,但如果我有类型为CarRent的模型,并且需要按RentEndDate排序,我该如何处理要求呢?也许我应该将Order的类型更改为十进制,并只获取RentEndDate的Ticks?但对于CarPurchase,它只会返回Mileage? - Oleksii Aza
我不确定一个十进制数和日期时间之间最合适的逻辑比较是什么,它们在语义上也有所不同。 - Selman Genç
是的,看起来它们应该分开处理。 - Grant H.

0

ICarPurchaseOrdered.Order用于排序的类型是字符串类型;因此,排序是按字母顺序进行的。

我建议将ICarPurchaseOrdered.Order的类型更改为object, 这样Orderby可以使用基础对象(DateTimeDecimal)进行排序。

**更新: 请尝试这个

c.OrderBy(n => n.SystemId)
 .ThenBy(n => n.GetType())
 .ThenBy(n => n.Order);

好的观点,但它会抛出一个异常:System.ArgumentException:对象必须是DateTime类型。 在System.DateTime.CompareTo(Object value)处 在System.Collections.Comparer.Compare(Object a, Object b)处 - Oleksii Aza

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接