C# Retrieve Common Records by LINQ - c#

I have a database with the table that keeps user_ids and tag_ids. I want to write a function which takes two user_ids and returns the tag_ids that both users have in common.
These are the sample rows from the database:
User_id Tag_id
1 100
1 101
2 100
3 100
3 101
3 102
What I want from my function is that when I call my function like getCommonTagIDs(1, 3), it should return (100,101). What I did so far is that I keep the rows which are related to user_id in two different lists and then using for loops, return the common tag_ids.
using (TwitterDataContext database = TwitterDataContext.CreateTwitterDataContextWithNoLock())
{
IEnumerable<Usr_Tag> tags_1 = database.Usr_Tags.Where(u => u.User_id == userID1).ToList();
IEnumerable<Usr_Tag> tags_2 = database.Usr_Tags.Where(u => u.User_id == userID2).ToList();
foreach (var x in tags_1)
{
foreach (var y in tags_2) {
if (x.Tag_id == y.Tag_id) {
var a =database.Hashtags.Where(u => u.Tag_id==x.Tag_id).SingleOrDefault();
Console.WriteLine(a.Tag_val);
}
}
}
}
What I want to ask is that, instead of getting all rows from database and searching for the common tag_ids in the function, I want to get the common tag_ids directly from database with LINQ by making the calculations on the database side. I would be grateful if you could help me.
This is the SQL that I wrote:
SELECT [Tag_id]
FROM [BitirME].[dbo].[User_Tag]
WHERE USER_ID = '1' AND Tag_id IN (
SELECT [Tag_id]
FROM [BitirME].[dbo].[User_Tag]
where USER_ID = '3')

What you want is the "Intersection" of those two sets:
var commonTags = database.Usr_Tags.Where(u => u.User_id == userID1).Select(u => u.Tag_id)
.Intersect(database.Usr_Tags.Where(u => u.User_id == userID2).Select(u => u.Tag_id));
And voila, you're done.
Or, to clean it up a bit:
public static IQueryable<int> GetUserTags(int userId)
{
return database.Usr_Tags
.Where(u => u.User_id == userId)
.Select(u => u.Tag_id);
}
var commonTags = GetUserTags(userID1).Intersect(GetUserTags(userID2));

Here's one way to do it:
int[] users = new int[] {1,3}; // for testing
database.Ustr_Tags.Where(t => users.Contains(t.User_id))
.GroupBy(t => t.Tag_id)
.Where(g => users.All(u => g.Any(gg=>gg.User_id == u))) // all tags where all required users are tagged
.Select(g => g.Key);
One benefit of this one is it can be used for any number of users (not just 2).

If i got it right, query like this is maybe what you need
var q = from t in database.Usr_Tags
//all Usr_Tags for UserID1
where t.User_Id == userID1 &&
//and there is a User_tag for User_ID2 with same Tag_ID
database.User_Tags.Any(t2=>t2.User_ID==userID2 && t2.Tag_ID==t.Tag_ID)
select t.Tag_Id;
var commonTags = q.ToList();

Related

How can I get build a query based on array of IDs using EF

I'm trying to build a query selecting all records containing IDs which are stored in the list using that code:
var assistsIds = _context.Assistances.Where(c => c.IdUser == user.IdUser)
.Select(x => x.Owner.IdOwner).ToList();
Then I'm going through all the list elements to get a query:
var query = _context.Accounts.Where(_ => _.IsDeleted != 1);
foreach(var assist in assistsIds)
{
query = query.Where(_ => _.IdOwner == assist);
}
The result is that I'm getting something like this:
SELECT * FROM Accounts WHERE IdOwner = 1 AND IdOwner = 2 ...etc
Instead of:
SELECT * FROM Accounts WHERE IdOwner = 1 OR IdOwner = 2 ... etc
Is there a way to apply OR operator, or maybe there is some other way to achieve that?
You could use Contains:
var query = _context.Accounts
.Where(_ => _.IsDeleted != 1 && assistsIds.Contains(_.IdOwner));
This will return all records which match an Id in the assistsIds list.

How to write linq query for this sql statement

