Find foreign key matching multiple row values using linq - c#

I have a table called conversation that lists the users who are in the same conversation. Eg:
id | conversation | user
1 | 1 | Bob
2 | 1 | Jane
3 | 2 | Tim
4 | 2 | Lily
5 | 2 | Rick
And I have list has some users like so..
List<string> usernames = new List<string>{"Bob","Jane"};
I now want to check when a user wants to start a conversation with other users whether they have previously had a conversation with those other users (exclusively).
Eg. Bob wants to create a new conversation with Jane.(I have Bob and Jane in my username List values to compare if those have been in conversation before?)
As we see they two have a conversation already, I want to get the conversation id belong these two guys.
if my list contains following data like so..
List<string> usernames = new List<string>{"Bob","Jane","Tim"};
This time I am expecting there is no conversation those 3 before.
I want to find out there is no conversation i can create one new for them.
I have trying this with Linq but cannot get any correct result so far.
Thank in advance for your help;

You can group by conversation IDs and match the groups with your list of users:
var previousConversations = userConversations
.GroupBy(uc => uc.Conversation)
.Where(g => g.OrderBy(uc => uc.user).Select(uc => uc.user)
.SequenceEqual(usernames.Sort()));

You could use the SequenceEqual method to compare 2 sequences.
var users1 = new List<String> { "Bob", "Jane" };
var users2 = new List<String> { "Bob", "Jane", "Tim" };
var tableData = new List<YourTable>
{
new YourTable {foreignKey = 1, name = "Bob"},
new YourTable {foreignKey = 1, name = "Jane"},
new YourTable {foreignKey = 2, name = "Tim"},
new YourTable {foreignKey = 2, name = "Lily"},
new YourTable {foreignKey = 2, name = "Rick"},
};
var keyFound = (from t in tableData
group t by t.foreignKey into users
where users.Select(u => u.name).SequenceEqual(users1)
select users.Key).FirstOrDefault();
var keyNull = (from t in tableData
group t by t.foreignKey into users
where users.Select(u => u.name).SequenceEqual(users2)
select users.Key).FirstOrDefault();
Edit:
You're using linq as a means to fetch data from the database, not all operations are supported this way. What we can do is extract the selection in memory and then we can use all operations again.
Depending on your situation this might not be a good idea, usually you want to let sql handle all the query power since he is better at that.
But if you are selecting on a not so big table you can easily pull in memory feel free to do it like this :
var rawData = (from t in tableData
group t by t.foreignKey into users
select users).ToList();
var key = (from d in rawData
where d.Select(u => u.name).SequenceEqual(users2)
select d.Key).FirstOrDefault();
If on the other hand the data is too big and you want it executed on sql side i would consider make a stored procedure for this.

You're basically doing a "these tags" query, which is answered over here:
Select items by tag when searching multiple tags
Applied to your problem, it looks like:
int userCount = myUsers.Count;
List<int> conversationIds = conversationsUsers
.Where(cu => myUsers.Contains(cu.UserName))
.GroupBy(cu => cu.ConversationId)
.Where(g => g.Select(cu => cu.Username).Distinct().Count() == userCount)
.Select(g => g.Key)
(exclusively).
Well, ok... Move the user filtering into the group filtering.
int userCount = myUsers.Count;
List<int> conversationIds = conversationsUsers
.GroupBy(cu => cu.ConversationId)
.Where(g => g.Select(cu => cu.Username).Distinct().Count() == userCount)
.Where(g => g.Select(cu => cu.UserName).All(userName => myUsers.Contains(userName)))
.Select(g => g.Key)

Check with the following linq. It will result in conversation id if any conversation is else empty (enumeration yields no result). You can check your conditions accordingly.
var cust = new List<Table>
{
new Table {Conversation = 1, Id = 1, User = "Bob"},
new Table {Conversation = 1, Id = 2, User = "Jane"},
new Table {Conversation = 2, Id = 3, User = "Tim"},
new Table {Conversation = 2, Id = 4, User = "Lily"},
new Table {Conversation = 2, Id = 5, User = "Rick"}
};
var names = new List<string> { "Rick", "Lily", "Tim" };
var res = from x in cust
group x by x.Conversation
into y
where y.Select(z => z.User).Intersect(names).Count() == names.Count
select y.Key;

