Linq to Collection - Dynamic Where Clause - c#

I have a WPF applicaion of which i would like to populate a treeview. Before this happens I would like the user to be able to select any of the fields/properties available in the collection from a drop down that will then populate the tree with data and grouped appropriately.
EXAMPLE Object
public class class_01{
public string Property_A {get; set;}
public string Property_B {get; set;}
public string Property_C {get; set;}
}
EXAMPLE Collection
List<class_01> list_01 = new List<class_01>();
Again, the drop down will be bound with what ever properties are available from the list. This way if the list where to change the application would not require modification. This is a big requirement for the answer i need.
Lets say the user selects "Property_A".
I would like a linq query method that looks something like this.
Linq Query
public groupingModel getGrouping(string groupBy) // groupby equals property A in this example
{
List<class_01> list = getList(); //Returns the list of data of type class_01
var q = from x in w where ????? == groupBy select x; // I dont want to specify a specific property but rather have one applied dynamically
return q;
}
I have a custom object that the query would then be parsed into. that looks similar to the following.
Custom Object
public class class_02{
public string header {get; set;} // will be set to the discrete values of the selected groupby property
public List<class_01> prop_a {get; set;}
}
This would then be bound to the tree appropriately.
Any Thoughts?
EDIT
Additionally how would I get a list of the unique value for the property the user selects.
for example
{a = 1, b =2, c =3}, {a = 2, b = 3, c = 4}
if the user decides to group on property "a" how would we produce a collection of [1,2]?
This will be needed to construct a where clause.
foreach(value of user selected property){
string whereClause = string.format("{0} = {1}",selected property, value")
}
EDIT - Catching exception from Dynamic Query
public List<groupingModel> getGrouping(string groupBy)
{
List<groupingModel> lgm = new List<groupingModel>();
//Categories.Select(c => c.Id).ToList()
var w2 = getWVWellsList();
//var v = w2.Select(groupBy).Distinct().Cast<string>().ToArray();
var v = w2.Select(groupBy).Distinct();
foreach(string val in v)
{
string whereClause = string.Format("{0} = {1}", groupBy, val);
try
{
IEnumerable<WVWellModel> q2 = w2.Where(whereClause);
List<WVWellModel> l = q2.ToList<WVWellModel>();
lgm.Add(new groupingModel { Header = val, Wells = l });
}
catch (Exception e)
{
MessageBox.Show(e.Message, "Query Error", MessageBoxButton.OK, MessageBoxImage.Error);
throw new Exception("Generic Exception - Issue With Group By Query", e);
}
}
return lgm;
}
Exception
"No Property or field "Colorado" Exists in type WVWellModel"
In the case of this example i can confirm that my where clause was "State = Colorado". It appears the query is applying the value as opposed to the property state which is apart of that type. It is as if it is reversed when the query is called.

Check at the post of Scott Gu
https://weblogs.asp.net/scottgu/dynamic-linq-part-1-using-the-linq-dynamic-query-library

Related

Merge or zip different list of objects having different structures into single list

Hi all i have three classes like this as below
class A
{
public int number{get; set;}
public string name{get; set;}
}
class B
{
public string casted{get; set;}
}
class C
{
public int Id{get; set;}
public bool isSelect {get; set;}
}
and i have data format like this
var aObject = new A () { number = 11, name = "John" };
var aObject2= new A () { number = 22, name = "Steve" };
IList<A> ListA= new List<A>(){ aObject,aObject2 };
var bObject1 = new B () { casted = "test" };
var bObject2 = new B () { casted = "test1" };
var bObject3 = new B () { casted = "test2" };
IList<B> ListB = new List<B>(){ bObject1 , bObject2 ,bObject3 };
var cObject = new C() { Id = "1", isSelect = true };
var cObject2 = new C(){ Id = "2", isSelect = false };
IList<C> ListC = new List<C>() { cObject ,cObject2 };
All are having different structure and i will be getting data in list of above classes like List<A>, List<B> and List<C>
I am looking to form a data kind of structure below with these three lists merged into single one like as below
I am looking for result list looks like as below.
Result :
Number name casted Id isSelect
11 john test 1 true
22 Steve test1 2 false
- - test2 - -
Is there any way i can achieve this kind of result object, I know if you have same structure we can concatenate the list but here i have different structure.
Could any one please let me know any ideas on how to do achieve this that would be very grateful to me Many thanks in advance
Addition after comment: in the while part of my code I forgot to MoveNext. Repaired. Thanks enigma state for noticing.
So you have three sequences of different items, and you want a method, that returns a sequence containing the same indexed elements of your sequences.
This is a difficult way to say, that you want a sequence like:
A[0] / B[0] / C[0]
A[1] / B[1] / C[1]
A[2] / null / C[2]
etc.
So if one of the sequences runs out of elements, you want to continue enumerating using NULL as value
This method is very similar to Enumerable.Zip, except that you have three sequences, and you want to continue enumerating if one of the sequences is too short.
Whenever you think there is a missing LINQ method, consider writing an extension method for IEnumerable. Especially if you think you can reuse it in other situations
Creating an extension method for an IEnumerable is usually fairly simple. See extension methods demystified
I'll write a generic extension method, that has three input sequences, and a result selector to define the returned output sequence. This result selector is similar to the selector parameter in Enumerable.Select.
public static IEnumerable<TResult> ZipWithNull<T1, T2, T2, TResult>(
this IEnumerable<T1> source1,
IEnumerable<T2> source2,
IEnumerable<T3> source3,
Func<T1, T2, T3, TResult> resultSelector)
{
// get the enumerators and start enumerating until there are no more elements
IEnumerator<T1> enumerator1 = source1.GetEnumerator();
IEnumerator<T2> enumerator2 = source2.GetEnumerator();
IEnumerator<T3> enumerator3 = source3.GetEnumerator();
// check if there is at least one item available
bool t1Available = enumerator1.MoveNext();
bool t2Available = enumerator2.MoveNext();
bool t3Available = enumerator3.MoveNext();
bool anyAvailabe = t1Available || t2Available || t3Available;
while (anyAvailable)
{
// if available use the Current, else use default (= null for classes)
T1 t1 = t1Available ? enumerator1.Current ?? default(T1);
T2 t2 = t2Available ? enumerator2.Current ?? default(T2);
T3 t3 = t3Available ? enumerator3.Current ?? default(T3);
TResult result = resultSelector(t1, t2, t3);
yield return result;
t1Available = enumerator1.MoveNext();
t2Available = enumerator2.MoveNext();
t3Available = enumerator3.MoveNext();
anyAvailabe = t1Available || t2Available || t3Available;
}
}
Usage:
List<A> listA = ...
List<B> listA = ...
List<C> listA = ...
// you want a dash if the value is null
const string dash = "-";
var result = listA.ZipWithNull(listB, listC,
// parameter ResultSelector: taks one a, b, c and create the output:
(a, b, c) => new
{
Number = a?.Number.ToString() ?? dash,
Name = a?.Name ?? dash,
Casted = b?.Casted ?? dash,
Id = c?.Id.ToString() ?? dash,
IsSelect = c?.IsSelect.ToString() ?? dash,
});
Do note that this result selector can be optimized. For instance, whether parameter a equals null is checked twice. With a little more effort you can make sure that every input of the resultSelector is checked only once. For the example I chose simplicity above efficiency.
The nice thing is that you can use ZipWithNull for any sequence that you want to zip with null values as substitute. Because I created ZipWithNull as any other LINQ method, I can intertwine it with other LINQ methods:
var result = customers
.Where(customer => customer.BirthDay.Year >= 2000)
.ZipWithNull(
addresses.Where(address => address.City == "Amsterdam"),
orders.Where(order => order.Total > 1000,
(customer, address, order) => new {...});
.GroupBy(...)
.OrderByDescending(...)
// Etc();
It's not very clear what you are trying to do, but I see 3 ways you could possibly accomplish this.
Most simply, you can create a List<object> that contains your data of different classes.
You would then lose the information about what class each object is (you could store these objects in the List as Tuple with Type).
List<object> joinedList = new List<object>();
joinedList.AddRange(ListA.Cast<object>());
joinedList.AddRange(ListB.Cast<object>());
joinedList.AddRange(ListC.Cast<object>());
Another solution would be to have all of these classes share a base class or interface and construct the List of that type.
When reading from the List you could check types like listItem is A
class z
{
}
class a : z
{
public int number{get; set;}
public string name{get; set;}
}
class B : z
{
public string casted{get; set;}
}
class c : z
{
public int Id{get; set;}
public bool isSelect {get; set;}
}
List<z> joinedList = new List<z>();
joinedList.AddRange(ListA);
joinedList.AddRange(ListB);
joinedList.AddRange(ListC);
You could also create a "joined" version of A, B, and C which might suit your use-case (since it looks like you might be creating a table from the data).
class Aggregate
{
A aData {get;set;}
B bData {get;set;}
C cData {get;set;}
}
List<Aggregate> aggregatedData = new List<Aggregate>();
// join your data together into aggregate and add to list
So you have a couple of challenges in that the models you provided don't match the lists you provided (e.g. classes are A and C not a and c and Id on C should be string not int), but after changing all that we can do sth like this:
private static void AddAllValuesToDictionary<T>(List<Dictionary<string, object>> target, IList<T> source) {
var properties = typeof(T).GetProperties();
for (var i = 0; i < source.Count(); i++)
{
if (target.Count() <= i) {
target.Add(new Dictionary<string, object>());
}
foreach(var prop in properties) {
target[i][prop.Name] = prop.GetValue(source[i]);
}
}
}
And then
var result = new List<Dictionary<string, object>>();
AddAllValuesToDictionary(result, ListA);
AddAllValuesToDictionary(result, ListB);
AddAllValuesToDictionary(result, ListC);
If you kind of really wanted an object instead of a dictionary, you could go for ExpandoObject but then essentially you will get experience reverse to the above.
In my listing, if two objects (e.g. 'A' and 'C') have a duplicate Property Name, the latter will override the former. With ExpandoObject the first value you set will be the one that comes in the result.
Update Here's an example with Expando
Add using System.Dynamic; to your file.
Then
private static void AddAllValuesToListOfExpandos<T>(List<ExpandoObject> target, IList<T> source) {
var properties = typeof(T).GetProperties();
for (var i = 0; i < source.Count(); i++)
{
if (target.Count() <= i) {
target.Add(new ExpandoObject());
}
foreach(var prop in properties) {
target[i].TryAdd(prop.Name, prop.GetValue(source[i]));
}
}
}
}
var result2 = new List<ExpandoObject>();
AddAllValuesToListOfExpandos(result2, ListA);
AddAllValuesToListOfExpandos(result2, ListB);
AddAllValuesToListOfExpandos(result2, ListC);
// Since you're using ExpandoObject you can now use the properties
// the same way you do with dynamic
Console.WriteLine(((dynamic)result2.First()).casted);
Console.WriteLine(((dynamic)result2.First()).number);
Console.WriteLine(((dynamic)result2.Last()).casted);
// note: Next line will fail with RuntimeBinderException
Console.WriteLine(((dynamic)result2.Last()).number);

Grouping and sum

I have a list as follows which will contain the following poco class.
public class BoxReportView
{
public DateTime ProductionPlanWeekStarting { get; set; }
public DateTime ProductionPlanWeekEnding { get; set; }
public string BatchNumber { get; set; }
public string BoxRef { get; set; }
public string BoxName { get; set; }
public decimal Qty { get; set; }
public FUEL_KitItem KitItem { get; set; }
public decimal Multiplier { get; set; }
}
I am wanting to group the report and sum it by using the BoxName and also the Qty SO I tried the following
var results = from line in kitItemsToGroup
group line by line.BoxName into g
select new BoxReportView
{
BoxRef = g.First().BoxRef,
BoxName = g.First().BoxName,
Qty = g.Count()
};
In My old report I was just doing this
var multiplier = finishedItem.SOPOrderReturnLine.LineQuantity -
finishedItem.SOPOrderReturnLine.StockUnitDespatchReceiptQuantity;
foreach (KitItem kItem in kitItems.Cast<KitItem().Where(z => z.IsBox == true).ToList())
{
kittItemsToGroup.Add(new BoxReportView() {
BatchNumber = _batchNumber,
ProductionPlanWeekEnding = _weekEndDate,
ProductionPlanWeekStarting = _weekStartDate,
BoxRef = kItem.StockCode,
KitItem = kItem,
Multiplier = multiplier,
Qty = kItem.Qty });
}
}
Then I was just returning
return kitItemsToGroup;
But as I am using it as a var I cannot what is best way to handle the grouping and the sum by box name and qty.
Whether it is the best way depends upon your priorities. Is processing speed important, or is it more important that the code is easy to understand, easy to test, easy to change and easy to debug?
One of the advantages of LINQ is, that it tries to avoid enumeration of the source more than necessary.
Are you sure that the users of this code will always need the complete collection? Can it be, that now, or in near future, someone only wants the first element? Or decides to stop enumeration after he fetched the 20th element and saw that there was nothing of interest for him?
When using LINQ, try to return IEnumerable<...> as long as possible. Let only the end-user who will interpret your LINQed data decide whether he wants to take only the FirstOrDefault(), or Count() everything, or put it in a Dictionary, or whatever. It is a waste of processing power to create a List if it is not going to be used as a List.
your LINQ code and your foreach do some completely different things. Alas it is quite common here on StackOverflow for people to ask for LINQ statements without really specifying their requirements. So I'll have to guess something in between your LINQ statement and your foreach.
Requirement Group the input sequence of kitItems, which are expected to be Fuel_KitItems into groups of BoxReportViews with the same BoxName, and select several properties from every Fuel_KitItem in each group.
var kitItemGroups = kitItems
.Cast<Fuel_KitItem>() // only needed if kitItems is not IEnumerable<Fuel_KitItem>
// make groups of Fuel_KitItems with same BoxName:
.GroupBy(fuelKitItem => fuelKitItem.BoxName,
// ResultSelector, take the BoxName and all fuelKitItems with this BoxName:
(boxName, fuelKitItemsWithThisBoxName) => new
{
// Select only the properties you plan to use:
BoxName = boxName,
FuelKitItems = fuelKitItemsWithThisBoxName.Select(fuelKitItem => new
{
// Only Select the properties that you plan to use
BatchNumber = fuelKitItem.BatchNumber,
Qty = fuelKitItem.Qty,
...
// Not needed, they are all equal to boxName:
// BoxName = fuelKitItem.BoxName
})
// only do ToList if you are certain that the user of the result
// will need the complete list of fuelKitItems in this group
.ToList(),
});
Usage:
var kitItemGroups = ...
// I only need the KitItemGroups with a BoxName starting with "A"
var result1 = kitItemGroups.Where(group => group.BoxName.StartsWith("A"))
.ToList();
// Or I only want the first three after sorting by group size
var result2 = kitItemGroups.OrderBy(group => group.FuelKitItems.Count())
.Take(3)
.ToList();
Efficiency Improvements: As long as you don't know how your LINQ will be used, don't make it a List. If you know that chances are high that the Count of group.FuelKitItems is needed, to a ToList