How would you write a linq query with the following SQL statement. I've tried several methods referenced on stackoverflow but they either don't work with the EF version I'm using (EF core 3.5.1) or the DBMS (SQL Server).
select a.ProductID, a.DateTimeStamp, a.LastPrice
from Products a
where a.DateTimeStamp = (select max(DateTimeStamp) from Products where a.ProductID = ProductID)
For reference, a couple that I've tried (both get run-time errors).
var results = _context.Products
.GroupBy(s => s.ProductID)
.Select(s => s.OrderByDescending(x => x.DateTimeStamp).FirstOrDefault());
var results = _context.Products
.GroupBy(x => new { x.ProductID, x.DateTimeStamp })
.SelectMany(y => y.OrderByDescending(z => z.DateTimeStamp).Take(1))
Thanks!
I understand you would like to have a list of the latest prices of each products?
First of all I prefer to use group by option even over 1st query
select a.ProductID, a.DateTimeStamp, a.LastPrice
from Products a
where a.DateTimeStamp IN (select max(DateTimeStamp) from Products group by ProductID)
Later Linq:
var maxDateTimeStamps = _context.Products
.GroupBy(s => s.ProductID)
.Select(s => s.Max(x => x.DateTimeStamp)).ToArray();
var results = _context.Products.Where(s=>maxDateTimeStamps.Contains(s.DateTimeStamp));
-- all assuming that max datetime stamps are unique
I've managed to do it with the following which replicates the correlated sub query in the original post (other than using TOP and order by instead of the Max aggregate), though I feel like there must be a more elegant way to do this.
var results = from x
in _context.Products
where x.DateTimeStamp == (from y
in _context.Products
where y.ProductID == x.ProductID
orderby y.DateTimeStamp descending
select y.DateTimeStamp
).FirstOrDefault()
select x;
I prefer to break up these queries into IQueryable parts, do you can debug each "step".
Something like this:
IQueryable<ProductOrmEntity> pocoPerParentMaxUpdateDates =
entityDbContext.Products
//.Where(itm => itm.x == 1)/*if you need where */
.GroupBy(i => i.ProductID)
.Select(g => new ProductOrmEntity
{
ProductID = g.Key,
DateTimeStamp = g.Max(row => row.DateTimeStamp)
});
//// next line for debugging..do not leave in for production code
var temppocoPerParentMaxUpdateDates = pocoPerParentMaxUpdateDates.ToListAsync(CancellationToken.None);
IQueryable<ProductOrmEntity> filteredChildren =
from itm
in entityDbContext.Products
join pocoMaxUpdateDatePerParent in pocoPerParentMaxUpdateDates
on new { a = itm.DateTimeStamp, b = itm.ProductID }
equals
new { a = pocoMaxUpdateDatePerParent.DateTimeStamp, b = pocoMaxUpdateDatePerParent.ProductID }
// where
;
IEnumerable<ProductOrmEntity> hereIsWhatIWantItems = filteredChildren.ToListAsync(CancellationToken.None);
That last step, I am putting in an anonymous object. You can put the data in a "new ProductOrmEntity() { ProductID = pocoMaxUpdateDatePerParent.ProductID }...or you can get the FULL ProductOrmEntity object. Your original code, I don't know if getting all columns of the Product object is what you want, or only some of the columns of the object.

Nested 3 level LINQ query

Here is my working SQL query I want write LINQ to
I have no idea to convert write 3 level nested query
Select *
from demo.dbo.Account
where accType = 3
and LinkAcc IN (
select accNum
from demo.dbo.Account
here inkAcc IN (
select accNum
from demo.dbo.Account
where memberid = 20
and accType= 0
)
and accType = 2
)
When writing LINQ equivalents to a SQL IN(), you have to think about it in reverse.
Rather than the SQL
where entity-value IN sub-values
the LINQ expression becomes
where sub-values contains entity-value
Because writing this in one monolithic LINQ statement is mind-bending, I have broken each subquery into a separate variable.
using System.Linq;
public IEnumerable<Account> FilterAccounts(IEnumerable<Account> accounts)
// start with the deepest subquery first
var memberAccountNums = accounts
.Where(x => x.MemberId == 20 && x.AccType == 0)
.Select(x => x.AccNum)
.ToArray();
var linkAccountNums = accounts
.Where(x => x.AccType == 2 && memberAccountNums.Contains(x.AccNum))
.Select(x => x.AccNum)
.ToArray();
var result = accounts
.Where(x => x.AccType == 3 && linkAccountNums.Contains(x.AccNum))
.ToArray();
return result;
}
I have used a method here to demonstrate a compilable version of the code (assuming the class and property names are correct). You would obviously want to parameterise this to meet your needs.
If you want to combine them for some reason (say LINQ-to-SQL), then you could write it as one query, or you could instead use a series of IQueryable variables instead of calling .ToArray().
I have created a working demo here: https://dotnetfiddle.net/pg0WLC
I assume that the logic is you want to return all accounts with AccType 3 where there is also a matching AccNum for AccType 0 and 2? This assumes that the MemberId property will match if the AccNum properties do.
Another way of doing this with LINQ would be to use group by:
int[] types = new int[] { 0, 2, 3 };
return accounts
.Where(x => x.MemberId == 20 && types.Contains(x.AccType))
.GroupBy(x => x.AccNum)
.Where(x => x.Count() == types.Count())
.SelectMany(x => x)
.Where(x => x.AccType == 3);

Most efficient way to order by and update a row in LINQ