Thanks everybody for your help and answers. The following code did the trick and gives me the result what I wanted. Thank you Kristof for providing this code and your effort. I have attached order by clause in second query otherwise it wont gives the right result. Hope this helps someone who need similar stuff.
var users1 = new List<String> { "Bob", "Jane" };
var users2 = new List<String> { "Bob", "Jane", "Tim" };
var tableData = new List<YourTable>
{
new YourTable {foreignKey = 1, name = "Bob"},
new YourTable {foreignKey = 1, name = "Jane"},
new YourTable {foreignKey = 2, name = "Tim"},
new YourTable {foreignKey = 2, name = "Lily"},
new YourTable {foreignKey = 2, name = "Rick"},
};
var rawData = (from t in tableData
group t by t.foreignKey into users
select users).ToList();
var key = (from d in rawData
where d.Select(u => u.name).OrderBy(s=> s).SequenceEqual(users2.OrderBy(s=>s))
select d.Key).FirstOrDefault();

Related

Select TOP 1 for each FK in list using Entity Framework

I have a large table where I'm trying to select the top 1 row for each FK in a list.
My table is laid out as:
ChangeId | AssetId | Timestamp
1 1 123
2 2 999
3 1 3478
4 3 344
5 2 1092
Where ChangeId is my PK, AssetId is my FK and Timestamp is the value I'm trying to select.
If I try the following:
var results =
from Asset in _context.Asset
join change in _context.Change on Asset.AssetId equals change.AssetId into potentialChange
from actualChange in potentialChange.OrderByDescending(y => y.ChangeId).Take(1)
select
{
AssetId,
Timestamp
}
Where my expected result would be:
[
{
AssetId: 1,
Timestamp: 3478
},
{
AssetId: 2,
Timestamp: 1092
},
{
AssetId: 3,
Timestamp: 344
}
]
This query flags up the The LINQ expression could not be translated and will be evaluated locally. which is not suitable for a production rollout.
Running a foreach loop and selecting each item out 1 by 1 works, not it's not a performant solution.
Is there a suitable way to achieve the above?
Try to group it by AssetId and take max from each group
var results =
from Asset in _context.Asset
join change in _context.Change on Asset.AssetId equals change.AssetId into potentialChange
group potentialChange by potentialCharge.AssetId into g
select
{
g.Key,
g.Max().Timestamp
}
Use Group By as follows:
List<MyTable> data = new List<MyTable>()
{
new MyTable(){ChangeId = 1, AssetId = 1, Timestamp = 123},
new MyTable(){ChangeId = 2, AssetId = 2, Timestamp = 999},
new MyTable(){ChangeId = 3, AssetId = 1, Timestamp = 123},
new MyTable(){ChangeId = 5, AssetId = 3, Timestamp = 123},
new MyTable(){ChangeId = 5, AssetId = 2, Timestamp = 123},
};
var expectedData = data.OrderByDescending(d => d.Timestamp).GroupBy(d => d.AssetId).Select(g => new
{
AssetId = g.Key,
TimeStamp = g.First().Timestamp
}).ToList();
This will give your expected result.
Try using .First() instead of .Take(1)
LINQ How to take one record and skip rest c#

Listing after implementing ranking skipping numbers

