OrderBy child property linq to sql - c#

Could someone help me with ordering nested collection in linq to sql. See my example below:
public class A
{
public int Id { get; set; }
}
public class B
{
public virtual ICollection<A> ListA { get; set; }
}
Call for data from db:
_unitOfWork.DbContext.B
.OrderByDescending(ev => ev.ListA.OrderBy(a => a.Id))
.ToList();
I case when ListA not empty all works fine. But if List A is empty i get an exception says that at least one object needs to implement IComparable. Is there a way to overcome that problem ?

You cannot order by null. null does not implement IComparable obviously.
Add a where clause before the orderby to check if listA is not null.
_unitOfWork.DbContext.B
.Where(ev => ev.ListA != null)
.OrderByDescending(ev => ev.ListA.OrderBy(a => a.Id))
.ToList();

If the aim is to have the B with Something in ListA, then Bs with empty collection, then Bs with Null reference.
With a ListA that is also ordered, you can check for null and empty list, then order based on that.
var Bs = new[] {
new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 },new A { Id = 2 }} }
,new B{ListA = new List<A>{} }
,new B{ListA = null }
};
var result = Bs.Select(b =>
{
int i = 0;
if (b.ListA == null)
{
i = 2;
}
else if (!b.ListA.Any())
{
i = 1;
}
else {
b.ListA = b.ListA.OrderBy(a => a.Id).ToList();
}
return new { oIndex = i, value = b };
})
.OrderByDescending(x => x.oIndex)
.Select(g => g.value);
You stated : "when ListA not empty all works fine"; I'm not sure to understand how. As the following code will throw without any null or empty collection.
var Bis = new[] {
new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 },new A { Id = 2 }} }
,new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 }} }
,new B{ListA = new []{ new A { Id = 999 }} }
,new B{ListA = new []{ new A { Id = 3 },new A { Id = 1 },new A { Id = 2 },new A { Id = 4 }} }
};
var result = Bis.OrderByDescending(ev => ev.ListA.OrderBy(a => a.Id)).ToList();
Il you are comparing list you need to compare them on something. Is {1, 2, 3} bigger than {1,1,1,1}? How is {1,2,3} compare to {999, 999}? {2} and {1,1}? Because it has more element? Bigger value? What if one has bigger value but the others sum is twice bigger? For 2 list of 0s.. you base you comparaison on Sum and Value?

Related

How to search in C# LINQ