I'm using this code to rank players in a game.
private void RecalculateUserRanks(GWDatabase db)
{
// RankedScore is a precalculated Double.
var users = db.UserStatistics.Where(x => x.RankedScore > 0);
var usersWithRank = users.OrderByDescending(x => x.RankedScore)
.Select(x => new
{
x.Id,
x.RankedScore
});
int position = 0;
foreach (var u in usersWithRank)
{
position++;
db.UserStatistics.First(x => x.Id == u.Id).Rank = position;
}
db.SaveChanges();
}
It's not the prettiest and as the number of players grows this will probably take some time and use a bit of memory.
I could do this in pure TSQL like this:
;WITH r AS
(
SELECT
[Id]
,[RankedScore]
,ROW_NUMBER() OVER(ORDER BY [RankedScore] DESC) AS Rnk
FROM [dbo].[UsersStatistics]
)
UPDATE u
SET u.Rank = r.Rnk
FROM [dbo].[UsersStatistics] u
INNER JOIN r ON r.Id = u.Id
But I would prefer to keep all my logic in the C# code as the database gets rebuilt all the time right now (and all other logic is there as well).
So my question is if there is a smarter way to do this in C# LINQ (or Lambda if thats your thing) without iterating over it in a for loop, and without dragging all the data outside of the SQL?
I assume by 'efficient' you mean 'efficient to read'. For a faster calculation you might consider to use a sorted list for db.UserStatistics; Those keep themselves sorted, while using log n time to insert a new member.
This is pretty much the same you posted, except lazy-evaluation might save a little time:
//get sorted list of IDs
var SortedIds = db.UserStatistics
.OrderByDescending(x => x.RankedScore)
.Select(x => x.Id);
//Fill in Values into result-set
db.UserStatistics = db.UserStatistics
.Where(x => x.RankedScore > 0)
.ForEach(x => u.Rank = SortedIds.IndexOf(x.id));
It seems a little inconsistent to have ranked and unranked players together.
This will give unranked players the rank -1 while saving a step. The downside would be, that all user will be altered, instead just those with a rank:
db.UserStatistics = db.UserStatistics.ForEach(u =>
u.Rank = db.UserStatistics
.Where(x => x.RankedScore > 0)
.OrderByDescending(x => x.RankedScore)
.IndexOf(u.id));

LINQ Using Max() to select a single row

I'm using LINQ on an IQueryable returned from NHibernate and I need to select the row with the maximum value(s) in a couple of fields.
I've simplified the bit that I'm sticking on. I need to select the one row from my table with the maximum value in one field.
var table = new Table { new Row(id: 1, status: 10), new Row(id: 2, status: 20) }
from u in table
group u by 1 into g
where u.Status == g.Max(u => u.Status)
select u
This is incorrect but I can't work out the right form.
BTW, what I'm actually trying to achieve is approximately this:
var clientAddress = this.repository.GetAll()
.GroupBy(a => a)
.SelectMany(
g =>
g.Where(
a =>
a.Reference == clientReference &&
a.Status == ClientStatus.Live &&
a.AddressReference == g.Max(x => x.AddressReference) &&
a.StartDate == g.Max(x => x.StartDate)))
.SingleOrDefault();
I started with the above lambda but I've been using LINQPad to try and work out the syntax for selecting the Max().
UPDATE
Removing the GroupBy was key.
var all = this.repository.GetAll();
var address = all
.Where(
a =>
a.Reference == clientReference &&
a.Status == ClientStatus.Live &&
a.StartDate == all.Max(x => x.StartDate) &&
a.AddressReference == all.Max(x => x.AddressReference))
.SingleOrDefault();
I don't see why you are grouping here.
Try this:
var maxValue = table.Max(x => x.Status)
var result = table.First(x => x.Status == maxValue);
An alternate approach that would iterate table only once would be this:
var result = table.OrderByDescending(x => x.Status).First();
This is helpful if table is an IEnumerable<T> that is not present in memory or that is calculated on the fly.
You can also do:
(from u in table
orderby u.Status descending
select u).Take(1);
You can group by status and select a row from the largest group:
table.GroupBy(r => r.Status).OrderByDescending(g => g.Key).First().First();
The first First() gets the first group (the set of rows with the largest status); the second First() gets the first row in that group.
If the status is always unqiue, you can replace the second First() with Single().
Addressing the first question, if you need to take several rows grouped by certain criteria with the other column with max value you can do something like this:
var query =
from u1 in table
join u2 in (
from u in table
group u by u.GroupId into g
select new { GroupId = g.Key, MaxStatus = g.Max(x => x.Status) }
) on new { u1.GroupId, u1.Status } equals new { u2.GroupId, Status = u2.MaxStatus}
select u1;
What about using Aggregate?
It's better than
Select max
Select by max value
since it only scans the array once.
var maxRow = table.Aggregate(
(a, b) => a.Status > b.Status ? a : b // whatever you need to compare
);
More one example:
Follow:
qryAux = (from q in qryAux where
q.OrdSeq == (from pp in Sessao.Query<NameTable>() where pp.FieldPk
== q.FieldPk select pp.OrdSeq).Max() select q);
Equals:
select t.* from nametable t where t.OrdSeq =
(select max(t2.OrdSeq) from nametable t2 where t2.FieldPk= t.FieldPk)
Simply in one line:
var result = table.First(x => x.Status == table.Max(y => y.Status));
Notice that there are two action.
the inner action is for finding the max value,
the outer action is for get the desired object.

Categories