Create expression tree to assign to property of list - c#

This has been plaguing me for days now....
If I have a list of my own object SearchResults and SearchResults contains multiple lists of objects, all of which have a match (bool) property, How can I recreate an expression tree to achieve the following:
//searchResults is a List<SearchResults>
searchResults[i].Comments = searchResults[i].Comments.Select(p1 =>
{
p1.Match = ListOfStringVariable.All(p2 =>
{
string value = (string)typeof(CommentData).GetProperty(propertyName).GetValue(p1);
return value.Contains(p2);
});
return p1;
}).OrderByDescending(x => x.Match);
....
public class SearchResults
{
public IEnumerable<CommentData> Comments { get; set; }
public IEnumerable<AdvisorData> Advisors { get; set; }
}
public class CommentData
{
public string CommentText { get; set; }
public bool Match { get; set; }
}
public class AdvisorData
{
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Match { get; set; }
}
The expression tree is needed as I won't know the property at compile-time that needs to be assigned, whether it is Comments, Advisors, etc (As this is a simplification of a larger problem). The above example is just for Comments, so how could the same code be used to assign to Advisors as well without having a conditional block?
Many thanks
Update:
So far using reflection we have the below from StriplingWarrior
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}
However orderedItems is of type System.Linq.OrderedEnumerable<IHaveMatchProperty,bool> and needs to be cast to IEnumerable<AdvisorData>. The below throws error:
'System.Linq.Enumerable.CastIterator(System.Collections.IEnumerable)' is a 'method' but is used like a 'type'
var castMethod = typeof(Enumerable).GetMethod("Cast").MakeGenericMethod(new[] {propertyType});
var result = castMethod.Invoke(null, new[] { orderedItems });
where propertyType is type AdvisorData

First, make your types implement this interface so you don't have to do quite so much reflection:
public interface IHaveMatchProperty
{
bool Match { get; set; }
}
Then write code to do something like this. (I'm making a lot of assumptions because your question wasn't super clear on what your intended behavior is.)
var searchResult = searchResults[i];
foreach (var srProperty in searchResultsProperties)
{
var collectionType = srProperty.PropertyType;
if(!collectionType.IsGenericType || collectionType.GetGenericTypeDefinition() != typeof(IEnumerable<>))
{
throw new InvalidOperationException("All SearchResults properties should be IEnumerable<Something>");
}
var itemType = collectionType.GetGenericArguments()[0];
var itemProperties = itemType.GetProperties().Where(p => p.Name != "Match");
var items = ((IEnumerable<IHaveMatchProperty>) srProperty.GetValue(searchResult))
// Materialize the enumerable, in case it's backed by something that
// would re-create objects each time it's iterated over.
.ToList();
foreach (var item in items)
{
var propertyValues = itemProperties.Select(p => (string)p.GetValue(item));
item.Match = propertyValues.Any(v => searchTerms.Any(v.Contains));
}
var orderedItems = items.OrderBy(i => i.Match);
srProperty.SetValue(srProperty, orderedItems);
}

Related

How to turn an object into a request in C#