I have a string search query which I have from the frontend app but I have a problem with the query.
I have a list of objects which have Id (number = int).
If the user will write in the search box number 12(string) he should have all lists of objects which contains the number 12 in id.
Objects (1,8,80,90,180);
Another example is if the user will input the number 8. He should have output 8,80,180;
How to write linq for questions about such a thing?
Any example remember search query is a string and id is a number :(
using System;
using System.Linq;
public class Program
{
public class MyObject
{
public int Id { get; set; }
}
public static void Main()
{
var arr = new MyObject[]
{
new MyObject() { Id = 1 },
new MyObject() { Id = 8 },
new MyObject() { Id = 80 },
new MyObject() { Id = 90 },
new MyObject() { Id = 180 }
};
var searchQuery = "8";
var result = arr.Where(x => x.Id.ToString()
.Contains(searchQuery))
.Select(x => x.Id)
.ToList();
Console.WriteLine(String.Join(",", result));
}
}
https://dotnetfiddle.net/AiIdg2
Sounds like you want something like this
var input = "8";
var integers = new[] { 8, 80, 810, 70 };
var result = integers.Where(x => x.ToString().Contains(input));
Something like this could be enough:
using System.Globalization;
namespace ConsoleApp2
{
internal class Program
{
static void Main(string[] args)
{
var items = new[]
{
new Item { Id = 8 },
new Item { Id = 18 },
new Item { Id = 80 },
new Item { Id = 6 },
new Item { Id = 13 },
};
var itemsWithSearchString = items
.Select(x => new { Item = x, SearchString = x.Id.ToString(CultureInfo.InvariantCulture) })
.ToArray();
const string userInput = "8";
var matchingItems = itemsWithSearchString
.Where(x => x.SearchString.Contains(userInput, StringComparison.Ordinal))
.Select(x => x.Item)
.ToArray();
foreach (var item in matchingItems)
{
Console.WriteLine($"Matching item: {item}");
}
}
}
public class Item
{
public int Id { get; set; }
public override string ToString()
{
return $"Id {this.Id}";
}
}
}

Linq query to return items of list with another list inside it

I'm new to Linq queries, and this problem encountered me (after the other dev was fired).
I'm consuming a freight API that returns a list of "n" freight prices for each product/item in the requisition. The response classes are:
public class FreightSimulation
{
public List<Item> Items { get; set; }
}
public class Item
{
public int Id { get; set; }
public List<ItemFreight> Freights { get; set; }
}
public class ItemFreight
{
public Company Company { get; set; }
public Type Type { get; set; }
public double Price { get; set; }
public int Days { get; set; }
public List<Error> Errors { get; set; }
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Type
{
public int Id { get; set; }
public string Description { get; set; }
}
public class Error
{
public string Message { get; set; }
}
I have a response model for 2 items (but it can also be "n" items), one of them have 5 freights possibilities, with no errors, but the other has two errors meaning that one company doesn't have a freight service for that item:
var response = new FreightSimulation
{
Items = new List<Item>
{
new Item
{
Id = 1,
Freights = new List<ItemFreight>
{
new ItemFreight
{
Errors = null,
Price = 10,
Days = 8,
Company = new Company
{
Id = 1
},
Type = new Type
{
Id = 3
}
},
new ItemFreight
{
Errors = null,
Price = 20,
Days = 3,
Company = new Company
{
Id = 1
},
Type = new Type
{
Id = 8
}
},
new ItemFreight
{
Errors = null,
Price = 20,
Days = 10,
Company = new Company
{
Id = 2
},
Type = new Type
{
Id = 1
}
},
new ItemFreight
{
Errors = null,
Price = 35,
Days = 4,
Company = new Company
{
Id = 2
},
Type = new Type
{
Id = 2
}
},
new ItemFreight
{
Errors = null,
Price = 15,
Days = 6,
Company = new Company
{
Id = 7468
},
Type = new Type
{
Id = 1
}
}
}
},
new Item
{
Id = 2,
Freights = new List<ItemFreight>
{
new ItemFreight
{
Errors = null,
Price = 10,
Days = 8,
Company = new Company
{
Id = 1
},
Type = new Type
{
Id = 3
}
},
new ItemFreight
{
Errors = null,
Price = 20,
Days = 3,
Company = new Company
{
Id = 1
},
Type = new Type
{
Id = 8
}
},
new ItemFreight
{
Errors = new List<Error>
{
new Error
{
Message = "Not found."
}
},
Company = new Company
{
Id = 2
},
Type = new Type
{
Id = 1
}
},
new ItemFreight
{
Errors = new List<Erro>
{
new Error
{
Message = "Not found."
}
},
Company = new Company
{
Id = 2
},
Type = new Type
{
Id = 2
}
},
new ItemFreight
{
Errors = null,
Price = 22,
Days = 4,
Company = new Company
{
Id = 7468
},
Type = new Type
{
Id = 1
}
}
}
}
}
};
What I need at first is a Linq Query that brings a List where the children List are common between all the Items and doesn't have errors in it.
In this case I have something like:
Company Id Type Id Error
Item 1 1 3 N
Item 1 1 8 N
Item 1 2 1 N
Item 1 2 2 N
Item 1 7468 1 N
Item 2 1 3 N
Item 2 1 8 N
Item 2 2 1 Y
Item 2 2 2 Y
Item 2 7468 1 N
So in this case I need a list where the combinations would be only the company 1 and 7468 (could be "n" companies). So in item 2 I have errors on my company 2 response, so it has to be eliminated. The result would be:
Company Id Type Id Error
Item 1 1 3 N
Item 1 1 8 N
Item 1 7468 1 N
Item 2 1 3 N
Item 2 1 8 N
Item 2 7468 1 N
It could happen with any of the combination companies/types, so it has to be some kind of a dynamic query, if it's possible.
Thanks in advance.
You can get the companies that return an error for any freight:
var errorCompanyIds = response.Items
.SelectMany(x => x.Freights)
.Where(y => y.Errors !=null && y.Errors.Any())
.Select(y => y.Company.Id)
.ToList();
Then you can create your new list and filter any freight from that comapny
var newList = new FreightSimulation
{
Items = response.Items.Select(x => new Item
{
Id = x.Id,
Freights = x.Freights.Where(y => !errorCompanyIds.Contains(y.Company.Id)).ToList()
}).ToList()
};
This should work:
var companyIds = new[] { 1, 7468 };
var result = response.Items.SelectMany(i => i.Freights.Select(f => new { itemId = i.Id, Freight = f }))
.Where(i => companyIds.Contains(i.itemId) && (!(i.Freight.Errors?.Any() ?? false)));
What you are doing here is creating pairs to preserve the ids and then filtering those pairs based on your desired criteria.
Alternatively, you can filter the original list of freights first, then pick by company:
var companyIds = new[] { 1, 7468 };
var result = response.Items.SelectMany(i => i.Freights.Where(f => (!(f.Errors?.Any() ?? false)))
.Select(f => new { itemId = i.Id, Freight = f }))
.Where(i => companyIds.Contains(i.itemId));
Just in case this part is a bit confusing:
(!(i.Freight.Errors?.Any() ?? false))
This essentially translates to:
i.Freight.Errors == null || !i.Freight.Errors.Any()
Meaning freights with null errors or no errors (empty collection).

LINQ, GroupBy inner value of field which has type of object

Is it possible to group collection by inner property value? how would I do this without creating a new object using only Linq. Or I need to create anonymous object before grouping process.
public class Item
{
public int Id { get; set; }
public List<ItemInv> Inventory {get; set;}
}
public class ItemInv
{
public int wid {get; set;}
}
var lst = new List<Item> {
new Item {
Id=1,
Inventory= new List<ItemInv> { new ItemInv() { wid = 2 } }
},
new Item {
Id=2,
Inventory= new List<ItemInv> { new ItemInv() { wid = 2 }}
}
};
I need group this lst variable by wid property.
I think I have almost the same solution as Gilad Green, but in method syntax.
var itemsByWid = lst
.SelectMany(item => item.Inventory.Select(itemInv => Tuple.Create(itemInv, item)))
.GroupBy(itemTuple => itemTuple.Item1.wid, itemTuple => itemTuple.Item2);
To group the Items by the ItemInvs with in their collections, first flatten the nested collections and then group by the ItemInvs:
var result = (from item in list
from inv in item.Inventory
group item by inv.wid into invGrouping
select new {
ItemInv = invGrouping.Key,
Items = invGrouping.ToList()
}).ToList();
For testing:
// Testing data
var list = new List<Item> {
new Item {
Id=1,
Inventory= new List<ItemInv> { new ItemInv() { wid = 2 } }
},
new Item {
Id=2,
Inventory= new List<ItemInv> {
new ItemInv() { wid = 2 },
new ItemInv() { wid = 3 } }
},
new Item {
Id=3,
Inventory= new List<ItemInv> {
new ItemInv() { wid = 3 } }
} };
//Output:
// wid 2 - Item id=1, Item id = 2
// wid 3 - Item id=2, Item id = 3
See that currently the grouping is by the inv.wid. If you desire grouping by the ItemInv instance you must first override the Equals and GetHashCode.

Linq - group by using the elements inside an array property

I have a number of objects and each object has an array, I would like to group these objects by the values inside the array, so conceptually they look as follows:
var objects = new []{
object1 = new object{
elements = []{1,2,3}
},
object2 = new object{
elements = []{1,2}
},
object3 = new object{
elements = []{1,2}
},
object4 = new object{
elements = null
}
}
after grouping:
group1: object1
group2: object2,object3
group3: object4
somethings that I have tried:
actual classes:
public class RuleCms
{
public IList<int> ParkingEntitlementTypeIds { get; set; }
}
var rules = new List<RuleCms>()
{
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1,2}
},
new RuleCms()
{
ParkingEntitlementTypeIds = new []{1}
},
new RuleCms()
{
ParkingEntitlementTypeIds = null
}
};
var firstTry = rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds, rules = g })
.Where(x => x.Key.entitlementIds !=null && x.Key.entitlementIds.Equals(x.Key.rules.ParkingEntitlementTypeIds));
var secondTry =
rules.GroupBy(g => new { entitlementIds = g.ParkingEntitlementTypeIds ?? new List<int>(), rules = g })
.GroupBy(x => !x.Key.entitlementIds.Except(x.Key.rules.ParkingEntitlementTypeIds ?? new List<int>()).Any());
You can use IEqualityComparer class. Here is the code:
class MyClass
{
public string Name { get; set; }
public int[] Array { get; set; }
}
class ArrayComparer : IEqualityComparer<int[]>
{
public bool Equals(int[] x, int[] y)
{
return x.SequenceEqual(y);
}
public int GetHashCode(int[] obj)
{
return string.Join(",", obj).GetHashCode();
}
}
Then
var temp = new MyClass[]
{
new MyClass { Name = "object1", Array = new int[] { 1, 2, 3 } },
new MyClass { Name = "object2", Array = new int[] { 1, 2 } },
new MyClass { Name = "object3", Array = new int[] { 1, 2 } },
new MyClass { Name = "object4", Array =null }
};
var result = temp.GroupBy(i => i.Array, new ArrayComparer()).ToList();
//Now you have 3 groups
For simple data that really is as simple as your example you could do this:
.GroupBy(x => string.Join("|", x.IDS))
.Select(x => new
{
IDS = x.Key.Split('|').Where(s => s != string.Empty).ToArray(),
Count = x.Count()
});

