LINQ equivalent of this query - linq

SELECT StudentHistoryId, StudentId, Grade, CreatedAt, ModifiedAt, ModifiedBy, Active
FROM TABLENAME TN
INNER JOIN ( SELECT StudentId, MAX(ModifiedAt) AS ModifiedAt FROM TABLENAME GROUP BY StudentId) M
ON TN.StudentId = M.StudentId AND TN.ModifiedAt = M.ModifiedAt

Here's a direct translaton:
var subquery = from tn in dc.TABLENAME
group tn by tn.StudentId into g
select new { StudentId = g.Key, ModifiedAt = g.Max(x => x.ModifiedAt) };
var query = from tn in dc.TABLENAME
join m in subquery
on new { tn.StudentId, tn.ModifiedAt }
equals new { m.StudentId, m.ModifiedAt }
select new
{
tn.StudentHistoryId,
tn.StudentId,
tn.Grade,
tn.CreatedAt,
tn.ModifiedAt,
tn.ModifiedBy,
tn.Active
};

Related

How to return FirstOrDefaultAsync EF Linq to entity List query

I have this Linq to entity query that returns a list of visitors with joins.
I want a similar query to return a single record without the query being a List collection, but changing it to a basic select with FirstOrDefaultAsync gets the error "A query body must end with a select clause or a group clause"
public async Task<List<VisitorDetail>> GetOneVisitor(int visitorId)
{
var query = await (from v in _context.Visitors
where v.Id == visitorId
join d in _context.Categories on v.VisitCategoryId equals d.Id
join c in _context.Counters on v.AssignedCounter equals c.Host into counterGroup
from cg in counterGroup.DefaultIfEmpty()
select new
{
FirstName = v.FirstName,
LastName = v.LastName,
CounterDescription = cg.Description,
VisitCategory = d.Description
}).ToListAsync();
List<VisitorDetail> visitors = new();
foreach (var p in query)
{
visitors.Add(new VisitorDetail
{
FirstName = p.FirstName,
LastName = p.LastName,
CounterDescription = p.CounterDescription,
CategoryDescription = p.VisitCategory
});
}
return visitors;
}
If you want to keep DRY with LINQ to Entities, return IQueryable for your common queries and invoke materialization only when it is needed.
Your method can be rewritten in the following way:
public IQueryable<VisitorDetail> GetVisitorDetails(int visitorId)
{
var query =
from v in _context.Visitors
where v.Id == visitorId
join d in _context.Categories on v.VisitCategoryId equals d.Id
join c in _context.Counters on v.AssignedCounter equals c.Host into counterGroup
from cg in counterGroup.DefaultIfEmpty()
select new VisitorDetail
{
FirstName = v.FirstName,
LastName = v.LastName,
CounterDescription = cg.Description,
CategoryDescription = d.Description
};
return query;
}
var many = await GetVisitorDetails(visitorId).ToListAsync();
var one = await GetVisitorDetails(visitorId).FirstOrDefaultAsync();

how to pass string variable to linq select new {} section

