I have a List containing a Dictionary<string, string> that represents lines from a database from multiple joined tables. I have tried all sorts of LINQ approaches but I always hit a wall where LINQ just doesn't allow certain operations on this data structure. I have tried to simplify the code example as much as I think I can without misrepresenting the core issue so...
How can I extract explicit KeyValuePairs from a Dictionary inside of a List and put it into a Model?
I need a scale-able solution where the Model could do the same with the subData.
public class Program
{
public static void Main(string[] args)
{
List<Dictionary<string, string>> data = GetData();
data.Add(new Dictionary<string, string>() { ["modelData"] = "Bob", ["subModelData"] = "Frank" });
data.Add(new Dictionary<string, string>() { ["modelData"] = "Nancy", ["subModelData"] = "Boy" });
List<Model> models = new List<Model>();
//Fails in this linq statement. Anonymous types don't allow key accessors
foreach (Dictionary<string, string> distinctModel in data.GroupBy(x => new { x["dataKey"] }))
{
List<Dictionary<string, string>> newModelData = data.Where(d => d["data1Key"] == distinctModel["dataKey"]).ToList();
Model newModel = new Model(newModelData);
models.Add(newModel);
}
}
}
public class Model
{
public string modelData;
public List<Dictionary<string, string>> subData;
public Model(List<Dictionary<string, string>> data) {
modelData = data[0]["dataKey"];
data.Remove("dataKey");
subData = data;
}
}
Anonymous type projection initializer should be a simple name or member access. So you need to add an explicit name like below:
data.GroupBy(x => new {S = x["dataKey"] })
Related
I want to group by my list of dictionaries dynamically just like:
public static List<Dictionary<string, ClassName>> GroupBy(this List<Dictionary<string, ClassName>> list)
{
return list.AsQueryable().GroupBy(i => new { Key1 = i["Key01"].ClassProperty, Key2 = i["Key02"].AnotherClassProperty });
}
but I need to set keys name and numbers dynamically.
I had an idea to create dynamic-typed variable using ExpandoObject like that:
public static List<Dictionary<string, ClassName>> GroupBy(this List<Dictionary<string, ClassName>> list, IEnumerable<string> fieldnames)
{
return list.AsQueryable().GroupBy(i => GetKeys(fieldnames, dict) });
dynamic GetKeys(IEnumerable<string> fieldnames, Dictionary<string, ClassName> dict)
{
IDictionary<string, object> sampleObject = new ExpandoObject();
foreach (var fieldname in fieldnames)
{
sampleObject.Add($"Key{sampleObject.Count}", dict.GetValueOrDefault(fieldname).ClassProperty;
}
return sampleObject;
}
}
I want to assign some static values to my KeyValuePair object.
private IEnumerable<KeyValuePair<string, string>> getCountries()
{
return new List<KeyValuePair<string, string>>()
{
{ "code1", "value1" },
{ "code2", "value2" }
};
}
But this is throwing nooverloaded method error.
return new List<KeyValuePair<string, string>>()
{
new KeyValuePair<string, string>("code1", "value1"),
new KeyValuePair<string, string>("code2", "value2"),
};
If you're using .NET Core 2.0+, you can use the slightly less verbose:
return new List<KeyValuePair<string, string>>()
{
KeyValuePair.Create("code1", "value1"),
KeyValuePair.Create("code2", "value2"),
};
In C# 9, you can use target-typed new to write this as:
return new List<KeyValuePair<string, string>>()
{
new("code1", "value1"),
new("code2", "value2"),
};
Or with Dictionary you can achieve desired initialization style
var pairs = new Dictionary<string, string>
{
{ "one", "first" },
{ "two", "second" },
}.ToList();
pairs.Should().BeOfType<List<KeyValuePair<string, string>>>(); // Pass
Notice, that if later in the code you are going to just enumerate list of key value pairs, then you can use dictionary without explicitly converting it to the list.
var pairs = new Dictionary<string, string>
{
{ "one", "first" },
{ "two", "second" },
}
// later somewhere in the code
foreach(var pair in pairs)
{
Console.WriteLine($"{pair.Key}: {pair.Value}")
}
If you are using values internally (inside class), you can use tuples.
private IEnumerable<(string Code, string Name)> GetCountries()
{
yield return ("code", "Earth");
yield return ("code", "Vulkan");
}
Which later can be consumed in more readable way
foreach(var country in GetCountries())
{
Console.WriteLine($"{country.Code}: {country.Name}")
}
If type used across application, then instead of using key-value pairs you can show intentions of your code to the readers of your code and create custom type.
public class Country
{
public string Code { get; }
public string Name { get; }
public Country(string code, string name)
{
Code = code;
Name = name;
}
}
private IEnumerable<Country> GetCountries()
{
yield return new Country("code", "Earth");
yield return new Country("code", "Vulkan");
}
Which later can be consumed in more readable way
foreach(var country in GetCountries())
{
Console.WriteLine($"{country.Code}: {country.Name}")
}
You need to consider that both Key and Value properties of the generic class are read-only, so you cannot set them directly. Instead you need to take advantage of the class's constructor in order to set the desired pairs.
public IEnumerable<KeyValuePair<string, string>> getCountries()
{
var keyValue1 = new KeyValuePair<string,string>("code1","value1");
var keyvalue2 = new KeyValuePair<string,string>("code2","value2");
var keyValueList = new List<KeyValuePair<string, string>> {keyValue1, keyvalue2};
return keyValueList;
}
I have created a method that inspects an object and returns a [requested] set of properties.
public static List<object> Inspect<T>(T obj, params Func<T, object>[] funcs)
{
List<object> results = new List<object>(funcs.Length);
foreach (var func in funcs)
{
results.Add(func(obj));
}
return results;
}
It is then invoked, for example on a List, like so:
List<string> peopleData = new List<string>(10) { "name", "age", "address" };
List<object> properties = Inspect(peopleData, p => p.Count, p => p.Capacity);
// The results would be
// properties[0] = 3
// properties[1] = 10
I would like to adapt the Inspect method to instead return a Dictionary<string, object>, where the keys of the dictionary would be the property names. The adapted method would then be invoked like this:
List<string> peopleData = new List<string>(10) { "name", "age", "address" };
Dictionary<string, object> properties = Inspect(peopleData, p => p.Count, p => p.Capacity);
// The results would be
// properties["Count"] = 3
// properties["Capacity"] = 10
Is this possible? If so, and if the solution is reflection-based (as I assume it'd have to be), would there be a big performance hit?
You'd have to use the classic "interrogation" approach for Func<..> - Retrieving Property name from lambda expression
public static IDictionary<string, object> Inspect<T>(T obj,
params Expression<Func<T, object>>[] funcs)
{
Dictionary<string, object> results = new Dictionary<string, object>();
foreach (var func in funcs)
{
var propInfo = GetPropertyInfo(obj, func)
results[propInfo.Name] = func.Compile()(obj));
}
return results;
}
Ps, as Servy pointed out, you'd also need to make the params use Expression.
I have a lot of times thinking about converting example of class to Dictionary<String, String> where key is variable name(class field name) and value is variable current assigned value. So we have a simple class:
public class Student
{
public String field1;
public Int64 field2;
public Double field3;
public Decimal field4;
public String SomeClassMethod1()
{
...
}
public Boolean SomeClassMethod2()
{
...
}
public Int64 SomeClassMethod1()
{
...
}
}
How I expect it will look like:
static void Main(String[] args)
{
Student student = new Student(){field1 = "", field2 = 3, field3 = 3.0, field4 = 4.55m};
Dictionary<String, String> studentInDictionary = ConvertAnyToDictionary<Student>(student);
}
public Dictionary<String, String> ConvertAnyToDictionary<T>(T value) where T:class
{
...
}
Any ideas about how to make it real? Thx a lot for any advices.
EDIT1:
Expected result:
studentInDictionary[0] = KeyValuePair("field1", "");
studentInDictionary[1] = KeyValuePair("field2", (3).ToString());
studentInDictionary[2] = KeyValuePair("field3", (3.0).ToString());
studentInDictionary[3] = KeyValuePair("field4", (4.55m).ToString());
Here is how you can do it:
public static Dictionary<String, String> ConvertAnyToDictionary<T>(T value) where T : class {
var fields = typeof(T).GetFields();
var properties = typeof(T).GetProperties();
var dict1 = fields.ToDictionary(x => x.Name, x => x.GetValue(value).ToString());
var dict2 = properties.ToDictionary(x => x.Name, x => x.GetValue(value, null).ToString());
return dict1.Union(dict2).ToDictionary(x => x.Key, x=> x.Value);
}
Edit: I'm taking in count both fields and properties there. If you will only be using properties, you can just use dict2.
You might want to take a look at the BindingFlags argument received by GetFields() and GetProperties() methods.
var proInfos = student.GetType().GetProperties();
if(proInfos!=null)
{
Dictionary<string,string> dict= new Dictionary<string, string>();
foreach (var propertyInfo in proInfos)
{
var tv = propertyInfo.GetValue(currentObj, null);
if(tv!=null)
{
if(dict.ContainsKey(propertyInfo.Name))
continue;
dict.Add(propertyInfo.Name, tv.ToString());
}
}
}
You can either serialize using already existing Serializers (Xml or JSON) or you can go about it using reflection.
Here is an example of how to get fields with reflection:
Not getting fields from GetType().GetFields with BindingFlag.Default
I have a Dictionary<string,int> dictionary1 and I need to convert it into a List<Data> where Data has the properties lable = dictionary1.key and value = dictionary1.value. I don't want to use a for/foreach loop (written by myself) because in order to avoid it I am trying to use a Dictionary.
Another option would be having two different dictionaries (dictionary2 and dictionary3) where dictionary2<string,keyOfDictionary1> and dictionary3<string,valueOfDictionary1>.
Do I make sense? Is that possible? Is there a better option?
Assuming:
class Data
{
public string Label { get; set; }
public int Value { get; set; }
}
Then:
Dictionary<string, int> dic;
List<Data> list = dic.Select(p => new Data { Label = p.Key, Value = p.Value }).ToList();
Perhaps you could use LINQ?
dictionary1.Select(p => new Data(p.Key, p.Value)).ToList()
This is however using yield and thus loops in the background...
myDictionary.Select(x => new Data(){ label = x.Key, value = x.Value).ToList();
I assume that "no loop" actually means "i want LINQ":
List<Data> = dictionary1.Select(
pair => new Data() {
label = pair.Key,
value = pair.Value
})).ToList();
public class Data
{
public string Key { get; set; }
public int Value { get; set; }
}
private static void Main(string[] args)
{
Dictionary<string, int> dictionary1 = new Dictionary<string, int>();
dictionary1.Add("key1", 1);
dictionary1.Add("key2", 2);
List<Data> data = dictionary1.Select(z => new Data { Key = z.Key, Value = z.Value }).ToList();
Console.ReadLine();
}
Try
dictionary1.Select(p => new Data(p.Key, p.Value)).ToList();
.NET already has a data type that does what Data would do: KeyValuePair<T1,T2>. Dictionary already implements IEnumerable<KeyValuePair<T1,T2>>, just cast to it.
Dictionary<string, int> blah = new Dictionary<string, int>();
IEnumerable<KeyValuePair<string, int>> foo = blah;
This is a old post, but post only to help other persons ;)
Example to convert any object type:
public List<T> Select<T>(string filterParam)
{
DataTable dataTable = new DataTable()
//{... implement filter to fill dataTable }
List<Dictionary<string, object>> rows = new List<Dictionary<string, object>>();
Dictionary<string, object> row;
foreach (DataRow dr in dataTable.Rows)
{
row = new Dictionary<string, object>();
foreach (DataColumn col in dataTable.Columns)
{
row.Add(col.ColumnName, dr[col]);
}
rows.Add(row);
}
string json = new JavaScriptSerializer().Serialize(rows);
using (MemoryStream stream = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
DataContractJsonSerializer deserializer = new DataContractJsonSerializer(typeof(T[]));
var tick = (T[])deserializer.ReadObject(stream);
return tick.ToList();
}
}
Just in case just helps anyone, I did it like this - will handle objects more complex than a single value type, as stated by the OP.
// Assumes: Dictionary<string, MyObject> MyDictionary;
List<MyObject> list = new List<MyObject>();
list.AddRange(MyDictionary.Values.ToArray());