How to set a value in one list when comparing it with other list using LINQ

I have two lists:
List A List B
ID FirstName WorkingID ID FirstName WorkingID
5 John Null 5 John 1
9 Patrick Null 9 Patrick 2
16 Ryan Null 16 Ryan 3
I want to compare these lists using LINQ and if IDs from both lists are equal, I want to set WorkingID in the first list, A list.
Thanks.
Also like this:
A.ForEach(c => c.WorkingID = B.Where(m => m.ID == c.ID).Select(s => s.WorkingID).FirstOrDefault());
Full source:
public class Test
{
public int ID;
public int? WorkingID;
}
class Program
{
static void Main(string[] args)
{
var A = new List<Test>()
{
new Test { ID = 5, WorkingID = null },
new Test { ID = 9, WorkingID = null },
new Test { ID = 16, WorkingID = null },
new Test { ID = 18, WorkingID = null }
};
var B = new List<Test>()
{
new Test { ID = 5, WorkingID = 1 },
new Test { ID = 9, WorkingID = 2 },
new Test { ID = 16, WorkingID = 3 }
};
A.ForEach(c => c.WorkingID = B.Where(m => m.ID == c.ID).Select(s => s.WorkingID).FirstOrDefault());
}
}
Try this:
listA.Where(a => listB.Any(b => a.ID == b.ID))
.ToList()
.ForEach(a => a.WorkingID = listB.First(b => b.ID == a.ID).WorkingID);
You could try something like this (not a pure LINQ approach):
foreach(var item in listA)
{
var itemInB = listB.FirstOrDefault(x=>x.ID==item.ID);
if(itemInB!=null)
item.WorkingID = itemInB.WorkongID
}
var query = from a in lista
join b in listb on a.ID equals b.ID
where b.WorkingID != null
select new {a, b};
foreach (var item in query)
{
item.a.WorkingID = item.b.WorkingID;
}
easy as that.
Here is one way, assuming a class structure as follows:
class MyItem
{
public int ID { get; set; }
public string FirstName { get; set; }
public int WorkingID { get; set; }
}
You can join the second list on matching IDs:
var list1 = new List<MyItem>() {
new MyItem() { ID = 5, FirstName = "John" },
new MyItem() { ID = 9, FirstName = "Patrick" },
new MyItem() { ID = 16, FirstName = "Ryan" },
};
var list2 = new List<MyItem>() {
new MyItem() { ID = 5, FirstName = "John", WorkingID = 1 },
new MyItem() { ID = 9, FirstName = "Patrick", WorkingID = 2 },
new MyItem() { ID = 16, FirstName = "Ryan", WorkingID = 3 },
};
var mergedList = from item1 in list1
join item2 in list2
on item1.ID equals item2.ID
select new MyItem() { ID = item1.ID, FirstName = item1.FirstName, WorkingID = item2.WorkingID };
I think using Join then looping through the list would be more efficient. The below is practically the same as the answer provided by Florian Schmidinger:
A.AsEnumerable().Join(B.AsEnumerable(), a => a.ID, b => b.ID, (a, b) => new { a, b })
.ToList().ForEach(obj => { obj.a.WorkingID = obj.b.WorkingID; });

Categories