Strawberry Strawberry - 25 days ago 8
C# Question

System.Linq.Dynamic.Core : How do I make a select many correctly?

I am new to the System.Linq.Dynamic.Core. I have this:

Let's say we have:

Packs = new List<Pack>
{
new Pack()
{
IdAtSource="Pack1",
Equipments= new List<Equipment>()
{
new Equipment
{
Id=1,
GenericEquipment = new GenericEquipment()
{
Id=7
}
}
}
},
new Pack()
{
IdAtSource="Pack2",
Equipments= new List<Equipment>()
{
new Equipment
{
Id=2,
GenericEquipment = new GenericEquipment()
{
Id=1
}
},
new Equipment
{
Id=2,
GenericEquipment = new GenericEquipment()
{
Id=2
}
}
}
}
}


I would like to select the
Packs
with
Equipments
, but in the selected
Equipments
I need to have only the one with
Id=2
for Generic Equipment.(the result should contain a list of packs with list of equipments).

I've tried this:

querable.Where("Packs.Equipments.Select((GenericEquipment.Id)=1)");


but I feel I am waaay of target here. Also is there any documentation page on how to use this library?

Thanks a lot

Answer

The most recent version of Dynamic LINQ appears to be the project here. A previous incarnation had hosted documentation including the expression language used by Dynamic LINQ, but that link is no longer available. However, if you download and run the code from tbis project, you can access that documentation (set web/DynamicLinqWebDocs/DynamicLinqWebDocs.csproj as the startup project). Everything you need is on the Getting Started link.


Generally, the only reason why you should require Dynamic LINQ over standard LINQ is when the types are not known at compile time.

If you know at compile time that the query will be against a List<Pack>, you can use stanadard LINQ, as in the following code (note that this modifies the original instances of Pack):

var usefulPacks = Packs.Select(pack => {
    pack.Equipments = pack.Equipments.Where(equipment => 
        equipment.GenericEquipment.Id == 1
    ).ToList(); 
}).Where(pack => pack.Equipments.Any()).ToList();

If you need code that doesn't modify the original instances, this code creates copies of the original instances:

var usefulPacks = Packs.Select(pack => {
    return new Pack() {
        IDAtSource = pack.IDAtSource,
        Equipments = pack.Equipments.Where(equipment => 
            equipment.GenericEquipment.Id == 1
        ).ToList(); 
    };
}).Where(pack => pack.Equipments.Any()).ToList();

Note that this isn't using .SelectMany.SelectMany is used to create a single enumerable from nested enumerables; here each Pack in the final list corresponds to a Pack in the original list.


Dynamic LINQ doesn't support modifying or initializing properties as part of the expression:

The expression language permits getting (but not setting) the value of any reachable public field, property, or indexer.

so the Equipments property cannot be changed / initialized to include only instances of Equipment that match the criteria.

In order to set the Equipments, you have two choices:

  1. Add a constructor to Pack which takes the appropriate arguments
  2. Write a static method on any class which takes the appropriate arguments

Add a constructor to Pack

You can add a constructor to Pack with the appropriate arguments, that sets Equipments:

Pack(int IDAtSource, IEnumerable<Equipment> equipments) {
    this.IDAtSource = IDAtSource;
    this.Equipments = equipments.ToList();
}

Then you could use the following:

IQueryable qry = Packs.AsQueryable();
qry = qry
    .Select("Pack(IDAtSource, Equipments.Where(GenericEquipment.ID=1))")
    .Where("Equipments.Any");

Define a static method

public static class MyPackMethods {
    public static Pack Create(int IDAtSource, IEnumerable<Equipment> equipments) {
        return new Pack() {
            IDAtSource = IDAtSource,
            Equipments = equipments.ToList()
        };
    }
}

and call:

IQueryable qry = Packs.AsQueryable();
qry = qry
    .Select("MyPackMethods.Create(IDAtSource, Equipments.Where(GenericEquipment.ID=1))")
    .Where("Equipments.Any");