Ii just want to make search functionality with linq with multiple ColumnNames that stored to session variable. I'm using one method:
public void FillGrid(string CommandName,string ColumnName, string SearchText)
That has three string variable that stores session value.
Now I just want to pass ColumnName with this query:
var query1 = (from p in db.Posts
join c in db.Categories on p.Category_id equals c.Id
join u in db.Users on p.User_id equals u.Id
where (p.ToUser_id == user_id || p.ToUser_id == null) && p.User_id != user_id
orderby p.Sent_Datetime descending
select new
{
Id = p.Id,
Title = p.Title,
Publisher = u.First_name + " " + u.Last_name,
ToUser = p.ToUser_id,
PublishDate = p.Sent_Datetime,
IsFile = p.IsFileAttached,
CategoryName = c.Category_name,
status_name = (from s in db.Status where (s.Id == p.status_id) select s.status_name).FirstOrDefault(),
Group_name = (from g in db.Groups where (g.Id == p.group_id) select g.Group_name).FirstOrDefault(),
FileSize = p.TotalFileSize,
ColumnName = Sesssion["ColumnName"].ToString()
}).Where(q => q.ColumnName.Contains(SearchText));
However, ColumnName does not give any text or it may be not part of this query i have to manually give column name because.
for multiple column i have, so i can not use this statement like:
.Where(q => q.Tile.Contains(SearchText));
this query works fine with single column. but there is multiple column i have so i have to set q.ColumnName from outer side.
I would do an extension method for that kind of things, building an expression for your predicate.
public static class Helper
{
public static IQueryable<T> FilterForColumn<T>(this IQueryable<T> queryable, string colName, string searchText)
{
if (colName != null && searchText != null)
{
var parameter = Expression.Parameter(typeof(T), "m");
var propertyExpression = Expression.Property(parameter, colName);
var searchExpression = Expression.Constant(searchText);
var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var body = Expression.Call(propertyExpression, containsMethod, searchExpression);
var predicate = Expression.Lambda<Func<T, bool>>(body, new[] { parameter });
return queryable.Where(predicate);
}
else
{
return queryable;
}
}
}
usage in your case
var query1 = (from p in db.Posts
join c in db.Categories on p.Category_id equals c.Id
join u in db.Users on p.User_id equals u.Id
where (p.ToUser_id == user_id || p.ToUser_id == null) && p.User_id != user_id
orderby p.Sent_Datetime descending
select new
{
Id = p.Id,
Title = p.Title,
Publisher = u.First_name + " " + u.Last_name,
ToUser = p.ToUser_id,
PublishDate = p.Sent_Datetime,
IsFile = p.IsFileAttached,
CategoryName = c.Category_name,
status_name = (from s in db.Status where (s.Id == p.status_id) select s.status_name).FirstOrDefault(),
Group_name = (from g in db.Groups where (g.Id == p.group_id) select g.Group_name).FirstOrDefault(),
FileSize = p.TotalFileSize,
}).FilterForColumn(Sesssion["ColumnName"].ToString(), SearchText);

linq after the join groupby and select

I need to construct a linq like this in regular sql query command:
select t1.vendorcode, t1.location, sum(t1.sales)
from table1 t1
where t1(vendorCode, location) in
(select t2.vendorCode, t2.location from table2 t2)
groupby t1.vendorCode, t1.location
I construct the linq as following:
query = from t1 in table1
where ...
join t2 in table2 on new
{
t2.vendorcode, t2.location
} equals new
{
t1.vendorcode, t1.location
}
The question I have is: How should I construct this linq? Do I need another subquery or can i add more group by and select statement to complete this linq?
You don't need to add another group by clause - you just need to select the sum:
var query = from t1 in table1
join t2 in table2
on new { t1.vendorcode, t1.location } equals
new { t2.vendorcode, t2.location }
group t1 by new { t1.vendorcode, t1.location } into g
select new {
g.Key.vendorcode,
g.Key.location,
g.Sum(t1 => t1.sale)
};
That will work if there's only a single record in table2 with any particular vendorcode/location pair. However, if there can be multiple records like that, then it doesn't work - and you probably want something more like:
var query = from t1 in table1
where table2.Select(t2 => new { t2.vendorcode, t2.location })
.Contains(new { t1.vendorcode, t1.location })
group t1 by new { t1.vendorcode, t1.location } into g
select new {
g.Key.vendorcode,
g.Key.location,
g.Sum(t1 => t1.sale)
};
That's logically your "exists" version.
This should do it
var query =
from t1 in table1
join t2 in table2
on new { vc = t1.vendorcode, lc = t1.location }
equals new { vc = t2.vendorcode, lc = t2.location }
group t1 by new { vc = t1.vendorcode, lc = t1.location };

multiple JOIN + SUM() query in LINQ -> can't loop in view

