Dynamically constructing a GET query with optional parameters in C# - c#

I have a class that contains all the properties of a query I'm constructing could possibly have (most of which are optional)
For example:
public class QueryClass
{
public string Offset {get; set;}
public string Limit {get; set;}
public string Sort {get; set;}
}
Then in a BuildQuery() method I am constructing the query by doing this:
private string BuildQuery(QueryClass query)
{
var queryDictionary = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(query.Offset)
{
queryDictionary.Add("offset", query.Offset);
}
if (!string.IsNullOrEmpty(query.Limit)
{
queryDictionary.Add("limit", query.Limit);
}
if (!string.IsNullOrEmpty(query.Sort)
{
queryDictionary.Add("sort", query.Sort);
}
var content = new FormUrlEncodedContent(queryDictionary);
return content.ReadAsStringAsync().Result;
}
This works, but the issue is my actual QueryClass is significantly larger than this, there's got to be a better way to do this than to have a ton of IF statements for every optional property but I haven't been able to come up with a more elegant solution. I also don't care for adding the keys in the dictionary in this way, I probably need a new approach for how I structure the QueryClass.

If you don't mind taking the reflection hit, just project, filter nulls, then send to ToDictionary
Note : The assumptions here are, all the property names are your keys, and all the properties are convertible to string
var queryClass = new QueryClass()
{
Limit = "asdf",
Sort = "Bob"
};
var results = queryClass
.GetType()
.GetProperties()
.Select(x => (Value: x.GetValue(queryClass) as string, x.Name))
.Where(x => !string.IsNullOrWhiteSpace(x.Value))
.ToDictionary(
x => x.Name.ToLower(),
x => x.Value);
foreach (var (key, value) in results)
Console.WriteLine($"{key} : {value}");
Output
limit : asdf
sort : Bob
Add pepper and salt to taste
Or as an extension method
public static IDictionary<string, string> ConvertToDictionary<T>(this T source)
=> typeof(T).GetProperties()
.Select(x => (Value: x.GetValue(source) as string, x.Name))
.Where(x => !string.IsNullOrWhiteSpace(x.Value))
.ToDictionary(
x => x.Name.ToLower(),
x => x.Value!);

Related

How to select from the list only some properties depending from list of property names?

I am looking for selection where the result is anonymous but the properties are given from list of string
Example
public class A{
public string PropertyA {get; set;}
public double PropertyB {get; set;}
public double PropertyC {get; set;}
public double PropertyD {get; set;}
}
var list = new List<A>{ ... };
var propertyNames = new List<string>{"PropertyA", "PropertyD"}
I would like create a selector which creates an anonymous with PropertyA and PropertyD, so I can get something like
var result = list.Select( selector(propertynames) ).ToList();
where again result[0] is anonymous with properties like result[0].PropertyA and result[0].PropertyD
I would really, really suggest going differently about this.
However, here is one way to approach this problem - use System.Dynamic.ExpandoObject:
var result = list
.Select(i =>
{
var expando = new ExpandoObject();
var expandoAsDict = (IDictionary<string, object>)expando;
var targetProperties = i
.GetType()
.GetProperties()
.Where(p => propertyNames.Contains(p.Name))
.ToDictionary(p => p.Name, p => p.GetValue(i));
foreach (var property in targetProperties)
{
expandoAsDict.Add(property);
}
return (dynamic)expando;
})
.ToList();
You can then proceed as you describe, for example:
Console.WriteLine(result[0].PropertyA);
writes out the expected value.
Of course you can forget about Intellisense support for your dynamic type (if that wasn't obvious to begin with).
Note: another approach would be to construct your type at runtime by using System.Reflection.Emit, but that would be even more awkward.
Good luck!

Removing specific values from a Dictionary C# with Linq

I have a dictionary which holds information from a parsed test run. The key is the name of the method and the value is a list of TestRunProperties. My dictionary contains all methods from a test run and I would like to remove the methods which failed during a test run. Is this possible to do with Linq?
TestRunProperties class:
public class TestRunProperties
{
public string computerName { get; set; }
public TimeSpan duration { get; set; }
public string startTime { get; set; }
public string endTime { get; set; }
public string testName { get; set; }
public string outcome { get; set; }
}
Dictionary:
//Key is the name of the method, value is the properties associated with each run
private static Dictionary<string, List<TestRunProperties>> runResults = new Dictionary<string, List<TestRunProperties>>();
I've tried this but I think I'm getting confused with the Where part:
runResults.Remove(runResults.Where(methodName => methodName.Value.Where(method => method.outcome.ToLower().Equals("failed"))));
I'm quite new to Linq and Lambda and I'm still trying to understand how to access data like this.
Just use a loop to remove the items you don't want. You can write an extension method to make it easier to call:
public static class DictionaryExt
{
public static void RemoveAll<K, V>(this IDictionary<K, V> dict, Func<K, V, bool> predicate)
{
foreach (var key in dict.Keys.ToArray().Where(key => predicate(key, dict[key])))
dict.Remove(key);
}
}
This usually will be more efficient than creating an entirely new dictionary, especially if the number of items being removed is relatively low compared to the size of the dictionary.
Your calling code would look like this:
runResults.RemoveAll((key, methodName) => methodName.Value.Where(method => method.outcome.ToLower().Equals("failed")));
(I chose the name RemoveAll() to match List.RemoveAll().)
You could create a new dictionary by filtering out the invalid ones:
var filtered = runResults.ToDictionary(p => p.Key, p => p.Value.Where(m => m.outcome.ToLower() != "failed").ToList());
Ok, grrrrrr was faster :-)
To be honest you're probably better off selecting a new dictionary from the existing one:
runResults.Select().ToDictionary(x => x.Key, x => x.Value.Where(x => x.Value.outcome != "failed"));
*editted to reflect list in the dictionary.
Actually, you can get rid of the ones with no successful results by doing this too:
runResults.Select(x => new { x.Key, x.Value.Where(x => x.Value.outcome != "failed")} ).Where(x => x.Value.Any()).ToDictionary(x => x.Key, x => x.Value);

Is there a way to simplify initializing Linq new object initialization?

I find that I am repeating a lot of new object initialization code in Linq queries, for example when creating different overloaded methods that use the same query structure.
var result = ItemResponses
.GroupBy(ir => ir.ItemID)
.Select(
grouped => new
{
ItemID = grouped.Key,
Average = (double)grouped.Average(g => g.OptionValue),
...etc. lots of properties, similar structure across lots of methods...
...Would really love to be able to write this code once somewhere...
}
);
At first I thought using constructors might be one way of doing it, something along these lines:
var result = ItemResponses
.GroupBy(ir => ir.ItemID)
.Select(grouped => new TestClass(grouped) //or anonymous type
);
public class TestClass
{
public int ItemID { get; set; }
public double Average { get; set; }
public TestClass() {}
public TestClass(IGrouping<int, ItemRespons> values)
{
ItemID = values.Key;
Average = values.Average(g => g.OptionValue);
}
}
But I see that Linq (to Entities at least) only allows parameterless constructors and initializers. So this approach doesn't seem to work.
Is there another way I can achieve simplifying this type of repetive code, and only having it in one place?
Use a delegate:
Func<IQueryable<ItemResponse>,IEnumerable<TestClass>> SelectResult = q =>
q.GroupBy(ir => ir.ItemID)
.Select(
grouped => new TestClass
{
ItemID = grouped.Key,
Average = (double)grouped.Average(g => g.OptionValue),
...
});
Then you can use it like this:
var result = SelectResult(ctx.ItemResponse);
It's even better to make it a extension method of course:
public static class Extensions
{
public static IEnumerable<TestClass> SelectResult(this IQueryable<ItemResponse> q)
{
return q.GroupBy(ir => ...)
}
}
And use it like this:
var result = ctx.ItemResponses.SelectResult();
It's not possible for anonymous type projections because there is no way to define a typed result, except some non generic type like dynamic, object or IQueryable, but then you'll have problem consuming it.
However it is possible to reuse projections to a custom types (like your sample TestClass). But instead of constructor, you have to put the code in a expression returning method.
For instance, instead of this
public TestClass(IGrouping<int, ItemResponse> values)
{
ItemID = values.Key;
Average = values.Average(g => g.OptionValue);
// ...etc. lots of properties
}
you could use something like this
static Expression<Func<IGrouping<int, ItemResponse>, TestClass>> ToTestClass()
{
return values => new TestClass
{
ItemID = values.Key,
Average = values.Average(g => g.OptionValue)
// ...etc. lots of properties
};
}
and the sample query would be
var result = ItemResponses
.GroupBy(ir => ir.ItemID)
.Select(ToTestClass());

List to Dictionary<Key, List<Value>> - C#

I have a List and MyClass is:
public class MyClass
{
public bool Selected { get; set; }
public Guid NoticeID { get; set; }
public Guid TypeID { get; set; }
}
My question is, how do i convert this list into a Dictionary<Guid, List<Guid>>, where the dictionary key is the GUID from the TypeID property, and the value is a list of all the NoticeID values corresponding to that TypeID. I have tried like so:
list.GroupBy(p => p.TypeID).ToDictionary(p => p.Key, p => p.ToList())
but this returns a Dictionary <Guid, List<MyClass>>, and I want a Dictionary<Guid, List<Guid>>.
Well, when you group you can specify the value you want for each element of the group:
var dictionary = list.GroupBy(p => p.TypeID, p => p.NoticeID)
.ToDictionary(p => p.Key, p => p.ToList());
However, I would strongly consider using a lookup instead of a dictionary:
var lookup = list.ToLookup(p => p.TypeID, p => p.NoticeID);
Lookups are much cleaner in general:
They're immutable, whereas your approach ends up with lists which can be modified
They express in the type system exactly what you're trying to express (one key to multiple values)
They make looking keys up easier by returning an empty sequence of values for missing keys, rather than throwing an exception

Querying nested lists

I have two classes:
public GeneralClassName
{
public GeneralClassName ()
{
SpecificList = new List<OtherClass>();
}
public string StringValue;
public string OtherStringValue;
public List<OtherClass> SpecificList;
}
and
public OtherClass
{
public string Name;
public string Number;
}
After a JSON deserialization I obtain a nice List<GeneralClassName>, the result I want is a Dictionary<string, int> whose value is the sum of the variabiles "Number" inside List<OtherClass> inside List<GeneralClassName>, while the key is the variabile Name.
In other words I'd like to sum Number grouping by Name.
Now, the only thing that came across my mind is a nested foreach, something like that:
Dictionary<string, int> resultDictionary = new Dictionary<string, int>();
foreach(List<OtherClass> listOtherClass in bigListGeneralClass.Select(x => x.SpecificList))
{
foreach(OtherClass otherClass in listOtherClass)
{
int value = 0;
if(resultDictionary.ContainsKey(otherClass.Name))
{
resultDictionary[otherClass.Name] += otherClass.Number;
}
else
{
resultDictionary.Add(otherClass.Name, otherClass.Number);
}
}
}
While this solution seems to work well, I don't like it at all.
Is there a more clean way to find this result? Maybe through a nice LINQ query?
As you don't use any information from the GeneralClassName you can use SelectMany to flatten your list. This flat list of OtherClass instances is than grouped by the Name property. Finally, the list of groups is transformed into a dictionary with the key of the group (aka the Name property) being the key of the new property and the value being the sum of all Number values in that group:
var result = bigListGeneralClass.SelectMany(x => x.SpecificList)
.GroupBy(x => x.Name)
.ToDictionary(x => x.Key,
x => x.Sum(y => y.Number));
This code assumes that OtherClass.Number is in fact an int not a string. This assumption is also used in your sample code with the loop.
If this assumption is not correct, change y.Number to int.Parse(CultureInfo.InvariantCulture, y.Number).
Note: This will throw an exception if any of the numbers can't be parsed, so you might want to make sure beforehand that all contain valid numbers.
Try this:
Dictionary<string, int> result =
bigListGeneralClass.SpecificList.GroupBy(sl => sl.Name)
.ToDictionary(group => group.Key, group => group.Sum(x => Int32.Parse(x.Number)));

Categories