How to turn dapper result into a dictionary using result mapping - c#

I want to use the splitOn feature denoted here: https://dapper-tutorial.net/result-multi-mapping
to group every Order of the results to a integer property "EmployeeId". I Followed the advice from How to map to a Dictionary object from database results using Dapper Dot Net?
but I am getting a An item with the same key has already been added. so how can I group my orders by EmployeeId?
I cannot modify the Order class and I prefer using a dictionary over creating a class that wraps Order. However, if there is no other way I am open to the idea of wrapping Order
https://dotnetfiddle.net/hn6Sjf
public class Program
{
public class Order
{
public int OrderID { get; set; }
public int CustomerID { get; set; }
public DateTime OrderDate { get; set; }
public int ShipperID { get; set; }
}
public static void Main()
{
string sql = #"
SELECT TOP 10
EmployeeID,
OrderID,
CustomerID,
OrderDate,
ShipperID
FROM Orders
ORDER BY OrderID;
";
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
var rawList = connection.Query<Order>(sql);
FiddleHelper.WriteTable(rawList);
var dict = connection.Query<int, List<Order>, KeyValuePair<int, List<Order>>>(sql,
(s, i) => new KeyValuePair<int, List<Order>>(s, i), null, null, true, "OrderID")
.ToDictionary(kv => kv.Key, kv => kv.Value);
FiddleHelper.WriteTable(dict);
}
}
}

Would this meet your needs?
var dict = connection.Query<int, Order, ValueTuple<int, Order>>(sql,
(s, i) => ValueTuple.Create(s, i), null, null, true, "OrderID")
.GroupBy(t => t.Item1, t => t.Item2, (k, v) => new {Key = k, List = v})
.ToDictionary(kv => kv.Key, kv => kv.List);
Fiddle

You could create an envelope class (Or use dynamic if you prefer that):
public class OrderEntity
{
public int EmployeeID {get;set;}
public Order Order {get;set;}
}
And then the mapping from the resultset into a dictionary grouped by employee id is straight forward:
var dict = new Dictionary<int,List<Order>>();
var r = connection.Query<OrderEntity, Order, OrderEntity>(sql,(orderEntity, order) =>
{
// You can skip that line if you want, the orderEntity is (probably) never used.
orderEntity.Order = order;
if(dict.ContainsKey(orderEntity.EmployeeID))
{
dict[orderEntity.EmployeeID].Add(orderEntity.Order);
}
else
{
dict.Add(orderEntity.EmployeeID, new List<Order> {orderEntity.Order});
}
return orderEntity;
}, splitOn: "OrderID");
This method does it all in 1 iteration over the result set and only requires a O(1) key lookup into the dictionary.

Related

C# Linq OrderBy Object.ID && Object.SortedList.Key.Value

I'm having headache trying to filter a List, and can't find answer on the web. To be honest, I don't even know if it's possible to achieve, but I guess it should be.
So, MyItem class looks like this :
public class MyItem
{
private int _ID1, _ID2, _registeredTime;
private SortedList<DateTime, AnotherItem> _mySortedList;
public int ID1
{
get { return _ID1; }
set { _ID1 = value; }
}
public int ID2
{
get { return _ID2; }
set { _ID2 = value; }
}
public int RegisteredTime
{
get { return _registeredTime; }
set { _registeredTime = value; }
}
public SortedList<DateTime, AnotherItem> MySortedList
{
get { return _mySortedList; }
set { _mySortedList = value; }
}
}
My goal is to only get the first occurrence of each pair (ID1, ID2) based on RegisteredTime, so I'm using the code below and it works like a charm :
BaseList = ItemsList.OrderBy(x => x.RegisteredTime).ToList();
FilteredList = new List<MyItem>(BaseList.GroupBy(x => new { x.ID1, x.ID2 })
.Select(x => x.First())).ToList());
But another constraint makes me need to get a list based on first pair (ID1, ID2) + MySortedList.Key, so I could get something like :
ID1, ID2, MySortedList[Date1]
ID1, ID2, MySortedList[Date2]
So I'd like to have a request that does :
FilteredList = BaseList.GroupBy(x => new { x.ID1, x.ID2, x.MySortedList."UniqueKey" })
.Select(x => x.First()))
.ToList();
But I can't find a solution to achieve this. I'm kinda new to Linq and don't know how this kind of particularity works (and, as I said before, if it's even possible).
Thanks for your help !
First group the items by the keys, order items of each group by the RegisteredTime and finally retrieve only the first in each group:
var result = BaseList.GroupBy(x => new { x.ID1, x.ID2, x.MySortedList[yourKey]})
.Select(g => g.OrderBy(i => i.RegisteredTime).First());
Notice that if you try to group by a key that is not present in collection you will get the following exception: "The given key was not present in the dictionary." To cope with that scenario use TryGetValue and determine what you want to do with items that do not have that key.
In addition instead of explicitly implementing the properties, as they all use the default implementation use auto-property:
public class MyItem
{
public int ID1 { get; set; }
public int ID2 { get; set; }
public int RegisteredTime { get; set; }
public SortedList<DateTime, AnotherItem> MySortedList { get; set; }
}