My Model, example:
public class Obj
{
public Obj()
{ }
public int? IdRestricao { get; set; }
public int? IdTipoRestringido { get; set; }
public string CodRestringido { get; set; }
public string NomeRestringido { get; set; }
public int? IdTipoRestricao { get; set; }
public string CodRestricao { get; set; }
public string NomeRestricao { get; set; }
public DateTime? PeriodoInicio { get; set; }
public DateTime? PeriodoFim { get; set; }
public int? IdStatus { get; set; }
}
Request
www.url.com.br?IdRestricao=1&IdTipoRestringido=2&CodRestringido=3&NomeRestringido=4&IdTipoRestricao=5&CodRestricao=6&NomeRestricao=7&PeriodoInicio=8&PeriodoFim=9
code:
var model = obj.*tostring()*//
something like
var request = new RestRequest($"/api/{**model**}", Method.GET);
edit1:
I'm wondering if there is a library or something that transforms my object in my request as in the examples above
You can override ToString to format the data from the Obj class into GET parameters.
You could write a utility function to convert all object properties into nameof and the associated value into key-value pairs.
Something such as this answer to convert your Obj into a dictionary, and then another method to convert the dictionary into Prop=value could be done.
For a one off something like this would be suitable:
public override string ToString()
{
return $"IdRestricao={IdRestricao}&IdTipoRestringido={IdTipoRestringido}&CodRestringido={CodRestringido}...
}
I found the answer to what I was looking for, it follows below in case anyone needs it in the future
private static string ToQueryString(this object request, string separator = ",")
{
if (request == null)
throw new ArgumentNullException("request");
// Get all properties on the object
var properties = request.GetType().GetProperties()
.Where(x => x.CanRead)
.Where(x => x.GetValue(request, null) != null)
.ToDictionary(x => x.Name, x => x.GetValue(request, null));
// Get names for all IEnumerable properties (excl. string)
var propertyNames = properties
.Where(x => !(x.Value is string) && x.Value is IEnumerable)
.Select(x => x.Key)
.ToList();
// Concat all IEnumerable properties into a comma separated string
foreach (var key in propertyNames)
{
var valueType = properties[key].GetType();
var valueElemType = valueType.IsGenericType
? valueType.GetGenericArguments()[0]
: valueType.GetElementType();
if (valueElemType.IsPrimitive || valueElemType == typeof(string))
{
var enumerable = properties[key] as IEnumerable;
properties[key] = string.Join(separator, enumerable.Cast<object>());
}
}
// Concat all key/value pairs into a string separated by ampersand
return string.Join("&", properties
.Select(x => string.Concat(
Uri.EscapeDataString(x.Key), "=",
Uri.EscapeDataString(x.Value.ToString()))));
}

C# LINQ Filter list of complex objects by sub-list using a list of values

I want to return a list of active groups that are discounted in requested states. The list of groups each have a list of states which include the state abbrev and a discount flag.
filter criteria:
string[] states //list of state abbreviations
List to filter:
public class WorksiteGroup
{
public long Id { get; set; }
public string Name { get; set; }
public string Type { get; set; }
public bool IsDiscontinued { get; set; }
public List<WorksiteGroupState> ActiveStates { get; set; } = new List<WorksiteGroupState>();
}
public class WorksiteGroupState
{
public string StateAbbrev { get; set; }
public bool IsDiscountApplied { get; set; }
}
Again, I want to return a list of WorksiteGroup with the full structure above where IsDiscontinued is false and have an ActiveState where StateAbbrev matches any of the filter criteria (states[]) and IsDiscountApplied is true for that state.
Let's do this step by step and then we can merge operations where necessary.
I want to return a list of WorksiteGroup with the full structure above
where IsDiscontinued is false
source.Where(e => !e.IsDiscontinued);
and have an ActiveState where StateAbbrev matches any of the filter
criteria (states[])
now let's take the previous pipeline and chain this criterion into it.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(a => states.Contains(a.StateAbbrev)))
and IsDiscountApplied is true for that state.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(s => states.Contains(s.StateAbbrev) && s.IsDiscountApplied));
for efficiency let's swap the Contains call to be after s.IsDiscountApplied e.g.
source.Where(e => !e.IsDiscontinued)
.Where(e => e.ActiveStates.Any(s => s.IsDiscountApplied && states.Contains(s.StateAbbrev)));
You can try this using Linq:
string[] states = new string[] { "abbrev1", "abbrev2" };
var list = new List<WorksiteGroup>();
var item = new WorksiteGroup();
item.Name = "Test1";
item.IsDiscontinued = false;
var subitem = new WorksiteGroupState();
subitem.IsDiscountApplied = true;
subitem.StateAbbrev = "abbrev1";
item.ActiveStates.Add(subitem);
list.Add(item);
item = new WorksiteGroup();
item.Name = "Test2";
item.IsDiscontinued = true;
subitem = new WorksiteGroupState();
subitem.IsDiscountApplied = true;
subitem.StateAbbrev = "abbrev1";
item.ActiveStates.Add(subitem);
list.Add(item);
var result = list.Where(wg => wg.IsDiscontinued == false
&& wg.ActiveStates.Where(state => state.IsDiscountApplied == true
&& states.Contains(state.StateAbbrev)).Any());
foreach ( var value in result )
Console.WriteLine(value.Name);
Console.ReadKey();
You can play with items and add more to see results.
sudo-code but would something like below work, im sure you could do this is one line but
var worksiteGroup = Populate();
var filteredWorkSiteGroup = worksiteGroup .Where(x=>x.IsDiscontinued == false);
filteredWorkSiteGroup.ActiveStates = filteredWorkSiteGroup.ActiveStates
.Where(x=> states.Contains(x.StateAbbrev)
&& x.IsDiscountApplied == true);

How to determine if all properties of type List<T> in an object are null or empty?

I have an object which contains few int/string properties and some List<T> properties where T are some other classes in the project itself. Is there a cleaner way to determine if only those List<T> properties are empty or null? Maybe using a Linq statement?
I tried searching about it but can't find a short and clean way. Should i opt for reflection?? Can someone provide a sample related to this?
public class A
{
..some properties..
List<ClassA> ListA { get; set; }
List<ClassB> ListB { get; set; }
List<ClassC> ListC { get; set; }
List<ClassD> ListD { get; set; }
..some properties..
}
EDIT 1:
So far i have managed to write a clean code to check if list properties are null. But how can i check if they are empty. I need to convert object to List but i dont know the type of List it is
var matchFound = myObject.GetType().GetProperties()
.Where(x => x.PropertyType == typeof(List<>))
.Select(x => x.GetValue(myObject))
.Any(x => x != null);
EDIT 2:
I ended up using this, a one liner that works fine:
var matchFound = myObject.GetType().GetProperties()
.Where(x =>(x.GetValue(myObject) as IList)?.Count()>0);
Here is what i would do.
/// <summary>
/// caching a Dyctionary of IList types for faster browsing
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
private static readonly Dictionary<Type, Type> CachedActualType = new Dictionary<Type, Type>();
// Get Internal type of IList.
// When the type is not a list then it will return the same type.
// if type is List<T> it will return the type of T
public static Type GetActualType(this Type type)
{
if (CachedActualType.ContainsKey(type))
return CachedActualType[type];
if (type.GetTypeInfo().IsArray)
CachedActualType.Add(type, type.GetElementType());
else if (type.GenericTypeArguments.Any())
CachedActualType.Add(type, type.GenericTypeArguments.First());// this is almost always find the right type of an IList but if it fail then do the below. dont really remember why this fail sometimes.
else if (type.FullName?.Contains("List`1") ?? false)
CachedActualType.Add(type, type.GetRuntimeProperty("Item").PropertyType);
else
CachedActualType.Add(type, type);
return CachedActualType[type];
}
And then
var matchFound = myObject.GetType().GetProperties()
.Where(x => x.PropertyType.GetActualType() != x.PropertyType &&
(x.GetValue(myObject) as IList)?.Count()>0);
You can actually do even better and dont need to check for type and only try to cast the value.
The value will always be null if the type is not an IList
var matchFound = myObject.GetType().GetProperties()
.Where(x =>(x.GetValue(myObject) as IList)?.Count()>0);
You can use reflection for your requirement, i have just tried it.
class Test
{
}
class UserDetails
{
public List<Test> Test1 { get; set; }
public List<Test> Test2 { get; set; }
public string firstname { get; set; }
public string surname { get; set; }
public string city { get; set; }
public string state { get; set; }
}
Use this query to search, you can customize where condition for your requirement
UserDetails yourObject = new UserDetails();
yourObject.Test1 = new List<Test> { new Test() };
var result = typeof(UserDetails).GetProperties()
.Select(prop => prop)
.Where(property =>
{
if (property.PropertyType == typeof(List<Test>))
{
var value = (List<Test>)property.GetValue(yourObject, null);
return value == null || value.Count == 0;
}
return false;
}).ToList(); // this will return 1 because 1 property has count > 1
Update if use Templete
class UserDetails<T>
{
public List<T> Test1 { get; set; }
public List<T> Test2 { get; set; }
public string firstname { get; set; }
public string surname { get; set; }
public string city { get; set; }
public string state { get; set; }
}
Query
UserDetails<Test> yourObject = new UserDetails<Test>();
yourObject.Test1 = new List<Test> { new Test() };
var result = typeof(UserDetails<Test>).GetProperties()
.Select(prop => prop)
.Where(property =>
{
if (property.PropertyType == typeof(List<Test>))
{
var value = (List<Test>)property.GetValue(yourObject, null);
return value == null || value.Count == 0;
}
return false;
}).ToList();
You need quite a bit of reflection:
// the type to test
public class TestData
{
public string A { get; set; }
public List<string> B { get; set; }
public List<int> C { get; set; }
}
// an helper class used to generate checking functions
public static class ListTester
{
public static Func<T, bool> MakeClassChecker<T>()
where T : class
{
var checkFunctions = EnumerateListProperties<T>()
.Select(MakePropertyChecker<T>)
.ToList();
return instance => checkFunctions.All(f => f(instance));
}
public static IEnumerable<PropertyInfo> EnumerateListProperties<T>()
{
return typeof(T).GetProperties(Instance | Public | NonPublic)
.Where(prop => IsListClosedType(prop.PropertyType));
}
public static Func<T, bool> MakePropertyChecker<T>(PropertyInfo prop)
where T : class
{
var propType = prop.PropertyType;
var listItemType = propType.GenericTypeArguments[0];
var listEmptyChecker = (Func<object, bool>) ListCheckerFactoryMethod
.MakeGenericMethod(listItemType).Invoke(null, new object[0]);
return instance => instance != null && listEmptyChecker(prop.GetValue(instance));
}
private static MethodInfo ListCheckerFactoryMethod
= typeof(ListTester).GetMethod(nameof(ListCheckerFactory), Static | Public);
public static Func<object, bool> ListCheckerFactory<T>()
{
return list => list == null || ((List<T>) list).Count == 0;
}
public static bool IsListClosedType(Type type)
{
return type != null &&
type.IsConstructedGenericType &&
type.GetGenericTypeDefinition() == typeof(List<>);
}
}
[Test]
public void TestTemp()
{
var props = ListTester.EnumerateListProperties<TestData>();
CollectionAssert.AreEquivalent(props.Select(prop => prop.Name), new[] {"B", "C"});
var allListsAreNullOrEmpty = ListTester.MakeClassChecker<TestData>();
Assert.That(allListsAreNullOrEmpty(new TestData()), Is.True);
Assert.That(allListsAreNullOrEmpty(new TestData() {B = new List<string>()}), Is.True);
Assert.That(allListsAreNullOrEmpty(new TestData() {B = new List<string>() {"A"}}), Is.False);
}
Now, for the important bits: you search for properties of closed generic types of List<>.
The selection of the properties is done in IsListClosedType.
Then, for each property, we make a checking function using MakePropertyChecker.
The job of MakePropertyChecker is to build via MakeGenericMethod a version of ListCheckerFactory
of the appropriate type.
You want to check all properites that are of type List<something>
This method can do the trick:
bool IsGenericList(Type t)
{
return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(List<>);
}
Now you can modify your Linq query so it returns if at least one of List members is not null or empty
var matchFound = myObject.GetType().GetProperties()
.Where(p => IsGenericList(p.PropertyType))
.Select(p => p.GetValue(myObject) as IEnumerable)
.Any(list => list != null && list.Cast<object>().Any());//Cast<object> needed to be able to use Linq Any()

using linq and c# to create a nested list object from a flat list object

I have a question. It's about linq in combination with c#.
I want to create a tree structure from a flatten structure in a pre defined object structure.
The following code which I've got work, but both are not exactly what i want.
In linq:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new { AgnNummer = group.Key, Items = group.ToList()}).ToList();
the issue is that this does not result in the object I want.
So I've rewritten this to the following code
List<string> test = listAgenderingen.Select(x => x.Agnnummer).Distinct().ToList();
foreach (var item in test)
{
List<Agendering> listAgendering = listAgenderingen.Where(agend => agend.Agnnummer == item).OrderBy(ord => ord.Agnnummer).ToList();
AgnAgendering AgnAgendering = new AgnAgendering() {AgnNummer =item, Agenderingen = listAgendering };
}
this code actually works correct. but for 200000 records, it's taking a lot of time while the original linq takes a few seconds.
my question is can the linq be rewritten so it will create or convert to the richt object?
the structure of the classes:
public class Agendering
{
public int AgnID { get; set; }
public string Agnnummer { get; set; }
}
public class AgnAgendering
{
public string AgnNummer { get; set; }
public List<Agendering> Agenderingen { get; set; }
}
I hope someone has a sollution.
If I understand correctly, you want:
var result = listAgenderingen.GroupBy(records => records.Agnnummer)
.Select(group => new AgnAgendering { AgnNummer = group.Key, Agenderingen = group.ToList()}).ToList();
Your properties naming makes it absolutely unreadable and unclear.
Assuming that you have a flat structure like:
public class Item
{
public int ID { get; set; }
public int? ParentID { get; set; }
}
and you want a tree-like structure:
public class TreeItem
{
public int ID { get; set; }
public TreeItem Parent { get; set; }
public List<TreeItem> Children { get; set; }
public TreeItem(int id)
{
ID = id;
Children = new List<TreeItem>();
}
public TreeItem(int id, TreeItem parent) : this(id)
{
Parent = parent;
}
}
You can do most optimally in O(n) using Dictionary:
Item[] items = ...;
Dictionary<int, TreeItem> result = new Dictionary<int, TreeItem>();
foreach (var item in items.OrderBy(x => x.ParentID ?? -1))
{
TreeItem current;
if (item.ParentID.HasValue)
{
TreeItem parent = result[item.ParentID]; // guaranteed to exist due to order
current = new TreeItem(item.ID, parent);
parent.Children.Add(current);
} else {
current = new TreeItem(item.ID);
}
}
TreeItem[] treeItems = result.Values.ToArray();

