简体   繁体   中英

C# IQueryable LINQ Group By with null values

i want to select distinct values from database based on one property with returned all values when that property is null . IDs in my database are Strings

My database looks like this:

Id1   Id2    Value
____________________
1     null   Value1
2     1      Value2
3     1      Value3
4     null   Value4
5     null   Value5
6     2      Value6
7     2      Value7
8     2      Value8

I want to get output from my query like this:

Id1   Id2    Value
____________________
1     null   Value1
2     1      Value2  // i dont care which one from Id2 = 1 i get
4     null   Value4
5     null   Value5
6     2      Value6  // i dont care which one from Id2 - 2 i get

As you can see i want to get a List that have all elements where Id2 is null and return only one element where Id2 is the same (i dont care which element query will return).

I tried to code something:

query
   .Where(x => !string.IsNullOrEmpty(x.Id2))
   .GroupBy(z => z.Id2)
   .Select(grp => grp.FirstOrDefault())
   .ToListAsync();

But i dont get what i want, only one item representation by Id2 and only one null value, something like this:

Id1   Id2    Value
____________________
1     null   Value1
2     1      Value2  // I want to get all elements where Id2 = null
6     2      Value6  // and distinct elements based on Id2

My question is, how to write query to EF to get all null items and all distinct items based on property?

Untested:

var result = query.Where(x => x.Id2 == null || !query.Any(y => y.Id2 == x.Id2 && string.Compare(y.Id1, x.Id1) < 0))

That should get you all rows where Id2 is null and only the row with minimum Id1 for each group of Id2

After your GroupBy, you have a sequence of Groups. The Key of the group is the value of Id2.

If the Key (Id2) is not null, you only want one element of the group, you don't care which one.

If the Key equals null, you want all elements of the group.

There are two methods to do this:

  • Do your GroupBy for all non-null elements
  • Concat it with the null elements

Or:

  • Use parameter ResultSelector of the GroupBy to check whether the Key is null or not, and select either only one element, or all elements of the group

Concatenation method

IQueryable<MyClass> source = dbContext....

// the elements with non-null Id2, keep only one element:
IQueryable<MyClass> filledId2 = source
    .Where(item => item.Id2 != null)
    .GroupBy(item => item.Id2,

        // Parameter ResultSelector, take Id2 (key) and all items with this Id2
        // to make one new:
        (key, itemsWithThisKey) => itemsWithThisKey.FirstOrDefault());

Note: there won't be any empty groups, so no "default" items in the result.

The elements with null Id2:

IQueryable<MyClass> emptyId2 = source.Where(item => item.Id2 == null);

Concatenate:

var result = filledId2.Concat(emptyId2);

Note: no query has been executed yet. If desired you can create one big LINQ statement. This won't improve efficiency. It will however deteriorate readability.

ResultParameter method

   IQueryable<MyClass> filledId2 = source.GroupBy(item => item.Id2,

       // resultSelector: if the key is null, select all elements of the group
       // otherwise select a sequence of one element of the group
       (key, itemsWithThisKey) => (key == null) ? 
             itemsWithThisKey : itemsWithThisKey.Take(1))

       // result: a sequence of sequences of MyClass objects
       // use SelectMany to make it one sequence of MyClass objects:
       .SelectMany(group => group);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM