Merging 2 rows from 2 different lists with same UserId? - c#

If I have 2 different lists,
list1 contains:
UserId: fcec4d6c-c971-4690-90da-be8411dcf251 Col1: 32 Col2: 2 Col3: 0 Col4: 0
UserId: 783ffaa5-03ef-4883-80d2-0500ef489832 Col1: 50 Col2: 4 Col3: 0 Col4: 0
and list2 contains:
UserId: fcec4d6c-c971-4690-90da-be8411dcf251 Col1: 0, Col2: 0, Col3: 45, Col4: 50
If I want to merge these to lists, so that the result would end up being a UserDto list which contains:
UserId: fcec4d6c-c971-4690-90da-be8411dcf251 Col1: 32 Col2: 2 Col3: 34 Col4: 50
UserId: 783ffaa5-03ef-4883-80d2-0500ef489832 Col1: 50 Col2: 4 Col3: 0 Col4: 0
How would one go about doing that?
UserDto just contains something like
[JsonSchema(JsonObjectType.String, Format = "uuid")]
public Guid UserId { get; set; }
public int Col1 { get; set; }
public int Col2 { get; set; }
public int Col3 { get; set; }
public int Col4 { get; set; }
I've tried
list1.AddRange(list2);
list1.GroupBy(e => e.UserId, (key, g) => new { User = key, Columns = g.ToList() }).ToList();
return list1;
list1 returns 2 UserIds, where fcec4d6c-c971-4690-90da-be8411dcf251 now has a Columns list that contains 2 columns, one with col1 + col2 filled and col3 + col4 filled. Please note that these lists will contain a lot of these instances.
Edit 1: I should've made it more clear that I want the sum of these instances in the end. I have now received a proper solution.

Select proper aggregate function:
var result = list1.Concat(list2)
.GroupBy(e => e.UserId)
.Select(g => new User
{
UserId = g.Key,
Col1 = g.Max(x => x.Col1),
Col2 = g.Max(x => x.Col2),
Col3 = g.Max(x => x.Col3),
Col4 = g.Max(x => x.Col4),
})
.ToList();

