Add correlated subqueries to Select statement using QueryOver - c#

I'm attempting to convert a SQL statement to use QueryOver (in hopes of pre-fetching the entities part of the response) but I'm having trouble figuring out how to add a correlated subquery to the Select statement (all the examples I found have only shown using a subquery in the Where clause).
This is the query I'm trying to convert:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
var projectWhereClause = project != null ? "AND f1.project_id = " + project.Id : "";
var query = Session.CreateSQLQuery(string.Format(#"
SELECT
ft.id as FEEDBACK_TYPE_ID,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0}) as ALL_FEEDBACK_COUNT,
(SELECT COUNT(*) FROM FEEDBACK f1 WHERE ft.id = f1.feedback_type_id AND f1.archive_ind = 0 {0} AND feedback_status_id = {1}) as PENDING_FEEDBACK_COUNT
FROM feedback f
RIGHT JOIN feedback_type ft on f.feedback_type_id = ft.id WHERE ft.RESTRICTED_IND = 0
GROUP BY ft.id, ft.sort_order
ORDER BY ft.sort_order",
projectWhereClause,
pendingFeedbackStatus.Id
))
.SetResultTransformer(Transformers.AliasToEntityMap);
var results = query.List<IDictionary>();
return results.Select(r =>
new FeedbackTypeSummary
{
Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])),
AllFeedbackCount = Convert.ToInt32(r["ALL_FEEDBACK_COUNT"]),
PendingFeedbackCount = Convert.ToInt32(r["PENDING_FEEDBACK_COUNT"])
}).ToList();
and here is what I have so far (which is mostly everything minus the correlated subqueries and some additional filtering added to the subqueries):
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending");
Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
var foo = Session.QueryOver<Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
// TODO: Add correlated subqueries here?
.SelectGroup(() => feedbackTypeAlias.Id)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc;
var test = foo.List<object[]>();
I'd also like to find a way to return a full FeedbackType entity of from the statement, instead of returning feedbackTypeAlias.Id and then having to perform Type = Get(Convert.ToInt32(r["FEEDBACK_TYPE_ID"])) in a loop as I do in the original.

I felt like I looked for this 10 times, but I overlooked the .SelectSubQuery() method which provided the desired correlated subqueries. This answer tipped me off - https://stackoverflow.com/a/8143684/191902.
Here is the full QueryOvery version:
var pendingFeedbackStatus = Session.QueryOver<FeedbackStatus>().Where(fs => fs.Name == "pending").SingleOrDefault();
Domain.Feedback.Feedback feedbackAlias = null;
FeedbackType feedbackTypeAlias = null;
var allFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived);
var pendingFeedback = QueryOver.Of<Domain.Feedback.Feedback>()
.Where(f => f.Type.Id == feedbackTypeAlias.Id)
.Where(f => !f.IsArchived)
.Where(f => f.Status.Id == pendingFeedbackStatus.Id);
if (project != null)
{
allFeedback.Where(f => f.Project.Id == project.Id);
pendingFeedback.Where(f => f.Project.Id == project.Id);
}
FeedbackTypeSummary result = null;
var query = Session.QueryOver<Domain.Feedback.Feedback>(() => feedbackAlias)
.Right.JoinAlias(f => f.Type, () => feedbackTypeAlias, ft => !ft.IsRestricted)
.SelectList(list => list
.SelectSubQuery(allFeedback.ToRowCountQuery()).WithAlias(() => result.AllFeedbackCount)
.SelectSubQuery(pendingFeedback.ToRowCountQuery()).WithAlias(() => result.PendingFeedbackCount)
.SelectGroup(() => feedbackTypeAlias.Id).WithAlias(() => result.TypeId)
.SelectGroup(() => feedbackTypeAlias.Name).WithAlias(() => result.TypeName)
.SelectGroup(() => feedbackTypeAlias.NamePlural).WithAlias(() => result.TypeNamePlural)
.SelectGroup(() => feedbackTypeAlias.SortOrder)
)
.OrderBy(() => feedbackTypeAlias.SortOrder).Asc
.TransformUsing(Transformers.AliasToBean<FeedbackTypeSummary>());
var results = query.List<FeedbackTypeSummary>();
return results;
I also was able to populate my FeedbackTypeSummary DTO from a single query, although I couldn't find a way to alias an entity and ended up extracting a few of the needed properties from FeedbackType into FeedackTypeSummary (which is probably a better thing to do anyways).

Related

Linq Group by with nested collection throwing Cannot perform an aggregate function

