I am trying to recreate the following query in NHibernate:
SELECT DISTINCT
orderid ,
tasktype
FROM "Task"
WHERE orderid IN ( SELECT orderid
FROM "Task"
GROUP BY orderid
HAVING COUNT(orderid) > 1 )
ORDER BY orderid
In NH, I need a QueryOver that returns a list of task types based on the order id. Basically, I am iterating over each task and for each task that occurs more than once, (because of a different task type), i need to add all those tasks into a list that gets returned to the client. This is what I have tried so far with NH.
var taskList = new List<Task>();
PendingTasks = session.QueryOver<Model.Task>()
.WhereRestrictionOn(c => c.OrderId).IsIn(taskList)
.SelectList
(list => list
.SelectGroup(b => b.OrderId)
.Select(b => b.TaskType)
)
.Where(Restrictions.Eq(Projections.Count<Model.Task>(x => x.OrderId), taskList.Count() > 1))
.TransformUsing((Transformers.AliasToBean<TaskType>()))
.List<TaskType>()
I have just started NH, and found some examples on here regarding the use of grouping and having. The property from the model I am returning to the client is here with TaskType being a simple enum.
public List<TaskType> PendingTasks { get; set; }
It seems to me so far, that the QueryOver is trying to return an IList, against my target type of List, however there is no .ToList(), so I do not know what this will return. Any help matching the sql query above is helpful.
UPDATE: Entire Method:
private static readonly string[] TaskTypeKeys = Enum.GetNames(typeof(TaskType));
var tasksByType = new List<TaskGroup>();
Task taskObject = null;
QueryOver subQuery = QueryOver.Of<Task>()
.Select(
Projections.GroupProperty(
Projections.Property<Task>(t => t.OrderId)
)
)
.Where(Restrictions.Gt(Projections.Count<Task>(t => t.OrderId), 1));
foreach (var type in TaskTypeKeys)
{
TaskType typeEnum;
Enum.TryParse(type, out typeEnum);
var tasks = session.QueryOver<Model.Task>()
.Where(
task =>
task.TaskType == typeEnum &&
task.Completed == false &&
task.DueDate <= DateTime.Today
)
.OrderBy(t => t.DueDate).Asc
.List<Model.Task>()
.Select(t => new Task()
{
Id = t.Id,
OrderId = t.OrderId,
CustomerId = t.CustomerId,
CustomerName = t.CustomerName,
GroupName = t.GroupName,
TripDate = t.TripDate,
TaskType = TaskTypeTitles[t.TaskType.ToString()],
DueDate = t.DueDate,
Completed = t.Completed,
IsActiveTask = t.IsActiveTask,
PendingTasks = session.QueryOver<Task>(() => taskObject)
// the WHERE clause: OrderId IN (subquery)
.WithSubquery
.WhereProperty(() => taskObject.OrderId)
ERROR-------> .In(subQuery)
// the rest of your SELECT/projections and transformation
.SelectList(list => list
.SelectGroup(b => b.OrderId)
.Select(b => b.TaskType)
)
.TransformUsing((Transformers.AliasToBean<TaskType>()))
.List<TaskType>()
}
).ToList();
tasksByType.Add(new TaskGroup()
{
Title = TaskTypeTitles[type.ToString()],
Content = tasks,
RemainingCount = tasks.Count(),
OverdueCount =
tasks.Count(
task =>
task.DueDate < DateTime.Today),
});
};
return tasksByType;
The type arguments for method 'NHibernate.Criterion.Lambda.QueryOverSubqueryPropertyBuilderBase,Api.Task,Api.Task>.In(NHibernate.Criterion.QueryOver)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
The subquery syntax will help us in this case. First of all, let's declare the inner select this way:
QueryOver<Task> subQuery = QueryOver.Of<Task>()
.Select(
Projections.GroupProperty(
Projections.Property<Task>(t => t.OrderId)
)
)
.Where(Restrictions.Gt(Projections.Count<Task>(t => t.OrderId), 1))
;
This will produce the:
(SELECT this_0_.OrderId as y0_
FROM [Task] this_0_
GROUP BY this_0_.OrderId
HAVING count(this_0_.OrderId) > 1)
Now we can use it as a subquery in the outer SELECT:
Task task = null;
var PendingTasks =
session.QueryOver<Task>(() => task)
// the WHERE clause: OrderId IN (subquery)
.WithSubquery
.WhereProperty(() => task.OrderId)
.In(subQuery)
// the rest of your SELECT/projections and transformation
.SelectList(list => list
.SelectGroup(b => b.OrderId)
.Select(b => b.TaskType)
)
.TransformUsing((Transformers.AliasToBean<TaskType>()))
.List<TaskType>()
;
And this will create the rest, with included subquery
Related
I'm trying to convert a sql stored proc to linq. I'm having issues with the groupby and inner joins.
Here is what I've tried:
var r = _context.Table1
.GroupBy(x => new { x.OptionId, x.Years, x.Strike })
.Join(_context.Table2,
oc => oc.OptionId, o => o.OptionId, (oc, o) => new
{
OptionsCosts = oc,
Options = o
}).Where(x => x.Options.OptionType == 1
&& x.Options.QualifierId != null
&& x.Options.CreditingMethod != "xxx")
.Select(y => new DataModel.Table1()
{
Years = y.Select(a => a.OptionsCosts.Years).FirstOrDefault(),
Strike = y.Select(a => a.OptionsCosts.Strike).FirstOrDefault(),
Value = y.Select(a => a.OptionsCosts.Value).FirstOrDefault(),
ChangeUser = y.Select(a => a.OptionsCosts.ChangeUser).FirstOrDefault(),
ChangeDate = DateTime.Now,
OptionId = y.Select(a => a.OptionsCosts.OptionId).FirstOrDefault()
});
Here is the SQL that I'm trying to convert:
SELECT o2.OptionId, o2.Years, o2.Strike, SUM(d2.Weights) as 'TotalWeight', COUNT(*) as 'Counts'
FROM Table1 o2
INNER JOIN #Dates d2 --this is a temp table that just holds dates. I was thinking just a where statement could do it???
ON d2.EffectiveDate = o2.EffectiveDate
INNER JOIN Table2 od2
ON od2.OptionId = o2.OptionId
AND od2.OptionType = 1
AND od2.qualifierid is null
AND od2.CreditingMethod <> 'xxx' --28095
GROUP BY o2.OptionId,o2.Years, o2.Strike
My data is way off so I'm sure I'm doing something wrong.
var table1=_context.Table1
.groupBy(o2=> new{
o2.OptionId
, o2.Years
, o2.Strike
})
.select(s=> new{
s.key.OptionId
, s.key.Years
, s.key.Strike
,TotalWeight=s.sum(x=>x.Weights)
,Counts=o2.count(c=>c.OptionId)
}).tolist();
var result=table1
.Join(_context.Table2,oc => oc.OptionId, o => o.OptionId, (oc, o) => new{ OptionsCosts = oc, Options = o })
.Where(x => x.Options.OptionType == 1
&& x.Options.QualifierId != null
&& x.Options.CreditingMethod != "xxx")
.select(x=> new {
x.oc.OptionId, x.oc.Years, x.oc.Strike, x.oc.TotalWeight, x.oc.Counts
}).tolist();
Small advise, when you rewriting SQL queries, use LINQ Query syntax which is close to SQL and more effective to avoid errors.
var dates = new List<DateTime>() { DateTime.Now }; // fill list
var query =
from o2 in _context.Table1
where dates.Contains(o2.EffectiveDate)
from od2 in _context.Table1.Where(od2 => // another way to join
od2.OptionId == o2.OptionId
&& od2.OptionType == 1
&& od2.qualifierid == null
&& od2.CreditingMethod != "xxx")
group o2 by new { o2.OptionId, o2.Years, o2.Strike } into g
select new
{
g.Key.OptionId,
g.Key.Years,
g.Key.Strike,
Counts = g.Count()
// SUM(d2.Weights) as 'TotalWeight', -this one is not available because dates in memory
};
If you are on start and trying to rewrite procedures on LINQ - EF Core is bad idea. Too limited IQueryable support and usually you will fight for each complex LINQ query.
Try linq2db which has temporary tables support and your stored proc can be rewritten into identical LINQ queries. Or you can use linq2db.EntityFrameworkCore to extend EF Core functionality.
Disclaimer. I’m creator of this extension and one from linq2db creators.
I am able to produce a set of results that are desirable, but I have the need to group and sum of these fields and am struggling to understand how to approach this.
In my scenario, what would be the best way to get results that will:
Have a distinct [KeyCode] (right now I get many records, same KeyCode
but different occupation details)
SUM wage and projection fields (in same query)
Here is my LINQ code:
private IQueryable<MyAbstractCustomOccupationInfoClass> GetMyAbstractCustomOccupationInfoClass(string[] regionNumbers)
{
//Get a list of wage data
var wages = _db.ProjectionAndWages
.Join(
_db.HWOLInformation,
wages => wages.KeyCode,
hwol => hwol.KeyCode,
(wages, hwol) => new { wages, hwol }
)
.Where(o => regionNumbers.Contains(o.hwol.LocationID))
.Where(o => o.wages.areaID.Equals("48"))
.Where(o => regionNumbers.Contains(o.wages.RegionNumber.Substring(4))); //regions filter, remove first 4 characters (0000)
//Join OccupationInfo table to wage data, for "full" output results
var occupations = wages.Join(
_db.OccupationInfo,
o => o.wages.KeyCode,
p => p.KeyCode,
(p, o) => new MyAbstractCustomOccupationInfoClass
{
KeyCode = o.KeyCode,
KeyTitle = o.KeyTitle,
CareerField = o.CareerField,
AverageAnnualOpeningsGrowth = p.wages.AverageAnnualOpeningsGrowth,
AverageAnnualOpeningsReplacement = p.wages.AverageAnnualOpeningsReplacement,
AverageAnnualOpeningsTotal = p.wages.AverageAnnualOpeningsTotal,
});
//TO-DO: How to Aggregate and Sum "occupations" list here & make the [KeyCode] Distinct ?
return occupations;
}
I am unsure if I should perform the Grouping mechanism on the 2nd join? Or perform a .GroupJoin()? Or have a third query?
var occupations = _db.OccupationInfo.GroupJoin(
wages,
o => o.KeyCode,
p => p.wages.KeyCode,
(o, pg) => new MyAbstractCustomOccupationInfoClass {
KeyCode = o.KeyCode,
KeyTitle = o.KeyTitle,
CareerField = o.CareerField,
AverageAnnualOpeningsGrowth = pg.Sum(p => p.wages.AverageAnnualOpeningsGrowth),
AverageAnnualOpeningsReplacement = pg.Sum(p => p.wages.AverageAnnualOpeningsReplacement),
AverageAnnualOpeningsTotal = pg.Sum(p => p.wages.AverageAnnualOpeningsTotal),
});
Im using MEF and executing a task which needs to be grouped with aggregate functions only when it returns more than 1 record. I need both the Max of the start hour and the min of the end hour grouped into a single record like my sql would result in on the restuled task
var ohs = await Bl.UoW.Repositories.OperatingHours
.FindInDataSourceAsync(oh => ((oh.ProductId == productTypeId
&& oh.StateId == state)
|| (oh.StateId == complianceHours.State)));
Here is the SQL that gets me basiccally what I need when more than 1 record returned
SELECT
StateId,
MAX(ComplianceHourStart),
MIN(ComplianceHourEnd)
FROM
OperatingHours
GROUP BY
StateId
HAVING
StateId = 'CA'
So when more than 1 I can filter it further but not sure how to achieve max and min?
if (ohs != null && ohs.Count() > 1)
{
//
ohs = ohs.GroupBy(x => x.State).Max(x => x.ComplianceHourStart?...
}
Thanks
From your SQL, this should be close:
var result = context.OperatingHours
.GroupBy(oh => oh.StateId)
.Select(oh => new {StateId = oh.Key,
MaxStart = oh.Max(x => x.ComplianceHourStart),
MinEnd = oh.Min(x => x.ComplianceHourEnd)});
...although I'm not sure why you are grouping when you are restricting the state id column (group key). The following should also suffice:
var result = context.OperatingHours
.Where(oh => oh.StateId == 'CA')
.Select(oh => new {MaxStart = oh.Max(x => x.ComplianceHourStart),
MinEnd = oh.Min(x => x.ComplianceHourEnd)});
Something like this should do it:
ohs = ohs.GroupBy(x => x.State)
.Select(g => new
{
//You need to make a choice on StateId, here... First one?
StateId = g.First().StateId,
MaxComplianceHourStart = g.Max(o => o.ComplianceHourStart),
MinComplianceHourEnd = g.Min(o => o.ComplianceHourEnd)
});
I wrote some entity framework select:
var query = context.MyTable
.Select(a => new
{
count = a.OtherTable.Where(b => b.id == id).Sum(c => c.value),
total = a.OtherTable2.Where(d => d.id == id) * count ...
});
I have always select total:
var query = context.MyTable
.Select(a => new
{
count = a.OtherTable.Where(b => b.id == id).Sum(c => c.value),
total = a.OtherTable2.Where(d => d.id == id) * a.OtherTable.Where(b => b.id == id).Sum(c => c.value)
});
Is it possible to select it like in my first example, because I have already retrieved the value (and how to do that) or should I select it again?
One possible approach is to use two successive selects:
var query = context.MyTable
.Select(a => new
{
count = a.OtherTable.Where(b => b.id == id).Sum(c => c.value),
total = a.OtherTable2.Where(d => d.id == id)
})
.Select(x => new
{
count = x.count,
total = x.total * x.count
};
You would simple do
var listFromDatabase = context.MyTable;
var query1 = listFromDatabase.Select(a => // do something );
var query2 = listFromDatabase.Select(a => // do something );
Although to be fair, Select requires you to return some information, and you aren't, you're somewhere getting count & total and setting their values. If you want to do that, i would advise:
var listFromDatabase = context.MyTable.ToList();
listFromDatabase.ForEach(x =>
{
count = do_some_counting;
total = do_some_totalling;
});
Note, the ToList() function stops it from being IQueryable and transforms it to a solid list, also the List object allows the Linq ForEach.
If you're going to do complex stuff inside the Select I would always do:
context.MyTable.AsEnumerable()
Because that way you're not trying to still Query from the database.
So to recap: for the top part, my point is get all the table contents into variables, use ToList() to get actual results (do a workload). Second if trying to do it from a straight Query use AsEnumerable to allow more complex functions to be used inside the Select
I have 2 tables. 1 has entity's, 1 per row. Another is simply a mapping table of my EntitiesID and EmployeeID. I am trying to write a LINQ method that returns all Entities from the First Table where the EntityID is in the mapping table that is filtered by the EmployeeID.
Simplified Table Structure Example
TaskTable: ID, Description, Status
TaskViewTable: ID, TaskID, EmployeeID
So I want to return all Rows from TaskTable where the ID is in a SubQuery results of TaskViewTable based on EmployeeID.
Any help on doing this in LINQ? I have a 1 to Many set up between the two tables as well. I know there are similar questions am maybe I'm dense but they didn't seem to apply completely to what I was asking.(e.g. Linq Return Filtered Children)
Sorry forgot to show what I have so far:
IQueryable<tblTask> tTask=context.GetTable<tblTask>();
return tTask.Where(t => t.tblTasksViews.Where(v => v.EmployeeID == empID))
It, however, does not like my wherewith an unkown method Where(?)
Something like this should do the trick:
var tasks = tTask.Where(t =>
tTaskView.Where(v => v.ID == empId).Select(v => v.TaskId).Contains(t.ID));
You could break up the above into two sections:
//1.) Get all task views for the employeeID and only select the mapped TaskId
var taskViews = tTaskView.Where(v => v.ID == empId).Select(v => v.TaskId); //taskViews = IEnumerable<int>
//2.) Then get all tasks from the filtered task ids
var tasks = tTask.Where(t => taskViews.Contains(t.ID));
UPDATE
//3.) Project filtered results into IEnumerable<Task>
return tasks.Select(t => new Task()
{
ID = t.ID,
ActionableID = t.ActionableID,
StatusID = t.StatusID,
TypeID = t.TypeID,
Description = t.Description
});
You can, of course, string everything into a nice one-liner:
public List<Task> GetTasks(int empId)
{
return tTask
.Where(t => tTaskView.Where(v => v.ID == empId).Select(v => v.TaskId).Contains(t.ID))
.Select(t => new Task()
{
ID = t.ID,
ActionableID = t.ActionableID,
StatusID = t.StatusID,
TypeID = t.TypeID,
Description = t.Description
}).ToList();
}
Try something like this:
var query =
from tt in TaskTable
join tvt in TaskViewTable on tt.ID equals tvt.TaskID into xs
where xs.Any(z => z.EmployeeID == empID)
select tt;