Updating entire node with mutating cypher in Neo4jclient

I need to update all the properties of a given node, using mutating cypher. I want to move away from Node and NodeReference because I understand they are deprecated, so can't use IGraphClient.Update. I'm very new to mutating cypher. I'm writing in C#, using Neo4jclient as the interface to Neo4j.
I did the following code which updates the "Name" property of a "resunit" where property "UniqueId" equals 2. This works fine. However,
* my resunit object has many properties
* I don't know which properties have changed
* I'm trying to write code that will work with different types of objects (with different properties)
It was possible with IGraphClient.Update to pass in an entire object and it would take care of creating cypher that sets all properies.
Can I somehow pass in my object with mutating cypher as well?
The only alternative I can see is to reflect over the object to find all properties and generate .Set for each, which I'd like to avoid. Please tell me if I'm on the wrong track here.
string newName = "A welcoming home";
var query2 = agencyDataAccessor
.GetAgencyByKey(requestingUser.AgencyKey)
.Match("(agency)-[:HAS_RESUNIT_NODE]->(categoryResUnitNode)-[:THE_UNIT_NODE]->(resunit)")
.Where("resunit.UniqueId = {uniqueId}")
.WithParams(new { uniqueId = 2 })
.With("resunit")
.Set("resunit.Name = {residentialUnitName}")
.WithParams(new { residentialUnitName = newName });
query2.ExecuteWithoutResults();
It is indeed possible to pass an entire object! Below I have an object called Thing defined as such:
public class Thing
{
public int Id { get; set; }
public string Value { get; set; }
public DateTimeOffset Date { get; set; }
public int AnInt { get; set; }
}
Then the following code creates a new Thing and inserts it into the DB, then get's it back and updates it just by using one Set command:
Thing thing = new Thing{AnInt = 12, Date = new DateTimeOffset(DateTime.Now), Value = "Foo", Id = 1};
gc.Cypher
.Create("(n:Test {thingParam})")
.WithParam("thingParam", thing)
.ExecuteWithoutResults();
var thingRes = gc.Cypher.Match("(n:Test)").Where((Thing n) => n.Id == 1).Return(n => n.As<Thing>()).Results.Single();
Console.WriteLine("Found: {0},{1},{2},{3}", thingRes.Id, thingRes.Value, thingRes.AnInt, thingRes.Date);
thingRes.AnInt += 100;
thingRes.Value = "Bar";
thingRes.Date = thingRes.Date.AddMonths(1);
gc.Cypher
.Match("(n:Test)")
.Where((Thing n) => n.Id == 1)
.Set("n = {thingParam}")
.WithParam("thingParam", thingRes)
.ExecuteWithoutResults();
var thingRes2 = gc.Cypher.Match("(n:Test)").Where((Thing n) => n.Id == 1).Return(n => n.As<Thing>()).Results.Single();
Console.WriteLine("Found: {0},{1},{2},{3}", thingRes2.Id, thingRes2.Value, thingRes2.AnInt, thingRes2.Date);
Which gives:
Found: 1,Foo,12,2014-03-27 15:37:49 +00:00
Found: 1,Bar,112,2014-04-27 15:37:49 +00:00
All properties nicely updated!