I want to do something like this:
var projectHistory = await Context.Tasks.GroupBy(x => x.ProjectId).Select(x => new ProjectHistoryStatModel
{
ProjectId = x.Key,
CompletedTasks = x.Where(y => y.StatusId == 4).Count(),
InProgressTasks = x.Where(y => y.StatusId == 3).Count(),
DelayedTasks = x.Where(y => y.EndDate < DateTime.Now && y.StatusId != 4).Count(),
DependentTasks = x.Where(y => y.Dependents.Any()).Count(),
TotalTasks = x.Count()
}).ToListAsync();
But DependentTasks property DependentTasks = x.Where(y => y.Dependents.Any()).Count(), is throwing:
Cannot perform an aggregate function on an expression containing an aggregate or a subquery.
Well y.Dependents is a Collection that is why its throwing the problem, I also tried this: DependetTasks = Context.TaskDependencies.Where(y => x.Any(z => z.Id == y.TaskId)).Count(), and it throws the same error.
Can you guys show me a way of doing this in the same request to the DB?
Regards
Assuming that you are using EF Core < 6.0, you can try to rewrite your query in the following way:
var query =
from t in Context.Tasks
group new { t, HasDependents = t.Dependents.Any() } by t.ProjectId into g
select new ProjectHistoryStatModel
{
ProjectId = g.Key,
CompletedTasks = g.Where(y => y.t.StatusId == 4).Count(),
InProgressTasks = g.Where(y => y.t.StatusId == 3).Count(),
DelayedTasks = g.Where(y => y.t.EndDate < DateTime.Now && y.t.StatusId != 4).Count(),
DependentTasks = g.Where(y => y.HasDependents).Count(),
TotalTasks = g.Count()
};
var projectHistory = await query.ToListAsync();
EF Core up to 6.0 do not support translating navigation properties after GroupBy.

LINQ error: System.NotSupportedException on GroupBy Selection