I am trying to achieve ranking functionality as below:
Name Points rank
ram 9 1
kamal 9 1
preet 8 2
lucky 7 3
kishan 6.5 4
devansh 6 5
neha 6 5
I have used below code to achieve this:
finalResult = finalResult.OrderByDescending(i => i.points).ThenBy(i => i.academy).ToList();
finalResult = finalResult.AsEnumerable() // Client-side from here on
.Select((player, index) => new RankingEntity()
{
competitorid = player.competitorid,
firstname = player.firstname,
lastname = player.lastname,
academy = player.academy,
points = player.points,
place = player.place,
eventId = player.eventId,
eventname = player.eventname,
categoryname = player.categoryname,
Rank = index + 1
}).ToList();
var t = (from i in finalResult
let rank = finalResult.First(x => x.points == i.points)
select new
{
Col1 = i,
Rank = rank.Rank
}).ToList();
List<RankingEntity> ttt = new List<RankingEntity>();
foreach (var item in t)
{
var a = item.Col1;
var row = new RankingEntity();
row.competitorid = a.competitorid;
row.firstname = a.firstname;
row.lastname = a.lastname;
row.academy = a.academy;
row.points = a.points;
row.place = a.place;
row.eventId = a.eventId;
row.eventname = a.eventname;
row.categoryname = a.categoryname;
row.Rank = item.Rank;
ttt.Add(row);
}
And i am getting result like below:
Please help what i am doing wrong.
What you are trying to achieve is a ranking of a "group" so group the results by the points and then order the groups. For each item in the group give the same rank.
finalResult.GroupBy(item => item.Points) // Group by points
.OrderDescendingBy(g => g.Key) // Order the groups
.Select((g, index) => new { Data = g, GroupRank = index + 1}) // Rank each group
.SelectMany(g => g.Data.Select(item => new RankingEntity
{
/* properties of each item */
Rank = g.GroupIndex
}); // Flatten groups and set for each item the group's ranking
The problem in your method is that you give the ranking for individual items and not the group. Then when you retrieve the rank for the group (from i in finalResult let rank = finalResult.First(x => x.points == i.points)...) you actually set for each item in the group the ranking of one of the elements in it. Therefore, if you first got the last item of the group - that will be the Rank value of each item in it.
Also notice that in the first line of your code you use ToList. Therefore there is not need to use AsEnumerable in the line under it - it is already a materialized in memory collection.

How to return Distinct Row using LINQ

I have two rows which have all the data same except one column.
I want to show only one row on the UI but one row which has different data should be shown as comma seperated values.
Sample Data
PricingID Name Age Group
1 abc 56 P1
1 abc 56 P2
Output should be :
PricingID Name Age Group
1 abc 56 P1,P2
I am using this approach but it is not working , it gives me two rows only but data i am able to concatenate with comma.
List<PricingDetailExtended> pricingDetailExtendeds = _storedProcedures.GetPricingAssignment(pricingScenarioName, regionCode, productCode, stateCode, UserId, PricingId).ToList();
var pricngtemp = pricingDetailExtendeds.Select(e => new
{
PricingID = e.PricingID,
OpportunityID = e.OpportunityID,
ProductName = e.ProductName,
ProductCD = e.ProductCD
});
pricingDetailExtendeds.ForEach(e=>
{
e.ProductCD = string.Join(",",string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductCD).ToArray())).Split(',').Distinct().ToArray());
e.OpportunityID =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.OpportunityID).ToArray())).Split(',').Distinct().ToArray());
e.ProductName =string.Join(",", string.Join(",", (pricngtemp.ToList().Where(p => p.PricingID == e.PricingID).Select(k => k.ProductName).ToArray())).Split(',').Distinct().ToArray());
}
);
// pricingDetailExtendeds = GetUniquePricingList(pricingDetailExtendeds);
return pricingDetailExtendeds.Distinct().AsEnumerable();
Any body can suggest me better approach and how to fix this issue ?
Any help is appreciated.
You want to use the GroupBy linq function.
I then use the String.Join function to make the groups comma seperated.
So something like this:
var pricingDetailExtendeds = new[]
{
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P1"
},
new
{
PricingID = 1,
Name = "abc",
Age = 56,
Group = "P2"
}
};
var pricngtemp =
pricingDetailExtendeds.GroupBy(pde => new {pde.PricingID, pde.Name, pde.Age})
.Select(g => new {g.Key, TheGroups = String.Join(",", g.Select(s => s.Group))}).ToList();
You can easily extrapolate this to the other fields.
To return the PricingDetailExtended, the just create it in the select. So something like this
.Select(g => new PricingDetailExtended {
PricingID = g.Key.PricingId,
TheGroups = String.Join(",", g.Select(s => s.Group))
}).ToList();
You won't have the field TheGroups though, so just replace that field with the proper one.
An example of what I was describing in my comment would be something along the lines of the following. I would expect this to be moved into a helper function.
List<PriceDetail> list = new List<PriceDetail>
{
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P1"},
new PriceDetail {Id = 1, Age = 56, Name = "abc", group = "P2"},
new PriceDetail {Id = 2, Age = 56, Name = "abc", group = "P1"}
};
Dictionary<PriceDetailKey, StringBuilder> group = new Dictionary<PriceDetailKey, StringBuilder>();
for (int i = 0; i < list.Count; ++i)
{
var key = new PriceDetailKey { Id = list[i].Id, Age = list[i].Age, Name = list[i].Name };
if (group.ContainsKey(key))
{
group[key].Append(",");
group[key].Append(list[i].group);
}
else
{
group[key] = new StringBuilder();
group[key].Append(list[i].group);
}
}
List<PriceDetail> retList = new List<PriceDetail>();
foreach (KeyValuePair<PriceDetailKey, StringBuilder> kvp in group)
{
retList.Add(new PriceDetail{Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
}
you could even convert the final loop into a LINQ expression like:
group.Select(kvp => new PriceDetail {Age = kvp.Key.Age, Id = kvp.Key.Id, Name = kvp.Key.Name, group = kvp.Value.ToString()});
Its worth noting you could do something similar without the overhead of constructing new objects if, for example, you wrote a custom equality comparer and used a list instead of dictionary. The upside of that is that when you were finished, it would be your return value without having to do another iteration.
There are several different ways to get the results. You could even do the grouping in SQL.

LINQ - Left join and Group by and Sum

I have two lists                                                      
var stores = new[]
{
new { Code = 1, Name = "Store 1" },
new { Code = 2, Name = "Store 2" }
};
var orders = new[]
{
new { Code = 1, StoreCode = 1, TotalValue = 14.12 },
new { Code = 2, StoreCode = 1, TotalValue = 24.12 }
};
OUTPUT
StoreName = Store 1 | TotalValue = 38.24
StoreName = Store 2 | TotalValue = 0
How can I translate this into LINQ to SQL?                                             
var lj = (from s in stores
join o in orders on s.Code equals o.StoreCode into joined
from j in joined.DefaultIfEmpty()
group s by new
{
StoreCode = s.Code,
StoreName = s.Name
}
into grp
select new
{
StoreName = grp.Key.StoreName,
TotalValue = ???
}).ToList();
When you doing group join, all orders related to store will be in group, and you will have access to store object. So, simply use s.Name to get name of store, and g.Sum() to calculate total of orders:
var lj = (from s in db.stores
join o in db.orders on s.Code equals o.StoreCode into g
select new {
StoreCode = s.Name,
TotalValue = g.Sum(x => x.TotalValue)
}).ToList();
Note - from your sample it looks like you don't need to group by store name and code, because code looks like primary key and it's unlikely you will have several stores with same primary key but different names.

c# - AutoMapper: how to copy fields from 2 different sources into 1 destination

I have a flat file with a bunch of records, let's say it's a sequence of 2 record types
--- Record1: ID;NAME;SURNAME
--- Record2: AGE;SEX;
Let's call R1 the class representing Record1 and R2 the class representing Record2
In this moment I have an array of R1 and another array of R2
If I have a POCO called Subject that has 5 fields, named exactly as the union of the fields of R1 and R2, how do I configure AutoMapper to do the magic for me?
Now I'm trying this:
var subjects = Mapper.Map<IEnumerable<R1>, List<Subject>>(arrayOfR1s);
Mapper.Map<IEnumerable<R2>, List<Subject>>(arrayOfR2s, subjects);
After the first mapping, I get an array of Subjects, in every element of the array the fields ID, SURNAME, NAME are correctly filled with values. AGE and SEX are left to NULL as expected.
But after the second mapping, all the fields from R1 (ID, NAME, SURNAME) are initialized to NULL and I only get fields from R2 (AGE and SEX).
How do I get the complete union of the fields?
Can someone point me to the right approach?
How about the straightforward dynamic mapping of joined (anonymously typed) objects?
Record1[] firstRecords = new[]
{
new Record1
{
ID = Guid.NewGuid(),
Name = "John", Surname = "Doe"
},
new Record1
{
ID = Guid.NewGuid(),
Name = "Jane", Surname = "Roe"
}
};
Record2[] secondRecords = new[]
{
new Record2 { Age = 20, Sex = Sex.Male },
new Record2 { Age = 20, Sex = Sex.Female }
};
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new
{
ID = first.ID,
Name = first.Name,
Surname = first.Surname,
Age = second.Age,
Sex = second.Sex
};
return Mapper.DynamicMap<Subject>(r);
})
.ToArray();
By the way, you can map these object without using AutoMapper, but using LINQ Select().
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new Subject
{
ID = first.ID,
Name = first.Name,
Surname = first.Surname,
Age = second.Age,
Sex = second.Sex
};
return r;
})
.ToArray();
Update
If you need to copy a lot of properties, please take a look at the Value Injecter. InjectFrom() FTW!
var subjects = firstRecords
.Select((first, index) =>
{
var second = secondRecords[index];
var r = new Subject();
r.InjectFrom(first).InjectFrom(second);
return r;
})
.ToArray();

Categories