Combine Specific Results from a Linq Query

I want to group a table by a specific category.
The categories look like: "AAA", "BBB", "CCC", "DDD", "EEE", etc... Using the code below, I can easily get results for each group. However, there is a special case where two categories should be combined and a new Key generated (e.g. Category BBB and DDD should end up as one category).
My end result should be something like:
Key: "AAA", Items: (items under AAA)
Key: "BBB/DDD", Items: (items under BBB and DDD)
Key: "CCC", Items: (items under CCC)
I have been at this for a while and can't get anything that works.
var query= ds.Tables[0].AsEnumerable()
.GroupBy(g => g.Field<string>("category"))
.Select(a => new workType
{
Key = a.Key,
Item = a.ToList()
});
public class workType
{
public string Key { get; set; }
public List<DataRow> Item { get; set; }
}
Basically, all you need to do is to transform the key before grouping by it:
var sequence = ds.Tables[0].AsEnumerable();
var result = sequence
.Select(e => new { Key = GetCompoundKey(e.Field<string>("category")), Value = e })
.GroupBy(e => e.Key)
.Select(g => new { Key = g.Key, Items = g.SelectMany(e => e.Value).ToList() });
private static string GetCompoundKey(string originalKey)
{
if (originalKey.Equals("BBB") || originalKey.Equals("DDD"))
{
return "BBB/DDD";
}
return originalKey;
}
Besides that, GroupBy has an overload that takes IEqualityComparer<T> so you could write an IEqualityComparer<string> implementation and use it:
private class KeysEqualityComparer : IEqualityComparer<string>
{
public bool Equals(string x, string y)
{
if (x.Equals(y))
{
return true;
}
return (x.Equals("BBB") && y.Equals("DDD"))
|| (x.Equals("DDD") && y.Equals("BBB"));
}
public int GetHashCode(string str)
{
return str.GetHashCode();
}
}
// your original code using the comparer:
var query= ds.Tables[0].AsEnumerable()
.GroupBy(g => g.Field<string>("category"), new KeysEqualityComparer())
.Select(a => new workType
{
Key = a.Key,
Item = a.ToList()
});
I'm not sure if this is a good idea though as you will most probably get weird group keys that you'll need to modify anyway. And then the modification code lives in different place than the comparison code, even though the logic should be the same. So that's asking for some trouble in the future.

Split collection linq entity framework

I have a PropertyValues entity which reperesent product properties:
public enum Property { Color = 1, Brand, Size }
public class PropertyValue
{
public int PropertyValueId { get; set; }
public Property PropertyId { get; set; }
public string Value { get; set; }
public ICollection<Product> Products { get; set; }
public PropertyValue()
{
Products = new HashSet<Product>();
}
}
Cause we have end sequnce of product properties i created an enum Property which keep properties.
I'm trying to achieve following result - to split collection depending on properties. Where the values are an another Dictionary
Dictionary<Property, Dictionary<int, string>> dict = new Dictionary<Property, Dictionary<int, string>>()
{
new KeyValuePair<Property, Dictionary<int, string>>()
{
{
Property.Color,
new KeyValuePair<int, string>()
{
{ 5, "white" }, // ProperyValueId, Value
{ 6, "green"}
}
}
}
}
I have been looked aside GroupBy and SelectMany, but didn't find a way.
For now i have following:
var query = await db.PropertyValues
.OrderBy(prop => prop.PropertyId)
.Where(prop => prop.PropertyId == Property.Brand)
.ToDictionaryAsync(prop => prop.PropertyValueId, prop => prop.Value);
Dictionary<Property, Dictionary<int, string>> properties = new Dictionary<Property, Dictionary<int, string>>();
properties.Add(Property.Brand, query);
Should return a json. But firstly need to get sequence. Json should look like:
[{
{"colors": [{"5", "Black"}, {"7", "White"}]},
{"brands": [{"78", "Q&Q"}, {"24", "Adidas"}]},
}]
The first GroupBy splits the list of PropertyValues by PropertyId, then this grouping is converted to a dictionary that has PropertyId as Key.
The values of each record of our dictionary is composed by creating a new dictionary where the key is PropertyValueId and the value is Value
PropertyValues.GroupBy( k => k.PropertyId )
.ToDictionary( k => k.First().Value,
k => k.ToDictionary( p => p.PropertyValueId, p => p.Value ) );
Now data is structured as you want.

how to convert a datatable to List<long,List<keyvaluepair<string,string>>> in C#

I have a datatable with three columns says, ID, Name and Value.
I want to convert this datatable into List<long,List<keyvaluepair<string,string>>>.
The only trick is ID is foreign ket from another table. so it could have repeatative values.
I want to do it in C#.
Thanks in advance.
no, not dictionaly.....actualy I have two classes says, definded as....
public class RunParameters
{
public long RunId { get; set; }
public List<WorkflowParameter<string>> WorkflowParameters { get; set; }
public RunParameters()
{
WorkflowParameters = new List<WorkflowParameter<string>>();
}
}
public struct WorkflowParameter<T>
{
public string ParameterName
{ get; set; }
public T ParameterValue
{ get; set; }
}
I want to convert the datable into List.
I cannot use dictionary since I want to serialize the this List to send it across the network....
Group items in your query by ID and project each item from group to KeyValuePair:
(from r in table.AsEnumerable()
group r by r.Field<long>("ID") into g
select g.Select(i => new KeyValuePair<string, string>(
i.Field<string>("Name"), i.Field<string>("Value"))
.ToList()).ToList()
Or completely with method syntax:
table.AsEnumerable()
.GroupBy(r => r.Field<long>("ID"))
.Select(g => g.Select(r => new KeyValuePair<string, string>(
r.Field<string>("Name"), r.Field<string>("Value"))
.ToList())
.ToList()

C# LINQ sub-where

I have object
public class OrderItem
{
public string idProduct { get; set; }
public int quantity { get; set; }
public List<WarehouseItem> WarehouseInfo = new List<WarehouseItem>();
}
public class WarehouseItem
{
public string Name{ get; set; }
public string LocnCode{ get; set; }
}
and i need select items which have WarehouseInfo.LocnCode == "A1"
It is doesnt work when I use something like
var items = itemList.Where(x => x.WarehouseInfo.Where(y => y.LocnCode.Equals("A1")));
Your requirements could be interpreted one of three ways, so here's three solutions:
Give me all OrderItems where ANY WarehouseItem has a LocnCode of "A1":
var items = itemList.Where(i => i.WarehouseInfo.Any(w => w.LocnCode == "A1"));
Give me all WarehouseItems within the OrderItems that have a LocnCode of "A1":
var items = itemList.SelectMany(i => i.WarehouseInfo)
.Where(w => w.LocnCode.Equals("A1"));
Give me all OrderItems where ANY WarehouseItem has a LocnCode of "A1", and filter WarehouseInfo to only those WarehouseItems:
This can't be done in a simple Linq query because there's no way to change the contents of the existing objects. You're going to have to create new objects with the filtered values:
var items = itemList.Where(i => i.WarehouseInfo.Any(w => w.LocnCode == "A1"))
.Select(i => new OrderItem
{
idProduct = i.idProduct,
quantity = i.quantity,
WarehouseInfo = i.WarehouseInfo.Where(w => w.LocnCode.Equals("A1"));
.ToList()
}
);
Try
var items = itemList.Where(x => x.WarehouseInfo.Any(y => y.LocnCode.Equals("A1")));
The Where takes a predicate that should return a bool. Any will return true if at least one item in the collection returns true for the given predicate.

Categories