根据自定义优先级排序对象和选择

Ordering Object and Selecting based on custom priority

我有一个自定义优先级数组。

int[] priorities = { 866, 663, 855, 853, 854};

我现在有这段代码可以对我的列表进行排序并根据条件选择第一个。

            var target = (from people in GetNearbyPeopleList()
                          where people.DistanceToTravel > 0 && people.ReachedDestination == false
                          orderby //not sure what to do here
                          select people).FirstOrDefault();

所以我想按我的自定义优先级排序。其中 people.currentlocation 按我的优先级数组排序。

我做 - ?

orderby priorities.Contains(people.currentlocation)

我能想到的就是这些,但它没有按照我自定义优先级的顺序正确排序。我希望它完全遵循这个顺序:

int[] priorities = { 866, 663, 855, 853, 854};

因此,如果位置是 866,请选择那个。我只想select一个,我想select第一个基于优先级列表。如果currentlocation == 866不存在,则选择663等等。

我想这样就可以了。这使您可以根据数组顺序进行排序。

必须添加逻辑,所以 -1(未找到)在末尾

orderby Array.IndexOf(priorities, people.currentLocation) == -1 ? Integer.MaxValue : Array.IndexOf(priorities, people.currentLocation);

这不能通过这种方式完成。* Linq 无法按照您需要的方式使用您自己的数组。但是我可以想到其他几种方法

  • 重新设计您的应用程序,使您在服务器table 中具有优先级
  • 使用 table 值参数 (TVP)。这是将数组发送到要在查询中使用的服务器的正确方法,它可以毫无问题地扩展到数千个项目。它需要支持它的服务器(比如 Microsoft SQL 服务器)并且查询必须在 linq 之外完成。 Linq 不支持这一点。
  • 使用Contains查询所有项目,在客户端选择第一个。 Contains 可以使用你自己的数组,但要注意它会将其转换为参数,一个值代表一个参数。如果数组中的项目太多(>100),请不要使用它,否则查询会很慢并且会崩溃超过大约 2000 个项目(取决于服务器类型)。
  • 循环数组并一次查询一项。例如,您可以将它与 Contains 结合使用并一次查询 10 个项目以加快速度。

*Amy B 找到了方法,我的错。但要注意限制。它不是为每个值使用一个参数,而是使用两个参数,从而使其更加受限。它可能会导致 table 扫描,这可能会更加昂贵,具体取决于 table 大小和数组大小。在 20M table 上,它需要比仅使用 Contains 查询所有 5 行多 25000 倍的数据库资源。您可能应该将它与 Contains 过滤器结合使用以避免 table 扫描,但这意味着每个项目三个参数...

如果你的值在编译时是固定的...避免数组并写:

orderby
  people.currentLocation == 866 ? 1 :
  people.currentLocation == 663 ? 2 :
  people.currentLocation == 855 ? 3 :
  people.currentLocation == 853 ? 4 :
  people.currentLocation == 854 ? 5 :
  6

如果您的值在 运行 时改变,但值的数量有一些固定的最大值,那么写:

Person FindPriorityPerson(IQueryable<Person> query,
  int p1 = 0, int p2 = 0, int p3 = 0, int p4 = 0,
  int p5 = 0, int p6 = 0, int p7 = 0, int p8 = 0)
{
  return query.OrderBy(person =>
    person.currentLocation == p1 ? 1 :
    person.currentLocation == p2 ? 2 :
    person.currentLocation == p3 ? 3 :
    person.currentLocation == p4 ? 4 :
    person.currentLocation == p5 ? 5 :
    person.currentLocation == p6 ? 6 :
    person.currentLocation == p7 ? 7 :
    person.currentLocation == p8 ? 8 :
    9).FirstOrDefault();
}

Ctznkan525's answer gave me the idea to use "Select with index" 按顺序或优先级对数组进行排序。

因此,您希望所有具有优先权的人在列表或 Priorities 中排在第一位。第二个是列表中提到的第二个优先的人,第三个是列表中第三个优先的人等等

你注意到我提到索引来识别顺序了吗?因此,如果我将索引添加到您的优先级列表中,我可以按此索引排序。

如果 GetNearByPeopleList returns 一个 IEnumerable,这就有效。

var indexedPriorities = priorities.Select( (priority, index) => new
{
    Priority = priority,
    OrderIndex = index,
});
var result = GetNearbyPeopleList()
    .Where(...)                          // take only people you want
    .Join(indexedPriorities,             // join with indexedPriorities
    people => people.CurrentLocation,    // from each people take the CurrentLocation
    indexedPrio => indexedPrio.Priority, // from each indexedPriority take the priority
    (people, prio) => new                // when they match, make a new object
    {
        Index = prio.Index,              // containing the index of the matching priority
        People = people,                 // and the matching data
    })
    .OrderBy(item => item.Index)         // order by ascending index
    .Select(item => item.People);        // keep only the People

此代码排除了所有具有 CurrentLocation 且不在您的优先级列表中的人。如果你想要它们,你应该在你想要的地方连接它们,可能在最后。

唉,linq-to-entities does not support "select with index"。您可以尝试先添加索引,将列表设为 AsQueryable,然后进行连接:

var indexedPriorities =  = priorities.Select( (priority, index) => new
{
    Priority = priority,
    OrderIndex = index,
})
.AsQueryable();

var result = GetNearbyPeopleList()
    .Where(...)       
    .Join(indexedPriorities, 
    people => people.CurrentLocation, CurrentLocation
    indexedPrio => indexedPrio.Priority, 
    ...

唉,这行不通,因为您只能传输要在连接中使用的简单类型 AsQueryable。

然而,如果数据是远程的,而你确实需要它,你可以将你的优先级和索引转换为小数:

  var indexedPriorities =  = priorities.Select( (x, i) => (decimal)x + (decimal)i/1000M)
      .AsQueryable();

小数点前优先,小数点后排序:(.001在.002之前等

IQueryable<People> nearbyPeople = GetNearbyPeopleList()
   .Where(...);

var result = nearbyPeople.Join(indexedPriorities,
    people => people.CurrentLocation,
    prio => (int)Math.Floor(prio),
    (people, prio) => new
    {
        OrderIndex =(prio - Math.Truncate(prio),
        People = people,
    })
    .OrderBy(item => item.OrderIndex)
    .Select(item => item.People);

首先我将原来的优先级列表:{ 866, 663, 855, 853, 854} 更改为 {866.000, 663.001, 855.002, 853.003, etc}。假设您的优先级列表没有 1000 个元素。

然后我与866.000、663.001等的Math.Floor进行连接。匹配时我将小数点后的部分保留为OrderIndex:.000、.001、.002等。(不需要,但如果需要:乘以 1000)

订购后我去掉了 OrderIndex。