Dapper: How to read into list of Dictionary from query? - c#

Dapper provides lots of ways mapping data into list of dynamic objects. However in some case I'd like to read data to list of Dictionary.
The SQL may looks like:
"SELECT * FROM tb_User"
As tb_User may change outside, I don't know what columns will return in result. So I can write some code like this:
var listOfDict = conn.QueryAsDictionary(sql);
foreach (var dict in listOfDict) {
if (dict.Contains("anyColumn")) {
// do right thing...
}
}
Is there any built-in methods for Dapper to do this conversion?

You can cast each row as IDictionary:
var row = (IDictionary<string, object>)conn.Query("select foo = 1, bar = 'bar'").First();
Assert.That(row["foo"], Is.EqualTo(1));
Assert.That(row["bar"], Is.EqualTo("bar"));

You could use the Cast extension method from System.Linq
IEnumerable<IDictionary<string, object>> rows;
rows = connection.Query(sqlRequest).Cast<IDictionary<string, object>>();
foreach (var row in rows)
{
var columnValue = row['columnName']; // returns the value of the column name
}

You can just assign aliases to your query so that it matches the Key and Value properties of a KeyValuePair and then use the .ToDictionary method like this:
var dict = db.Query<KeyValuePair<string, int>>(#"
SELECT COMMUNITY_TYPE As Key, COUNT(*) AS Value
FROM SNCOMM.COMMUNITY
GROUP BY COMMUNITY_TYPE")
.ToDictionary(x => x.Key, x => x.Value);
Now you have a Dictionary<string, int> without any manual converting.

Related

How to convert DapperRow to Dictionary<string,string>

I've a method that returns IEnumerable with Dapper Row.
But I'm trying to access the data without typecasting it to a particular class and I'm getting null value.
Assuming that you are connecting to an SQL database
public List<IDictionary<string, object>> DapperSelect(string connectionString, string query, object parameters)
{
using (var connection = new SqlConnection(connectionString))
{
var result = connection.Query(query, parameters).ToList();
return result.Select(x => (IDictionary<string, object>)x).ToList();
}
}
I don't think that you should be converting your result to IDictionary<string, string> I don't think that has the desired effect you want, not every item in the dictionary is going to be a string, it could be bool, int, double, etc...
But if you insist, You could try to do something like
result.Select(x => ((IDictionary<string, object>)x).ToDictionary(ks => ks.Key, vs => vs.ToString())).ToList();
but I don't recommend it.
Better than all of that is that with dapper you can always strongly type the result returned from SQL, so instead of
connection.Query(query, parameters).ToList();
you would write
connection.Query<YOURTYPE>(query, parameters).ToList();
Something like this:
var foo = db.Query(
"MySp",
new { parameters },
commandType: CommandType.StoredProcedure)
.ToDictionary(
row => (int) row.Id,
row => (string) row.Name);
With row. being the names of the columns and foo being of type Dictionary .

C# Programmatically creating xyseries

I am trying to create an XyDataSeries for every column of my Datatable programmatically to be used for charting (SciChart). I need to do this is because the amount of columns and names are unknown in advance.
For any given series the x value will always just be an incrementing integer from 0 and the y values are the column values.
I am importing excel data into the Datatable and then passing each column to a Dictionary as a List where the column header is the name of the List by the following;
dict = dt.Columns.Cast<DataColumn>().ToDictionary(c => c.ColumnName, c => dt.AsEnumerable().Select(r => r[c]).ToList());
Can i do something like the above where Dictionary<string, XyDataSeries<double, double>>?
Assuming i did know the amount of columns and their names, I could do something like this for every column/list;
Manually creating an XyDataSeries and iterating through a list to append the values. I then set the chartseries.DataSeries for the chart (defined in XAML) to the respective XyDataSeries.
var list = dict["ListName"];
XyDataSeries<double, double> xyseries;
xyseries = new XyDataSeries<double, double>() { SeriesName = "ListName" };
foreach (var i in list)
{
double x = 1;
var d = Convert.ToDouble(i);
xyseries.Append(x++, d);
}
chartseries.DataSeries = xyseries;
I also have to declare the series in XAML - Can I get around this?
Having to do this over 600 times is far from ideal though and I am really desperate for an elegant solution for this.
Can i do something like the above where Dictionary<string, XyDataSeries<double, double>>?
The ToDictionary method accepts a Func<TSource, TElement> as its second argument. So you could create and populate the XyDataSeries<double, double> in this one. Something like this:
dict = dt.Columns.Cast<DataColumn>()
.ToDictionary(c => c.ColumnName, c =>
{
var dataSeries = new XyDataSeries<double, double>();
double x = 1.0;
dataSeries.Append(dt.AsEnumerable().Select(_ => x++), dt.AsEnumerable().Select(r => Convert.ToDouble(r[c])));
return dataSeries;
});

Show only columns from an string array

I have a Table in my Entities with 370 columns ! Furthermore i have a string Array which is not known before runtime (comes from a website).
e.g.:
string [] columns = {"column1", "column2", "column3"}
How can i fire a linq to my entities which gives me only the result with the giving columns?
I searched for hours, but don`t suceed till now - any suggestions?
This is not something that you can do with Linq-to-Entities. You need to be able to declare the columns in your code.
A better approach would be to create the query in Sql using the column names in your array, and use something like Dapper to map the results to your objects.
You could instantiate an ExpandoObject in your Delegate and use Reflection to get the columns specified in the incoming array.
The following:
List<IDictionary<String, Object>> List = Context.Row_Type.Select(delegate(Row_Type Row) {
IDictionary<String, Object> Out = new ExpandoObject() as IDictionary<String, Object>;
PropertyInfo[] PIs = Row.GetType().GetProperties();
foreach(PropertyInfo PI in PIs) {
if(PI.GetIndexParameters().Length == 0) {
Out.Add(PI.Name, PI.GetValue(Row));
}
}
return Out;
}).ToList();
Will return a List < IDictionary< String, Object>> with all the properties, to return the desired columns just discriminate by PI.Name:
if(PI.Name == "Desired column"){ // Or Array index
// Add to Out:
Out.Add( PI.Name, PI.GetValue(Row) )
}
I'm guessing you don't really want to return a class with unknown column names. Would a dictionary of column names and values work for you? Your query would still have to retrieve all of the columns, but you can only return the ones you care about.
string [] columns = {"column1", "column2", "column3"}
var entity = GetEntity();
var dictionary = columns.ToDictionary(c => c, c => entity.GetType().GetProperty(c).GetValue(entity));
Or, if you have a collection...
var entities = GetEntities();
var results = entities
.Select(e => columns.ToDictionary(c => c, c => e.GetType().GetProperty(c).GetValue(e)));

Returning a Dictionary<string, string> from a linq query

I have a table with 2 columns defined as varchar(50): Column1 and Column2. I want to return a dictionary of <string, string> where each row is in the dictionary and where Column1 is the key and Column2 is the value. This is what I have:
public Dictionary<string, string> LoadAllTheDataFromDB()
{
using (MyDC TheDC = new MyDC())
{
return (from c in TheTable
select new Dictionary<string, string>()
{
//stuck here
}).FirstOrDefault();
}
}
How do I make it that the dictionary is filled?
Try this:
var dict = TheTable.Select( t => new { t.Col1, t.Col2} )
.ToDictionary( t => t.Col1, t => t);
Remember in select lambda you will perform projection and create some anonymous object. Then in ToDictionary you will pass two parameters: First Parameter is a lambda to specify the key; in code above we are choosing Col1 to be the key. Second parameter is a lambda to specify the value; in code above we are choosing the object itself to be the value.
If you want the value to be an anonymous type, change the 2nd lambda like this:
ToDictionary( t => t.Col1, t => new { t.Col2 });
If you want the value to be a type you have defined, change the 2nd lambda like this:
ToDictionary( t => t.Col1, t => new YourType { Prop1 = t.Col2 });
Since you just need value of one first row why not to do that first:
var row = TheTable.FirstOrDefault();
And than just construct that dictionary if you got the result:
return row == null ? null :
new Dictionary<string,string>{ {row.Column1, row.Column2 } };

LINQ query and Array of string

I have a array of string say:
String[] Fields=new String[]{RowField,RowField1}
In which I can use the below query to get the values by specifying the values is query i.e RowField and RowField1:
var Result = (
from x in _dataTable.AsEnumerable()
select new
{
Name = x.Field<object>(RowField),
Name1 = x.Field<object>(RowField1)
})
.Distinct();
But if suppose I have many values in the Array like:
String[] Fields= new String[]
{
RowField,
RowField1,
RowField2,
.......
RowField1000
};
How can I use the query here without specifying each of the rowfield in the query?
How can i iterate through the array items inside the LINQ?
var Result = (
from x in _dataTable.AsEnumerable()
select (
from y in Fields
select new KeyValuePair<string, object>(y, x))
.ToDictionary())
.Distinct(DictionariesComparer);
You'll also need to write your own .ToDictionary() extension method and DictionariesComparer method (as Dictionary doesn't implement IEquatable).
Essentially, you want to retrieve specific fields from a DataTable without hardcoding the field names.
The following code will return a single dictionary object per row with the fields you specify in your array. There is no need to create additional extension methods or comparers:
var result = (from row in _dataTable.AsEnumerable()
let projection = from fieldName in fields
select new {Name = fieldName, Value = row[fieldName]}
select projection.ToDictionary(p=>p.Name,p=>p.Value));
The inner select picks the field values you need from each table row and stores them in the projection variable. The outer select converts this variable in a Dictionary
You can iterate over the result to get specific fields like this:
foreach (var row in result)
{
Console.WriteLine(row["field1"]);
}
EDIT:
The above code doesn't return distinct values. It is possible to return distinct values without writing a special comparer using group by but the code is not very pretty:
var result = (from row in table.AsEnumerable()
let projection = from fieldName in fields
select new { Name = fieldName, Value = row[fieldName] }
group projection by projection.Aggregate((v, p) =>
new {
Name = v.Name + p.Name,
Value = (object)String.Format("{0}{1}", v.Value, p.Value)
}) into g
select g.FirstOrDefault().ToDictionary(p=>p.Name,p=>p.Value));
The Aggregate creates a new projection whose Name and Value properties are the concatenation of all name and value fields. The result of the aggregate is used to group all rows and return the first row of each group. It works but it is definitely ugly.
It would be better to create a simple DictionaryComparer like the following code:
public class DictionaryComparer<TKey,TValue>: EqualityComparer<Dictionary<TKey,TValue>>
{
public override bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
//True if both sequences of KeyValuePair items are equal
var sequenceEqual = x.SequenceEqual(y);
return sequenceEqual;
}
public override int GetHashCode(Dictionary<TKey, TValue> obj)
{
//Quickly detect differences in size, defer to Equals for dictionaries
//with matching sizes
return obj.Count;
}
}
This allows you to write:
var result = (from row in table.AsEnumerable()
let projection = from fieldName in fields
select new {Name = fieldName, Value = row[fieldName]}
select projection.ToDictionary(p=>p.Name,p=>p.Value))
.Distinct(new DictionaryComparer<string, object>());
There is no foreach linq expression. I typically create my own extension method
Something along the lines of:
public static void Foreach<T>(this IEnumerable<T> items, Action<T> action)
{
foreach(T t in items)
{
action(t);
}
}
However beware if you're planning on using this with Linq2SQL as it could create a lot of db hits!

Categories

Resources