You didn't specify why you didn't change Col1 and Col2, but changed Col3 and Col4. Do you always want to replace these two columns? Or do you only want to replace them if they have value zero? Or maybe you want to replace all columns with a zero value?
Anyway, first you need to get every list1Element with all zero or more list2Elements that have the same UserId.
Whenever you want to fetch "items with their sub-items", like Schools with their Students, Customers with their Orders, or list1Elements with their list2Elements, consider to use one of the overloads of Enumerable.GroupJoin.
As parameter keySelector use the properties that makes it "its list2Element"
IEnumerable<User> list1 = ...
IEnumerable<User> list2 = ...
var result = list1.GroupJoin(list2
list1User => list1User.UserId, // from every user in list1 take the UserId
list2User => list2User.UserId, // from every user in list2 take the UserId
// parameter resultSelector: from every user in list1, with the zero or more
// users from list2 that have the same UserId, make one new
(list1User, list2UsersWithSameId) => new
{
// decide what you want.
// Replace all 0 properties with the corresponding list2 column?
Col3 = (list1User.Col3 != 0) ? list1User.Col3 :
list2UsersWithSameId.Select(list2User => list2User.Col3)
.FirstOrDefault(),
So if listUser.Col3 not zero, use this Col3 value,
otherwise, from the zero or more list2UsersWithSameId take the Col3 and use the first or default. If there is a list2 user with same Id, you have got its Col3, if not, you get the value zero.
So the value is only replaced if Col3 is zero, and there is at least one list2 with the same Id. If there is none, Col3 remains zero.
Do the same for the other columns that you want to replace.
TODO: you didn't specify that UserId in list2 is unique. If not, it can be that a list item has more than one corresponding list2 item with the same userId. You have to decide which value to use: the first one? the largest one?

Related

Using Where clause in Group Join

Please Consider these 2 tables:
CategoryID CategoryName CategoryModel
-----------------------------------------------------------
1 Book 1
2 Shoe 2
3 Glass 1
and
SubCategoryID SubCategoryName CategoryID SubCategoryModel OtherColumn1 OtherColum2
---------------------------------------------------------------------
1 Book1 1 3
2 Book2 1 1
3 Shoe1 2 2
4 Shoe2 2 2
I want such this query:
from a in Category
join b in SubCategory
on a.CategoryID equals b.CategoryID into grpDetail
where a.CategoryModel != b.SubCategoryModel <----------
select new
{
Id = a.CategoryID,
Count1 = grpDetail.Count(o=>o.OtherColumn1 == 1),
...
}
the problem id I can't access to b in above specifies line. How can I write this query?
Thanks
There is a straightforward one to many relation between Categories and SubCategories: every Category has zero or more SubCategories; every SubCategory belongs to exactly one Category, namely the Category that the foreign key SubCategory.CategoryId refers to.
You want to join Category and SubCategory on this foreign key. You don't want all Category-SubCategory combinations that match, you want only those where Category.CategoryModel is not equal to SubCategory.SubCategoryModel.
From the remaining records, you want to select several properties. I don't see property GrpDetail in your classes, so I don't know what you want.
Luckily you mention that your problem is in the Where:
var result = Categories.Join(SubCategories, // join tables Categories and SubCategories
category => category.Id, // from every category take the Id,
subCategory => subCategory.CategoryId, // from every subCategory take foreign key CategoryId
(category, subCategory) => new // when they match make one new object
{
// we need at least Category.CategoryModel and SubCategory.SubCategoryModel
CategoryModel = category.CategoryModel,
SubCategoryModel = subCategory.SubCategoryModel,
// Select other Category properties that you plan to use:
CategoryId = category.Id,
...
// Select other SubCategory properties that you plan to use:
...
})
// we don't want all combinations, only those where
// CategoryModel is not equal to SubCategoryModel
.Where(joinResult => joinResult.CategoryModel != joinResult.SubCategoryModel)
// from the remaining combinations calculate the final result
.Select(joinResult => new
{
Id = joinResult.CategoryId,
Count1 = ... // sorry, don't know what property grpDetail does
...
});
split your query into 2, first do your join with the where clause and then do your group by.

Entity Framework - .Include doesn't load all records

I have this a database with these entities:
public class User
{
public GUID UserId { get; set;}
public IEnumerable<Item> items { get; set;}
}
public class Item
{
public GUID ItemId { get; set;}
public GUID ownerId { get; set;}
public int boughtCount{ get; set;}
}
I need to return the list of users that have items ordered by the items bought the most.
So for example if we have this users:
A: userId: ..
items: 1. itemId: .. | boughtCount: 2
2. itemId: .. | boughtCount: 1
B: userId: ..
items: 1. itemId: .. | boughtCount: 7
C: userId: ..
items: 1. itemId: .. | boughtCount: 3
D: userId: ..
items: none
The query needs to return the users in the following order: B,C,A (D is not returned as he doesn't have any items)
I am using the following query:
users = await _context.Items.OrderByDescending(c => c.BoughtCount)
.Join(_context.Users,
i => i.OwnerId,
u => u.Id,
(i, u) => new { i, u })
.OrderByDescending(x => x.i.BoughtCount)
.Select(x => x.u)
.Distinct()
.Skip(skip)
.Take(take)
.Include(u => u.Items)
.ToListAsync();
This query returns the users in the correct order, but my problem is that for each user it returns maximum of 15 of the items he has, so if for example user A would have 30 items, I will only get his first 15.
What is this 15 items limit?
Am I doing something that cause this limit to come or it's just "hard coded" somewhere?
If so, how do I remove/change the limit?
Note: My sql database is hosted in Azure.
UPDATE:
This is the generated sql query from my linq query:
SELECT [I].[ItemId], [I].[ownerId], [I].[boughtCount]
FROM [Items] AS [I]
INNER JOIN (
SELECT DISTINCT [t0].*
FROM (
SELECT DISTINCT [u].[UserId]
FROM [Items] AS [I]
INNER JOIN [User] AS [u] ON [c].[ownerId] = [u].[UserId]
ORDER BY [u].[UserId]
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
) AS [t0]
) AS [u] ON [I].[ownerId] = [u].[UserId]
Thanks!

C# EntityFramework Join

I would like to join the results of 2 entities based on the quantities. Here's what I have...
Order Ticket #1
OrderID CustomerID ItemID Description POS# Cost Quantity
1 1 1 Apples 111 1.25 3
1 1 2 Oranges 222 1.12 5
1 1 3 Bananas 333 1.17 5
Order Ticket #2
OrderID CustomerID ItemID Description POS# Cost Quantity
2 1 1 Apples 111 1.25 7
2 1 2 Oranges 222 1.12 2
2 1 3 Bananas 333 1.17 5
Here is the code I use to get each ticket:
public OrderEntity getOrder(int orderId)
{
var data = from c in Orders
where c.OrderID == orderId
select c;
return data;
}
How would I write the LINQ code to combine the 2 tickets so I get a Sum of the quantities? It should look like this...
Order Tickets #1 and #2
CustomerID ItemID Description POS# Cost Quantity
1 1 Apples 111 1.25 10
1 2 Oranges 222 1.12 7
1 3 Bananas 333 1.17 10
It seems like I should be able to do something like so...
public List<OrderEntity> getCustomerOrders(int customerId)
{
var data = from c in Orders
where c.CustomerID == customerId
select c;
return data.ToList();
}
The problem is I cannot figure out the grouping. There is a lot of info about there about how to write the EF code for grouping, but I'm not sure if I should be grouping on CustomerID or on the Quantity. Any tips on how to do the grouping here would be greatly appreciated.
You should group by CustomerID and ItemID:
Try something like this:
public List<OrderEntity> getCustomerOrders(int customerId)
{
var data = from c in Orders
where c.CustomerID == customerId
group c by new { c.CustomerID, c.ItemID } into g
select new OrderEntity () {
CustomerID = g.Key.CustomerID,
ItemID = g.Key.ItemID,
Quantity = g.Sum(x => x.Quantity)
};
return data.ToList();
}
I'm not sure how you define your data, but if you need to have Description POS, Cost in your result, try:
public List<OrderEntity> getCustomerOrders(int customerId)
{
var data = from c in Orders
where c.CustomerID == customerId
group c by new { c.CustomerID, c.ItemID,c.Description,c.POST,c.Cost } into g
select new OrderEntity () {
CustomerID = g.Key.CustomerID,
ItemID = g.Key.ItemID,
Quantity = g.Sum(x => x.Quantity),
Description = g.Key.Description,
POST = g.Key.POST,
Cost = g.Key.Cost
};
return data.ToList();
}

Filter a generic list based on another list

I have a generic list which needs to be filter based on another list (say, List<string>).
public class Model
{
public string ID { get; set;}
public string Make { get; set;}
}
List<Model> lstModel = new List<Model>();
And the lstModel is as follows
ID Make
---- -----------
5 MARUTI
4 BENZ
3 HYUNDAI
2 HONDA
1 TOYOTA
And i have another list which contains only car makers,ie
List<string> lstMakers = new List<string>() {"MARUTI", "HONDA"};
1) I need to filter lstModel which contains only items in lstMakers.
The output would be
ID Make
---- -----------
5 MARUTI
2 HONDA
2) Based on output (1), need another list of ids with 1 increment to each item in descending order,
The output would be List<int> ie,
6
5
3
2
Note: Using lambda expression / linq is more preferable
1 )
var list1 = lst.Where(x=>lstMakers.Contains(x.Make)).ToList();
2)
var list2 = list1.Select(x=>int.Parse(x.ID)+1)
.Concat(list1.Select(x=>int.Parse(x))
.OrderByDescending(x=>x)
.ToList();
Use Enumerable.Join and OrderByDescending:
var models = from maker in lstMakers
join model in lstModel
on maker equals model.Make
select model;
List<int> result = models
.Select(m => int.Parse(m.ID) + 1)
.OrderByDescending(i => i)
.ToList();
However, this selects two ints since only two models match. Your result contains 4 ints. I assume that your result is not related to your sample, is it?
but i need both the item and its incremental value,...
Now it's clear, use Enumerable.SelectMany with an array:
List<int> result = models
.Select(m => int.Parse(m.ID))
.SelectMany(id => new int[]{ id, id + 1 })
.OrderByDescending(id => id)
.Distinct()
.ToList();

Generate same ID out of same sets of IDs

I have an issue that I'll try to explain. My thought is to create a script in SSIS in C# and with that generate a list of IDs for each unique combination of IDs in a table.
I have a SQL server table which consists of two columns. The columns are IDs (I can make them numeric but in raw format they are alphanumeric strings). I want to generate a new ID out of the set of IDs in column 2 that are connected to column 1.
Col1 Col2 Generated ID
1 1
1 2 => 1
1 3
-----------
2 1 => 2
2 3
-----------
3 3
3 1 => 1
3 2
I'm thinking of a Hash function maybe? But how do I get the same ID out of the set for 1 and 3? Independent of order? Do I need to sort them first?
I needed "10 reputation" to post an image so I hope my illustration explains the issue...
As further examples to try to understand your problem, would you expect the following sets of values in Col2 to return something like '123' as the "Generated ID" value for all the listed cases below, like so?
Col2 => Generated ID
1,2,3 => 123
1,3,2 => 123
2,1,3 => 123
2,3,1 => 123
3,1,2 => 123
3,2,1 => 123
etc
If so, then based on the above assumptions and to answer your questions:
Yes, a Hash function could do it
How you get the same "Generated ID" for sets 1 and 3 (in your example) will depend on your GetHashCode() override/implementatio
Yes, you will probably need to sort, but again, that depends on your implementation.
Since you refer to using a C# script in SSIS, a possible C# implementation might be to implement a (very!) simple Hash class which given a set of Col2 values (for each data set), simply:
sorts the values for Col2 to get them in the 'right' order and
returns some integer representation of the sorted set of data to get the Hash (e.g., concatenate the int's as strings and then convert back to int)
The hash class could be instantiated in your (base?) class's GetHashCode() function, which is passed the Col2 values and performs steps (1) and (2) above, returning the hash code as needed.
Something like this might work for you (assuming you have access to Generics in the .NET version you're using):
namespace SimpleHashNamespace
{
public class SimpleHash
{
private readonly List<int> _data;
public SimpleHash(List<int> col2)
{
_data = col2;
}
public int GetMyHash()
{
_data.Sort();
string stringHash = string.Join("", _data);
return int.Parse(stringHash); // warning 1: assumes you always have a convertible value
}
}
public class MyDataSet
{
private readonly List<int> _dataSetValues;
public MyDataSet(List<int> dataSetValues)
{
_dataSetValues = dataSetValues;
}
public override int GetHashCode()
{
SimpleHash simpleHash = new SimpleHash(_dataSetValues);
return simpleHash.GetMyHash(); // warning 2: assumes the computed hash can fit into the int datatype given that GetHashCode() has to return int
}
}
public partial class Program
{
private static void Main(string[] args)
{
// how you split up Col1 to get your list of int's dataset is up to you
var myDataSet1 = new MyDataSet(new List<int>(new int[] { 1,2,3 }));
Console.WriteLine(myDataSet1.GetHashCode());
var myDataSet2 = new MyDataSet(new List<int>(new int[] { 2,1,3 }));
Console.WriteLine(myDataSet2.GetHashCode());
var myDataSet3 = new MyDataSet(new List<int>(new int[] { 3,2,1 }));
Console.WriteLine(myDataSet3.GetHashCode());
Console.ReadLine();
}
}
}
Obviously this is a trivial implementation however given the simplicity of the problem as it has been specified, perhaps this will suffice?
CREATE TABLE T (Col1 INT, Col2 INT);
GO
INSERT INTO [dbo].[T]([Col1],[Col2])
VALUES (1,1), (1,2), (1,3), (2,1), (2,3), (3,3), (3,1), (3,2), (2,3),(2,1);
GO
SELECT
T1.Col1,
(
SELECT Convert (VARCHAR,Col2) + ','
FROM T T2
WHERE T2.Col1 = T1.Col1
ORDER BY Col2
FOR XML PATH('')
) AS Col2_Cat
INTO X
FROM T T1
GROUP BY Col1 ;
SELECT T.Col1, T.Col2, Y.Col3
FROM T
INNER JOIN
(
SELECT X1.Col1, Min (X2.Col1) AS Col3 FROM X X1
----inner join X X2 on HASHBYTES ('SHA1',X1.Col2_Cat) = HASHBYTES('SHA1',X2.Col2_Cat)
inner join X X2 on X1.Col2_Cat = X2.Col2_Cat
GROUP BY X1.Col1
) AS Y
ON T.Col1 = Y.Col1;
DROP TABLE X
DROP TABLE T

Categories

Resources