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.
Related
public class UserValues
{
public string UserId { get; set; }
public int FieldId { get; set; }
public string FieldValue { get; set; }
}
public class LookupMeta
{
public int FieldId { get; set; }
public int Id { get; set; }
public int FieldValueId { get; set; }
public string Title { get; set; }
}
I have kept this in 2 different lists after reading it from DB.
Now I want to compare both the list with
FieldId == FieldId
and FieldValue equals Id
then replace FieldValue from uservalues to FieldValueId from lookupMeta
UserValues
.Where(x => LookupMeta.Any(y =>
y.FieldId == x.FieldId &&
y.FieldValueId.Equals(x.FieldValue)))
.Select(x => x.FieldValue.Replace(x.FieldValue, ???))
I am looking at this link as well. I am struck C# LINQ code for two list compare and replace
Is it good to have in List and doing like this or is there any other optimized way?
Based on the comment that has been left on pwilcox's answer it seems like the OP is look for a solution where the unmatched rows are also included. That means instead of using inner join we are looking for a left outer join.
In the world of Linq this could be achieved via a combination of GroupJoin, SelectMany and Select operators.
In order to be able to join on two different columns we have to introduce an intermediate class to be able to tell the types of the GroupJoin. So, I have created the following class:
internal class IntermediateKey
{
public int Id { get; set; }
public string Value { get; set; }
}
We also have to define a comparer for this class to be able to find matching data:
internal class IntermediateKeyComparer : IEqualityComparer<IntermediateKey>
{
public bool Equals(IntermediateKey x, IntermediateKey y)
{
return x.Id == y.Id && x.Value == y.Value;
}
public int GetHashCode(IntermediateKey obj)
{
return obj.Id.GetHashCode() + obj.Value.GetHashCode();
}
}
Please bear in mind that this implementation is quite simplified. The correct way to implement it is shown in this thread.
Now can define our query as this:
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(paired, meta) => new { Value = paired.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
We defined that userValues should be left outer joined on lookupMetas
if uv's FieldId is matches to lm's FieldId
and if uv's FieldValue is matches to lm's Id's string representation
With the SelectMany we choose either the matching LookupMeta entity or null
With the Select we update the UserValue's FieldValue property only if there is a related LookupMeta otherwise we use its original value.
Now let's see how this works with some sample data:
static void Main(string[] args)
{
var userValues = new List<UserValue>
{
new UserValue { FieldId = 1, FieldValue = "2"},
new UserValue { FieldId = 2, FieldValue = "3"},
new UserValue { FieldId = 4, FieldValue = "5"}
};
var lookupMetas = new List<LookupMeta>
{
new LookupMeta { FieldId = 1, Id = 2, FieldValueId = 20 },
new LookupMeta { FieldId = 2, Id = 3, FieldValueId = 30 },
new LookupMeta { FieldId = 3, Id = 4, FieldValueId = 40 },
};
var comparer = new IntermediateKeyComparer();
var result = userValues
.GroupJoin(
lookupMetas,
uv => new IntermediateKey { Id = uv.FieldId, Value = uv.FieldValue },
lm => new IntermediateKey { Id = lm.FieldId, Value = lm.Id.ToString() },
(uv, lm) => new { Value = uv, Lookups = lm},
comparer)
.SelectMany(
pair => pair.Lookups.DefaultIfEmpty(),
(x, meta) => new { Value = x.Value, Lookup = meta})
.Select(res =>
{
res.Value.FieldValue = res.Lookup?.FieldValueId.ToString() ?? res.Value.FieldValue;
return res.Value;
});
foreach (var maybeUpdatedUserValue in result)
{
Console.WriteLine($"{maybeUpdatedUserValue.FieldId}: {maybeUpdatedUserValue.FieldValue}");
}
}
The output will be:
1: 20
2: 30
4: 5
So, as you can see there is no matching LookupMeta for the last UserValue that's why its FieldValue remained intact.
If I follow you correctly, then the .Join() method in LINQ may be of use to you. Here I use it to accomplish what I think you're after.
UserValues
.Join(
LookupMeta,
uv => new { uv.FieldId, uv.FieldValue },
lm => new { lm.FieldId, lm.FieldValueId },
(uv,lm) => {
uv.FieldValue = lm.FieldValueId;
return uv;
}
);
The second and third lines in the method build anonymous objects from the source tables. The values of these are matched to make a link.
The last line takes the joined entries as inputs and then gives your output. In your case, I just return the UserValues entry. But before I do I change its "FieldValue" property to the "FieldValueId" property of the LookupMeta entry.
You have some inconsistencies. For instance, you talk about matching FieldValue to Id in the paragraph, but in the code you match FieldValue to FieldValueId. Also, you use == in one comparison and .Equals() in the other. No wrong answer here. I just don't know your underlying objects. So you may have to modify my code a bit to get what you want. But it shows the general strategy that I hope will work for you.
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.
I have one class to bind for ex.
public class MyClass
{
public int Id { get; set; }
public string Name { get; set; }
public int Price { get; set; }
}
Now, this class will get values from different tables but they have common ID.
here is the Lists that I am getting in my loop.
var List1 = item.TicketTypes.Where(m => m.PerformanceID == item.Id)
.Select(t => new { t.Name, t.ID});
var List2 = item.Pricies.Where(m => m.PerformanceID == item.Id)
.Select(t => new { t.Price, t.ID });
item is object of for each loop instance.
What I want is,
I want List of MyClass filled with Entity Name and Price as per their ID.
There isn't much informations here for me to test this, but I have this to suggest:
// Make sure to add a constructor to MyClass
var List1 = item.TicketTypes.Where(m => m.PerformanceID == item.Id)
.Select(t => new MyClass(t.Name, t.ID, item.Pricies.FirstOrDefault(p => p.PerformanceID == t.ID).Price));
This should give you the desired result, but like I said I don't have enough informations to test this so make sure you comment the problems so we can fix them together.
In a Linq Statement, if my assumptions are right(cannot test it):
var result= from t1 in item.TicketTypes
join t2 in item.Pricies on t1.ID equals t2.ID
select new MyClass() { Id = t1.ID, Name = t1.Name, Price = t2.Price };
use .ToList() if you need a List
Try this :
var query = List1.Join(List2, l1 => l1.Id, l2 => l2.Id, (l1, l2) => new { ID = l1.Id, Name = l1.Name, Price = l2.Price });
foreach (var obj in query)
{
Console.WriteLine("{0} - {1} - {2}", obj.ID, obj.Name, obj.Price);
}
Consider the following classes:
public class Recipe
{
public string Id { get; set; }
public ICollection<RecipeFacet> RecipeFacets { get; set; }
}
public class RecipeFacet
{
public string Id { get; set; }
public Facet Facet { get; set; }
public string RecipeId { get; set; }
}
public class Facet
{
public string Name { get; set; }
}
I need to improve an existing query. I was thinking of using Linq's deferred execution. How would I write a Linq query that returns only Recipes, that contains ALL Facets I specify in a list of Tuples?
This is the original code that loops through Recipes and its Facets. It works but it is slow if my intial results query has lots of Recipes.
IQueryable<Recipe> result; //assume we have data here
string query = "Cuisine:American+Recipe-Type:dinners";
IEnumerable<Tuple<string, string>> taxFacets = query
.Split(' ')
.Select(tf => tf.Split(':'))
.Select(tf => new Tuple<string, string>(tf[0], tf[1]))
.Distinct();
var recipeFacetCollection = result.Select(r => r.RecipeFacets).ToList();
var matchedRecipesIds = new List<string>();
var recIds = result.Select(r => r.Id).ToList();
// initially, include all recipes
matchedRecipesIds.AddRange(recIds);
// loop through each recipe's facet collection
foreach (var col in recipeFacetCollection)
{
// loop through the tax facets from the query
foreach (var tf in taxFacets)
{
var exists = col.Any(f => f.Facet.Name.Equals(tf.Item2, StringComparison.OrdinalIgnoreCase));
// remove any recipe that is missing a facet
if (!exists)
{
matchedRecipesIds.Remove(col.First().RecipeId);
}
}
}
result = result.Where(r => matchedRecipesIds.Contains(r.Id));
How can I have a nice Linq query with deferred execution?
UPDATE::
Turning my Tuple into a List allows me to do this. But this query doesn't return any of my records.
This is my criteria:
Recipes, that have a collection of RecipeFacts, that contains Facets that have Name = "American" AND Name = "dinners".
var listFacets = new List<string>()
{
"American",
"dinners"
};
result = result
.Where(r => r.RecipeFacets
.All(f => !listFacets.Any(t => t.Equals(f.Facet.Name, StringComparison.OrdinalIgnoreCase))));
Your query logic selects all recipes whose facets don't exist in listFacets.
#Hung's logic is closer but selects recipes that have all of their facets in listFacets
I think that you want to select all recipes that contain all listFacets.
Simplifying the example to use lists of strings:
var listFacets = new[] { "a", "d" };
var recipes = new[] { new[] { "a" },
new[] { "a", "d" },
new[] { "a", "d", "e" },
new[] { "x" }
};
// correct query, returns 2 results ad and ade
var result = recipes.Where(r => listFacets.All(f => r.Any(rf => rf == f)));
// original incorrect query, returns x
var result2 = recipes.Where(r => r.All(f => !listFacets.Any(rf => rf == f)));
I am not quite sure cause your code block is quite long but here is what I can come up with
result = result.Where(r => r.RecipeFacets.All(f => taxFacets.Any(t => t.Item1.Equals(f.Facet.Name))));
Let me know if it helps or not
Remove the exclamation from the clause in the check on the listFacets collection.
result = result.Where(r => r.RecipeFacets.All(f => listFacets.Any(t => t.Equals(f.Facet.Name, StringComparison
I got this from #Rimp's help.
WHERE - filter
ALL - Require that All values from listFacets
ANY - is in ANY of the Facets.
result = result
.Where(x => listFacets
.All(lf => x.RecipeFacets
.Any(f => f.Facet.Slug.Equals(lf, StringComparison.OrdinalIgnoreCase))));
I have an IEnumerable<RuleSelection> with these properties:
public class RuleSelection{
public int RuleId { get; set;}
public int? CriteriaId { get; set; }
public int? CriteriaSourceId{ get; set; }
}
RuleId in RuleSelection is not unique.
Can I write a linq query to normalize these into IEnumerable<Rule> which would be:
public class Rule{
public int RuleId { get; set; }
public IEnumerable<int> Criteria { get; set; }
public IEnumerable<int> CriteriaSource { get; set; }
}
Rule.RuleId would be unique and the properties Criteria and CriteriaSource would include all the CriteriaId's and CriteriaSourceId's for the RuleId respectively.
It sounds like you want something like:
var rules = selections.GroupBy(rs => rs.RuleId)
.Select(g => new Rule {
RuleId = g.Key,
Criteria = g.Select(rs => rs.CriteriaId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
CriteriaSource = g.Select(rs => rs.CriteriaSourceId)
.Where(c => c != null)
.Select(c => c.Value)
.ToList(),
});
Using my FullOuterGroupJoin extension method
LINQ - Full Outer Join
you could:
theRules.FullOuterGroupJoin(theRules,
r => r.RuleId,
r => r.RuleId,
(crit, critSource, id) => new Rule {
RuleId = id,
Criteria = crit
.Where(r => r.CriteriaId.HasValue)
.Select(r => r.CriteriaId.Value),
CriteriaSource = critSource
.Where(r => r.CriteriaSourceId.HasValue)
.Select(r => r.CriteriaSourceId.Value),
}
);
To write this:
var rules =
from sel in selections
group sel by sel.RuleId into rg
select new Rule {
RuleId = rg.Key,
Criteria = rg.Select(r => r.CriteriaId).FilterValues(),
CriteriaSource = rg.Select(r => r.CriteriaSourceId).FilterValues(),
};
I created the following FilterValues extension (to eliminate duplication):
public static IEnumerable<T> FilterValues<T>(
this IEnumerable<T?> items)
where T : struct
{
// omitting argument validation
return
from item in items
where item.HasValue
select item.Value;
}
I set out to provide essentially a pure query-syntax version of JonSkeet's answer. I gave up on that in effort to remove duplication for the property assignments and wound up with this combination extension & query-syntax approach.