I'm trying to translate an SQL query into LINQ, but after numerous attempts, I'm still not there... Can anyone help ?
This is my working SQL statement
SELECT Users.email, SUM(Skills.level) AS SkillLevel
FROM Skills INNER JOIN
SkillsPerUser ON Skills.pk_skill_id = SkillsPerUser.fk_skill_id INNER JOIN
Users ON SkillsPerUser.fk_user_id = Users.pk_userid
GROUP BY Users.email
ORDER BY SkillLevel DESC
This is what I came up with so far, but it lacks a sum() where I hard coded the number 3, that should be the sum of Skills.level:
var allSkillsPerUser = from u in dc.Users
join spu in dc.SkillsPerUsers on u.pk_userid equals spu.fk_user_id
join s in dc.Skills on spu.fk_skill_id equals s.pk_skill_id
select new { Email = u.email, Level = s.level } into su
group su by su.Email into gsu
select new { Email = gsu.Key, SkillLevel = gsu.Sum(su => su.Level) };
ViewBag.spu = allSkillsPerUser.ToList();
The view bag gives the following error (Email can't be found, while in the variables below you can see that they do exist...):
context.Skills
.Join(context.SkillsPerUser, s => s.pk_skill_id, spu => spu.fk_skill_id, (s, spu) => new { Skill = s, SkillToUser = spu })
.Join(context.Users, sspu => sspu.SkillToUser.fk_userId, u => u.pk_userid, (sspu, u) => new { Email = u.Email, SkillLevel = sspu.Skill.level })
.GroupBy(su => su.Email)
.Select(g => new { Email = g.Key, SkillLevel= g.Sum(su => su.Level) })
.OrderByDescending(g => g.SkillLevel)
It's a bit simpler if you have navigation properties set up on your entities:
context.SkillsPerUser
.Select(spu => new { Email = spu.User.email, Level = spu.Skill.level }) // guessing at the navigation property names here
.GroupBy(su => su.Email)
.Select(g => new { Email = g.Key, SkillLevel = g.Sum(su => su.Level) })
.OrderByDescending(g => g.SkillLevel)
Or, using the query syntax
from u in dc.Users
join spu in dc.SkillsPerUsers on u.pk_userid equals spu.fk_user_id
join s in dc.Skills on spu.fk_skill_id equals s.pk_skill_id
select new { Email = u.email, Level = s.level } into su
group su by su.Email into gsu
select new { Email = gsu.Key, SkillLevel = gsu.Sum(su => su.Level) }
To order by the sum you can do this:
from u in dc.Users
join spu in dc.SkillsPerUsers on u.pk_userid equals spu.fk_user_id
join s in dc.Skills on spu.fk_skill_id equals s.pk_skill_id
select new { Email = u.email, Level = s.level } into su
group su by su.Email into gsu
select new { Email = gsu.Key, SkillLevel = gsu.Sum(su => su.Level) } into userSkills
orderby userSkills.SkillLevel descending

SQL to Linq Conversion

I have sql as below
SELECT Q.MaterialID AS MaterialID, Q.ProductID AS ProductID, QB.Quantity AS Quantity,
Q.ParameterID AS ParameterID, SUM((Q.ParameterValue * Q.Quantity)/Q.TotalTonnes) AS ParameterValue
FROM #Quality Q
INNER JOIN #QuantityBreakdown QB
ON ((Q.MaterialID = QB.MaterialID) OR (Q.MaterialID IS NULL AND QB.MaterialID IS NULL))
AND ((Q.ProductID = QB.ProductID) OR (Q.ProductID IS NULL AND QB.ProductID IS NULL))
GROUP BY Q.MaterialID, Q.ProductID, ParameterID, QB.Quantity
conversion to LINQ..... struck at ???
var enumerable = from final in (from q in qualities
from qb in quantityBreakDowns
where q.ProductID == qb.ProductID && q.MaterialID == qb.MaterialID
select new
{
q.MaterialID,
q.ProductID,
q.ParameterID,
qb.Quantity,
ParameterValue = ((q.ProductID*q.Quantity)/q.TotalTonnes)
}
)
group final by new
{
final.MaterialID,
final.ProductID,
final.ParameterID,
???
}
into finalresult select finalresult;
Is there some other good way to do this.
Thanks
Ok solved this as:
from final in
(from q in qualities
from qb in quantityBreakDowns
where q.ProductID == qb.ProductID && q.MaterialID == qb.MaterialID
select new
{
q.MaterialID,
q.ProductID,
q.ParameterID,
qb.Quantity,
ParameterValue = ((q.ActualValue*q.Quantity)/q.TotalTonnes)
}
)
group final by new
{
final.MaterialID,
final.ProductID,
final.ParameterID,
final.Quantity
}
into finalresult
select new
{
finalresult.Key.MaterialID,
finalresult.Key.ProductID,
finalresult.Key.ParameterID,
finalresult.Key.Quantity,
ActualValue = finalresult.Sum(fq => fq.ParameterValue)
};

Resources