How can I set the value of a parameter inside of an object?

I have this list that I am checking and then creating another list, where the definition is not equal to null.
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
public class WebWordForm
{
public string definition { get; set; }
public string partOfSpeech { get; set; }
public int sourceId { get; set; }
public List<string> synonyms { get; set; }
public List<string> typeOf { get; set; }
public List<string> hasTypes { get; set; }
public List<string> derivation { get; set; }
public List<string> examples { get; set; }
}
Is there a simple way that I could also set the sourceId in my rootObject.webWordForms list to the value of 2?
var new = rootObject.webWordForms
.Where(w => w.definition != null)
.Select(w => new WebWordForm{
definition = w.definition,
partOfSpeech = w.partOfSpeech,
sourceId = 2,
synonyms= w.synonyms,
typeOf = w.typeOf,
hasTypes = w.hasTypes,
derivation = w.derivation,
examples = w.examples
}).ToList();
You could use List ForEach method to do this, but please mind this is nothing different than looping.
var list = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
list.ForEach(x=> x.sourceId =2);
Use .ForEach() on new, this would iterate on list new and update the param.
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.ForEach(n=>n.sourceId = 2);
Note - If the queried final list is your return you need to do above operations before returning anything.
While I am suggesting ForEach(), many articles focus on avoiding it.
An alternative,
var finalList = rootObject.webWordForms
.Where(w => w.definition != null)
.ToList();
finalList.All(n=>{
n.sourceId = 2;
return true;
});

Categories

Resources