List<object> Self-Filter

I have a list like
List<VoieData> listVoieData = new List<VoieData>();
and in VoieData Class I have :
public class VoieData
{
public int Depart { set; get; }
public int Arrive { set; get; }
public int DistanceDepart { set; get; }
public int DistanceArrive { set; get; }
}
Since I have a massive values I want to only consider all my Depart number , I would like to filter the listVoieData by finding the Arrive only have the same value as the
Depart
for example I have
listVoieData.Select(p=>p.Depart).ToList()= List<int>{1,2,3};
listVoieData.Select(p=>p.Arrive).ToList()= List<int>{1,2,3,4,5};
I need to throw away the entire VoieData which contain {4,5} as Arrive
right now my soulution is like this , but it' s not correct ;
List<VoieData> listVoieDataFilter = listVoieData .Join(listVoieData , o1 => o1.Arrive, o2 => o2.Depart, (o1, o2) => o1).ToList();
Sorry for the confusing question ;
I want to remove Arrive which is different from all the Depart in the list list , and return the new
List
it 's not only in one VoieData;
Arrive!=Depart
Thanks
I think you want to remove all objects where Arrive is not in any of the Depart from any object. In that case, first get all Depart and then filter by Arrive:
HashSet<int> allDepart = new HashSet<int>(listVoieData.Select(x => x.Depart));
var result = listVoieData.Where(v => !allDepart.Contains(v.Arrive))
We use a HashSet<int> for efficiency.
Use LINQ Where:
var records = listVoieData.Where(x => x.Arrive == x.Depart);
This will return results where both Arrive and Depart are the same.
That would be a typical case to use linq.
something like:
var res = from data in listVoieData
where data.Depart == data.Arrive
select data;
and then optionally just use res.ToArray() to run the query and get the array.
Since you've stated that you want:
I want to remove Arrive which is different from all the Depart
This can be re-phrased as, "The set of all arrivals except those in the set of departures", which translates very nicely into the following LINQ query:
var arrivalsWithNoDepartures = listVoieData.Select(p=>p.Arrive)
.Except(listVoieData.Select(p=>p.Depart));