When var items = q3.ToList(); executes from the code snippet below, it throws exception System.NotSupportedException. The aim is to get the list of items after the grouping.
Exception:
Unable to create a constant value of type 'AppDB.Stage.Rules'. Only primitive types or enumeration types are supported in this context.
var valuations = context.stage
.Where(q => q.stageID == stageID && !rules.ToList().Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
var q3 = valuations.Select(y => new StageType
{
TypeKey = y.Key,
TypeName= "UNKNOWN",
});
var items = q3.ToList(); //error here
Your database doesn't have any idea of what your in-memory rules actually is, and in-turn cant convert this statement to SQL
The simplest solution will be to leave it as an IQueryable and don't use ToList,
context.stage
.Where(q => q.stageID == stageID && !rules.Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
However, if it is already in memory, then you will have to send the values as a primitive list
var type1s = rules.Select(x => x.type1);
var type2s = rules.Select(x => x.type2);
context.stage
.Where(q => q.stageID == stageID && !type1s.Contains(q.type1) && !type2s.Contains(q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
Because rules.ToList() makes results in memory, you can't use it inside an IQueryable that executes over SQL. You should first bring your data into memory and then narrow it by other in-memory object.
var valuations = context.stage.ToList()
.Where(q => q.stageID == stageID && !rules.ToList().Any(r => r.type1 == q.type1 || r.type2 == q.type2))
.GroupBy(q => q.stageKey)
.Select(g => g) ;
var q3 = valuations.Select(y => new StageType
{
TypeKey = y.Key,
TypeName= "UNKNOWN",
});
var items = q3.ToList();

Transform SQL Query to LINQ

I would like if someone helps me to convert this SQL Query to LINQ syntax.
SELECT i.Id, i.Condomino as Condomino, i.Interno as Interno,
p.NomePiano as NomePiano, s.Nome as NomeCondominio,
m.millesimi_fabbisogno_acs, m.millesimi_fabbisogno_riscaldamento
FROM Interni i
INNER JOIN Piani p ON i.IdPiano = p.Id
INNER JOIN Stabili s ON i.IdStabile = s.Id
LEFT JOIN MillesimiTabellaC m ON i.Id = m.idInterno
WHERE s.IdCondominio = {0}
I tried using something like this, but is not working..
return _Db.Interni.Include("Piani").Where(x => x.Piani.IdCondominio == iidcond).ToList();
I made it on-the-spot (so it's not tested), but perhaps it's enough to give you the idea. I'm also assuming that your DB model has foreign keys set up.
var result = _db.Interni
.Where(i => i.Stabili.IdCondominio = [value])
.Select(i => new
{
i.Id,
Condomino = i.Condomino,
Interno = i.Interno,
NomePiano = i.Piani.NomePiano,
NomeCondominio = i.Stabili.Nome,
i.MillesimiTabellaC.millesimi_fabbisogno_acs,
i.MillesimiTabellaC.millesimi_fabbisogno_riscaldamento
})
.ToList();
update
In case you don't have a foreign key between Interni and MillesimiTabellaC, try this:
var result = _db.Interni
.Include(i => i.Piani)
.Include(i => i.Stabili)
.Where(i => i.Stabili.IdCondominio = [value])
.Select(i => new
{
Interni = i,
MillesimiTabellaC = _db.MillesimiTabellaC.Where(m => i.Id = m.idInterno)
})
.Select(x => new
{
Id = x.Interni.Id,
Condomino = x.Interni.Condomino,
Interno = x.Interni.Interno,
NomePiano = x.Interni.Piani.NomePiano,
NomeCondominio = x.Interni.Stabili.Nome,
x.MillesimiTabellaC?.millesimi_fabbisogno_acs,
x.MillesimiTabellaC?.millesimi_fabbisogno_riscaldamento
})
.ToList();

Converting query to lambda expression with joins and where clause

I have this query and I'm having trouble converting into a lambda expression
SELECT [dbo].[Prospects].[Id]
,[UserId]
,[NewId]
,[dbo].[Prospects].[Status]
FROM [dbo].[Prospects] join [dbo].[User] on [dbo].[User].Id = [dbo]. [Prospects].UserId
where [dbo].[Prospects].NewId = 3 and [dbo].[User].IsActive = 1
This is what i got, but it is not working
var result = Workspace.Prospects.Join
(Workspace.Users, pros => pros.UserId,
use => use.Id, (pros, use)
=> new { Prospect = pros, User = use}).Where
(both => both.User.IsActive == true && both.Prospect.NewId == idVacante)
.OrderBy(both => both.Prospect.Id).AsEnumerable().ToList();
List<Prospect> prospects = result.Cast<Prospect>().ToList();
It's not that hard. you can filter the tables before joining them
var result = Workspace.Prospects.Where(x=> x.NewId == 3)
.Join(Workspace.Users.Where(x => x.IsActive == 1),
p => p.UserId,
u => u.Id,
(p, u) => new { p.Id, p.UserId, p.NewId, p.Status })

Group By using Select statement in Linq

var query = from i in SFC.Supplies_ReceiveTrans
orderby i.Poprctnm descending
select new { RR = i.Poprctnm };
Result:
RR-01,
RR-01,
RR-02,
RR-02,
RR-02,
RR-TEST,
RR-TEST,
How do i group RR in this kind of statement
Result:
RR-01,
RR-02,
RR-TEST
just a few modification to ask if is it possible to do this one or what you have in your mind? Sorry for asking too much just really interested in learning more on linq.. how do i convert it into string coz its showing true or false.. boolean statement
var query = SFC.Supplies_ReceiveTrans.Select(s =>
s.Poprctnm.StartsWith(p))
.Distinct()
.OrderBy(p => p)
.Select(p => new { RR = p })
.Take(10);
You can use Distinct or GroupBy methods in this case
var query = SFC.Supplies_ReceiveTrans.Select(s=> s.Poprctnm)
.Distinct()
.OrderByDescending(p => p)
.Select(p=> new { RR = p });
if you use OrderByDescending then the result will be
RR-TEST
RR-02
RR-01
But I think you want OrderBy then the result will be
RR-01
RR-02
RR-TEST
So try below
var query = SFC.Supplies_ReceiveTrans.Select(s=> s.Poprctnm)
.Distinct()
.OrderBy(p => p)
.Select(p=> new { RR = p });
var query = SFC.Supplies_ReceiveTrans
.GroupBy(x=>x.Poprctnm)
.Select(g=>g.First())
.OrderByDescending(x=>x.Poprctnm)
.Select(x=>new { RR = x.Poprctnm });
If you want to get result as group:
var query = SFC.Supplies_ReceiveTrans
.GroupBy(x=>x.Poprctnm)
.OrderByDescending(g=>g.Key);
var result = SFC.Supplies_ReceiveTrans
.Select(x => new { RR = x.Poprctnm })
.Distinct()
.OrderByDescending(x => x.Poprctnm);
Looks like you need Distinct here, not group
var query = SFC.Supplies_ReceiveTrans
.Select(x => new {RR = i.Poprctnm})
.Distinct()
.OrderByDescending(i => i);

Categories