LINQ and creating NON anonymous return values

I think I understand returning records of an anonymous type from But in this I want to create NEW CatalogEntries, and set them from the values selected. (context is a Devart LinqConnect database context, which lets me grab a view).
My solution works, but it seems clumsy. I want to do this in one from statement.
var query = from it in context.Viewbostons
select it;
foreach (GPLContext.Viewboston item in query)
{
CatalogEntry card = new CatalogEntry();
card.idx = item.Idx;
card.product = item.Product;
card.size = (long)item.SizeBytes;
card.date = item.Date.ToString();
card.type = item.Type;
card.classification = item.Classification;
card.distributor = item.Distributor;
card.egplDate = item.EgplDate.ToString();
card.classificationVal = (int)item.ClassificationInt;
card.handling = item.Handling;
card.creator = item.Creator;
card.datum = item.Datum;
card.elevation = (int)item.ElevationFt;
card.description = item.Description;
card.dirLocation = item.DoLocation;
card.bbox = item.Bbox;
card.uniqID = item.UniqId;
values.Add(card);
}
CatalogResults response = new CatalogResults();
I just tried this:
var query2 = from item in context.Viewbostons
select new CatalogResults
{ item.Idx,
item.Product,
(long)item.SizeBytes,
item.Date.ToString(),
item.Type,
item.Classification,
item.Distributor,
item.EgplDate.ToString(),
(int)item.ClassificationInt,
item.Handling,
item.Creator,
item.Datum,
(int)item.ElevationFt,
item.Description,
item.DoLocation,
item.Bbox,
item.UniqId
};
But I get the following error:
Error 79 Cannot initialize type 'CatalogService.CatalogResults' with a
collection initializer because it does not implement
'System.Collections.IEnumerable' C:\Users\ysg4206\Documents\Visual
Studio
2010\Projects\CatalogService\CatalogService\CatalogService.svc.cs 91 25 CatalogService
I should tell you what the definition of the CatalogResults is that I want to return:
[DataContract]
public class CatalogResults
{
CatalogEntry[] _results;
[DataMember]
public CatalogEntry[] results
{
get { return _results; }
set { _results = value; }
}
}
My mind is dull today, apologies to all. You are being helpful. The end result is going to be serialized by WCF to a JSON structure, I need the array wrapped in a object with some information about size, etc.
Since .NET 3.0 you can use object initializer like shown below:
var catalogResults = new CatalogResults
{
results = context.Viewbostons
.Select(it => new CatalogEntry
{
idx = it.Idx,
product = it.Product,
...
})
.ToArray()
};
So if this is only one place where you are using CatalogEntry property setters - make all properties read-only so CatalogEntry will be immutable.
MSDN, Object initializer:
Object initializers let you assign values to any accessible fields or properties of an
object at creation time without having to explicitly invoke a constructor.
The trick here is to create a IQueryable, and then take the FirstOrDefault() value as your response (if you want a single response) or ToArray() (if you want an array). The error you are getting (Error 79 Cannot initialize type 'CatalogService.CatalogResults' with a collection initializer because it does not implement 'System.Collections.IEnumerable') is because you're trying to create an IEnumerable within the CatalogEntry object (by referencing the item variable).
var response = (from item in context.Viewbostons
select new CatalogEntry()
{
idx = item.Idx,
product = item.Product,
size = (long)item.SizeBytes,
...
}).ToArray();
You don't have to create anonymous types in a Linq select. You can specify your real type.
var query = context.Viewbostons.Select( it =>
new CatalogEntry
{
idx = it.idx,
... etc
});
This should work:
var query = from it in context.Viewbostons
select new CatalogEntry()
{
// ...
};

Categories