Understanding MVC SQL query. From, where, then select - sql

I inherited the code below and was hoping someone could explain it to me. The model definition is fine, but I am not understanding the string Minor1 setup. The SQL query looks backwards. Is this a normal practice? I tried putting the query in the normal order (select, from, where) and it did not work.
Example for model:
[Table("MINORS")]
public class VerifyDataModel_MINORS
{
[Key]
public string MINORS_ID { get; set; }
public string MINORS_DESC { get; set; }
}
string Minor1 = (from A in db.VerifyDataModel_STUDENT_PROGRAM
join B in db.VerifyDataModel_STPR_STATUS on A.STUDENT_PROGRAMS_ID equals B.STUDENT_PROGRAMS_ID into AB
from AB1 in AB.Where(B => B.POS == 1).DefaultIfEmpty()
join C in db.VerifyDataModel_STPR_MINOR_LISTS on A.STUDENT_PROGRAMS_ID equals C.STUDENT_PROGRAMS_ID into ABC
from ABC1 in ABC.Where(C => C.STPR_MINORS.Substring(0, 2) != "XX").DefaultIfEmpty()
join D in db.VerifyDataModel_MINOR on ABC1.STPR_MINORS equals D.MINORS_ID into ABCD
from ABCD1 in ABCD.DefaultIfEmpty()
where AB1.STPR_STATUS == "A"
where ABC1.STUDENT_PROGRAMS_ID.Substring(8, 5) != "GENED"
where A.STUDENT_PROGRAMS_ID.Substring(0, 7) == personId
where ABC1.STPR_MINOR_END_DATE == null
select ABCD1.MINORS_DESC).Min();

Related

Find missing element SQL

I have a couple of tables of a database where one defines a set of matrices and the other the data in the matrices
Matrices
Id Name
1 M1
2 M2
3 M3
4 M4
MatrixElements
Matrix_Id ElementKey Value
1 1 234
1 2 234
1 3 4432
2 1 234
2 2 13
2 3 123
3 1 34
3 3 345
4 1 234
4 2 11
4 3 344
So the Matrix_Id column is a foreign key back to the Id of the Matrices table. The ElementKey reprents an ij pair. The matrices are sparse, so there may or may not be an element with a specific key. However, if one matrix has a particular ElementKey, then the ElementKey with that ID must be defined in ALL matrices.
Is there some SQL that I can run that will find Matrix_Id and ElementKey combinations for any offending entries, i.e. one that is not defined for all matrices? So for the example above, ElementKey = 2 is defined for Matrix 1, 2 and 4 but not 3, so I would expect [Matrix = 3, ElementKey = 2] back.
This will get the missing elements and the matrices they are in:
select m.id, me.element_key
from matrices m cross join
(select distinct element_key from matrix_elements me) e left join
matrix_elements me
on me.matrix_id = m.id and me.element_key = e.element_key
where me.matrix_id is null;
The cross join generates all combinations of matrices with known element keys. The left join and where then find the ones that are missing.
First we make a list of all live elements, then cross join to all active matrices. With this list, we left join the active elements and use a case to determine if they exist.
I've used ANSI, but a CTE would be better if SQL server or Oracle.
select c.id, c.ElementKey, case when b.MatrixID is null then 0 else 1 end as InPlace
from
(
select id, a.ElementKey
from Matrices
cross join
(
select distinct ElementKey
from MatrixElements
) a
) c
left join Matrices b
on b.Matrix_id = c.id
and b.ElementKey = c.ElementKey
Thanks to the responses I have had. I haven't been able to implement it effectively because I am using Entity Framework and it is hard to translate the code given to a query statement that will give me back the results. I did take inspiration from the samples given and this is what I came up with.
public class Matrix
{
[Key]
public int Id { get; set; }
public virtual List<MatrixElement> Data { get; set; }
}
public class MatrixElement
{
[Key]
public int Id { get; set; }
public int OdPairKey { get; set; }
public double Value { get; set; }
}
public class EngineDataContext : DbContext
{
public virtual DbSet<Matrix> MatrixFiles { get; set; }
public virtual DbSet<MatrixElement> MatrixData { get; set; }
}
public class SqliteRepository
{
private readonly EngineDataContext _dataContext;
public SqliteRepository(EngineDataContext dataContext)
{
_dataContext = dataContext;
}
public IEnumerable<Tuple<Matrix, int>> FindMissingODPairs(IEnumerable<Matrix> matrices)
{
IEnumerable<Matrix> matricesWithData =
matrices.Select(m => _dataContext.MatrixFiles
.Include("Data").First(mf => mf.Id == m.Id));
// Do the cross join on matrices and OD pairs
IEnumerable<dynamic> combinations =
from m in matrices
from od in matricesWithData.SelectMany(mat => mat.Data.Select(md => md.OdPairKey)).Distinct()
select new { M = m.Id, O = od };
// Find all the used matrix / OD pair combinations
IEnumerable<dynamic> used =
from m in matricesWithData
from od in m.Data
select new { M = m.Id, O = od.OdPairKey };
// Find the missing combinations with a simple "Except" query
return combinations
.Except(used)
.Select(c => new Tuple<Matrix, int>(matrices.First(m => m.Id == c.M), c.O));
}
}

Convert SQL sentence to LINQ

I need to create a LINQ sentence that does the same as this SQL sentence but I can't solve it...
SELECT l.Id, l.Url, t.Name, t.LanguageCode
FROM link l
LEFT OUTER JOIN linktr t ON t.Id = (SELECT tt.Id FROM linktr tt WHERE tt.LinkId = l.Id AND (tt.LanguageCode = 'en-EN' OR tt.LanguageCode = 'es-ES') ORDER BY (tt.LanguageCode = 'en-EN') DESC, tt.LanguageCode LIMIT 1)
ORDER BY l.OrderPos
Table Link
Id, Url
Table LinkTr
Id, LinkId, Name, LanguageCode
The table link contains the Url of the link while the LinkTr contains the name and it can cointain multiple languages
I want to select all the links we have in the links database and for each link I want to show JUST ONE language, the one that corresponds to the current language if exists, but if it doesn't exist I have to show the one that corresponds to the default language. If no one of them exist it wouldn't show that record.
For this example the default language is es-ES and the current one is en-EN.
How can I do this using LINQ?
Thanks in advance
EDIT
If I use this LINQ expression it works only if I comment the ORDER line, but without the ORDER line (.OrderBy(p3 => p3.LanguageCode == currentLanguage)) it takes the first record in the db by Id, but not the current language first.
var queryString = from i in _dbContext.Links
let p = _dbContext.LinksTrs.Where(p2 => i.Id == p2.LinkId && (p2.LanguageCode == currentLanguage || p2.LanguageCode == defaultLanguage))
.OrderBy(p3 => p3.LanguageCode == currentLanguage)
.FirstOrDefault()
orderby i.OrderPos
select new LinksWebListModel
{
Id = i.Id,
Name = (p == null) ? "" : p.Name,
Url = i.Url,
Summary = (p == null) ? "" : p.Summary,
};
EDIT 2
I'm suing EF6 and my entities, which I can't change, are these:
public class Link : EntityBase
{
public int Id { get; set; }
public int OrderPos { get; set; }
public string Url { get; set; }
public virtual IEnumerable<LinkTr> Translations { get; set; }
}
public class LinkTr
{
public int Id { get; set; }
public string Name { get; set; }
public string LanguageCode { get; set; }
public int LinkId { get; set; }
public virtual Link Link { get; set; }
}
EDIT 3
it would be something like this...
var queryString = (from entity in _dbContext.Links
let translation = _dbContext.LinksTrs
.Where(p2 => p2.LinkId == entity.Id)
.Where(p3 => p3.LanguageCode == currentLanguage || p3.LanguageCode == defaultLanguage)
.OrderBy(p4 => p4.LanguageCode)
.FirstOrDefault()
orderby entity.OrderPos descending
select new LinksWebListModel()
{
Id = entity.Id,
Name = translation.Name,
Url = entity.Url,
Summary = translation.Summary,
});
buit the problems here are 2:
- I need to return the record (Links) only if there is a row in the sub query (LinksTrs)
- Also I'd need to order the subquery by "currentLanguage" first and then "defaultLanguage" and then the rest. If I add this to the sub query I get an error:
.OrderBy(p4 => p4.LanguageCode == currentLanguage)
.ThenBy(p5 => p5.LanguageCode == defaultLanguage)
.ThenBy(p6 => p6.LanguageCode)
Do you have an idea on how to order by certain values, for example if I have these languages "es-ES", "en-EN", "it-IT", "fr-FR" for example to get first "it-IT", then "es-ES" and then order by name...

Nhibernate Join 2 QueryOver

I can't find how to join two different QueryOver, group by and perform a substraction in the select.
Say you have :
public class EntityA
{
public virtual int Id;
public virtual string Reference;
public virtual int Quantity;
[Some properties]
}
public class EntityB
{
public virtual int Id;
public virtual int EntityAId;
[Some properties]
}
If i translate my query in pseudo-SQL, i would like to have :
SELECT A.Id, A.Reference, A.Quantity - COALESCE(DERIV_B.TOTAL, 0)
FROM EntityA A
LEFT JOIN (
SELECT B.EntityAId, COUNT(B.Id) AS TOTAL
FROM EntityB B
GROUP BY B.EntityAId) DERIV_B
ON A.Id = DERIV_B.EntityAId
WHERE (A.Quantity - COALESCE(DERIV_B.TOTAL, 0)) >= 0
I can have the subquery on EntityB via QueryOver, but i can't join on EntityA :
var entitiesB = GetCurrentSession().QueryOver<EntityB>().SelectList(select => select.SelectGroup(x => x.EntityAId).SelectCount(x => x.Id));
var entitiesA = GetCurrentSession().QueryOver<EntityA>(). ???
I tried to store the entitiesB in an alias and the perform a JoinAlias on it but i have an exception because it can't retrieve my alias.
Do you have any solution ?
I don't want to create a reference between these two entites.
Short answer is not , you can't do QueryOver if your entities are not connected through model.
One solution would be to use NHibernate.Linq and subqueries
var session = GetCurrentSession();
var entityBQuery = session.Query<EntityB>();
var entityAQuery = session.Query<EntityA>()
.Select(eA=>new { Id = eA.Id,
Description = eA.Description,
Quantity = eA.Quantity - entityBQuery.Where(eb=>eb.EntityAId = eA.Id).Count()
});

how to compare string linq with sub query with another linq with sub query

I Have 4 tables:
Position Table:
| Position | PositionId |
| driver | 1 |
| clerk | 2 |
position Skill table:
| SkillId | skill | PositionId |
| 1 | driving | 1 |
| 2 | drifting | 1 |
Worker table:
| Name | WorkerId |
| John | 1 |
| alex | 2 |
Worker skill table:
| skillId | skill | WorkerId |
| 1 | driving | 1 |
| 2 | drifting | 1 |
I join the position table with position Skill table
and worker table with worker skill
What I'm having trouble with is how can I compare the two joined tables to have a result of
for example:
I need to know who's worker have all the specific skills that the position have
Like:
I Select position with positionId of 1 and have the skillname of driving and drifting
I need to get the Worker with the same skills with driving and drifting also
so far i got this:
var PositionsWithSkills = (from a in db.Client_Customer_Position
where a.ID == position
select new
{
PositionID = a.ID,
RequiredSkills = (from b in db.Client_Customer_Position_Skills
where b.ClientCusPosId == a.ID
select b.SkillName)
}).ToList();
var WorkersWithSkills = (from x in db.Workers
select new
{
workerId = x.ID,
Skills = (from y in db.Worker_Skills
where y.Worker_ID == x.ID
select y.SkillName)
}).ToList();
var PositionWithSkilledWorkers = (from pos in PositionsWithSkills
select new
{
PositionId = pos.PositionID,
Workers = (from worker in WorkersWithSkills
where pos.RequiredSkills.All(skill => worker.Skills.Any(workerSkill => workerSkill == skill))
select worker.workerId)
}).ToList();
the two query works well.. but the last query where i must compare the two query =.. i cant get the worker id
and can i turn this to a stored proc?
Sorry if am wrong. What I got to know from your question is you want the workers list satisfying all the skills of the position you pass. If this is what you want you may try this:
var workerWithallSkill = (from u in db.workerList join x in db.workerSkillList on u.WorkerId equals x.WorkerId
where ((from y in db.workerSkillList where y.WorkerId == u.WorkerId select y).Count() == (from p in db.positionSkillList where p.PositionId == 1("pass your positionId here") select p).Count())
select u).ToList().Distinct();
or if you want to use lambda expression you can use this
var workerWithallSkill = (from u in workerList join x in workerSkillList on u.WorkerId equals x.WorkerId where (workerSkillList.Where(y=> y.WorkerId == u.WorkerId).Count() == positionSkillList.Where(p=>p.PositionId == 1).Count()) select u).ToList().Distinct();
For more understanding you can try the below code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication8
{
class Program
{
static void Main(string[] args)
{
IList<Position> positionList = new List<Position>() {
new Position(){ position="Driver", PositionId=1}
,new Position(){ position="clerk", PositionId=2}
};
IList<PositionSkill> positionSkillList = new List<PositionSkill>() {
new PositionSkill(){ Skill = "driving",skillid = 1,PositionId = 1}
,new PositionSkill(){ Skill = "drifting",skillid = 2,PositionId = 1}
};
IList<Worker> workerList = new List<Worker>() {
new Worker(){ name = "John",WorkerId = 1}
,new Worker(){ name = "alex",WorkerId = 2}
};
IList<WorkerSkill> workerSkillList = new List<WorkerSkill>(){
new WorkerSkill(){Skill = "driving",skillid = 1,WorkerId = 2}
, new WorkerSkill(){Skill = "drifting",skillid = 2,WorkerId = 2}
};
var workerWithallSkill = (from u in workerList join x in workerSkillList on u.WorkerId equals x.WorkerId where (workerSkillList.Where(y => y.WorkerId == u.WorkerId).Count() == positionSkillList.Where(p => p.PositionId == 1).Count()) select u).ToList().Distinct();
foreach (var worker in workerWithallSkill)
{
Console.WriteLine(worker.name);
}
Console.ReadLine();
}
}
public class Position
{
public string position { get; set; }
public int PositionId { get; set; }
}
public class PositionSkill
{
public int skillid { get; set; }
public string Skill { get; set; }
public int PositionId { get; set; }
}
public class Worker
{
public string name { get; set; }
public int WorkerId { get; set; }
}
public class WorkerSkill
{
public int skillid { get; set; }
public string Skill { get; set; }
public int WorkerId { get; set; }
}
}
if a worker has skills from different positions the above code will not work, if this is the scenario try the below code:
var WorkerPositionSkill = from p in db.positionSkillList join q in db.workerSkillList on p.skillid equals q.skillid select new { posSkill = p, workerSkill = q };
var workerWithallSkill = (from u in db.workerList join x in db.workerSkillList on u.WorkerId equals x.WorkerId where (WorkerPositionSkill.Where(y => y.workerSkill.WorkerId == u.WorkerId && y.posSkill.PositionId == 1).Count() == db.positionSkillList.Where(p => p.PositionId == 1).Count()) select u).ToList().Distinct();
This is highly unlikely to work with Linq To SQL because...its a huge steaming pile of #$%&. But this Linq query should given a sufficiently magical IQueryProvider give the right SQL. I've seen some very magical things come from Entity Framework.
var PositionsWithSkills = from a in db.Client_Customer_Position
where a.ID == position
select new
{
PositionID = a.ID,
RequiredSkills = (from b in db.Client_Customer_Position_Skills
where b.ClientCusPosId == a.ID
select b.SkillName)
};
var WorkersWithSkills = from x in db.Workers
select new
{
workerId = x.ID,
Skills = (from y in db.Worker_Skills
where y.Worker_ID == x.ID
select y.SkillName)
};
var PositionWithSkilledWorkers = from pos in PositionsWithSkills
from worker in WorkersWithSkills
where pos.RequiredSkill.All(worker.Skills.Contains)
group worker.Name by pos.PositionID;
PS please learn to use associations as opposed to join/where. If you are going to use join/where, you might as well just use SQL.
var PositionsWithSkills = (from a in Positions select new {
PositionID = a.PositionId,
RequiredSkills = (from b in PositionSkills where b.PositionId == a.PositionId select b.skillId).ToList()
}).ToList();
var WorkersWithSkills = (from x in Workers select new {
Name = x.Name,
Skills = (from y in WorkerSkills where y.WorkerId == x.WorkerID select y.skillId).ToList()
}).ToList();
var PositionWithSkilledWorkers = (from pos in PositionsWithSkills select new {
PositionId = pos.PositionID,
Workers = (from worker in WorkersWithSkills where pos.RequiredSkills.All(skill => worker.Skills.Any(workerSkill => workerSkill == skill)) select worker.Name).ToList()
}).ToList();
i think, your database's tables have not been designed correctly...
you need a relation between worker skill and position skill, i think your tables must be desinged like this:
Skill table: SkillID, Skill
Position table: PositionID, Position
PositionSkill table: ID, SkillID, PositionID
Worker table: WorkerID, Name
WorkerSkill table: ID, SkillID, WorkerID
but by this way you designed your tables, if assume skill field (description of skills) are the same in worker skill and position skill, we can use this as a relation, and your query can be like this:
// skills of specific position
var PositionSkills = Context.PositionSkill.Where(u => u.PositionId == 1);
var WorkersWithSkills = Context.Worker
.Join(Context.WorkerSkill,
worker => worker.WorkerId,
workerSkill => workerSkill.WorkerId,
(worker, workerSkill) => new { worker, workerSkill })
.GroupBy(u => u.worker)
.Select(u => new
{
u.Key.WorkerId,
u.Key.Name,
Skills = u.Select(t => t.workerSkill.skill)
});
var SkilledWorkers = WorkersWithSkills
.Where(u => PositionSkills.All(t => u.Skills.Contains(t.skill)))
.ToList();
if you wont change your database's tables, you can add a join table between position skill and worker skill like:
WorkerPositionSkill: PositionSkillID, WorkerSkillID
Here is a LinqPad program that returns the result, { Worker = John, Position = Driver}. If I understand your requirements you want to find a worker who satisfies the conditions where the worker has all the skills required for Position = 1, which is driving and drifting skills. The query returns two rows the following [{worker = John, Position = Driver}, {worker = John, Position = Driver}]. I had to use distinct to display it once. The reason for two rows is he satisfies both driving and drifting job skills. If the position required 4 skills in which the worker met, there would be 4 duplicate rows. The unique fixes that problem. Hope this helps you along.
I created this solution in LinqPad, which is great tool with hundred's of very well documented linq query examples.
void Main()
{
// Table Setup
// ************
var position = new List<Position>();
position.Add(new Position { Id = 1, Name = "driver" });
position.Add(new Position { Id = 2, Name = "clerk" });
var positionSkill = new List<PositionSkill>();
positionSkill.Add(new PositionSkill { Id = 1, Skill = "driving", PositionId = 1 });
positionSkill.Add(new PositionSkill { Id = 2, Skill = "drifting", PositionId = 1 });
var worker = new List<Worker>();
worker.Add(new Worker { Id = 1, Name = "John" });
worker.Add(new Worker { Id = 2, Name = "alex" });
var workerSkill = new List<WorkerSkill>();
workerSkill.Add(new WorkerSkill { Id = 1, Skill = "driving", WorkerId = 1 });
workerSkill.Add(new WorkerSkill { Id = 2, Skill = "drifting", WorkerId = 1 });
// The Query
// *********
var positionValue = 1;
var r = from p in position
join ps in positionSkill on p.Id equals ps.PositionId
join ws in workerSkill on ps.Skill equals ws.Skill
join w in worker on ws.WorkerId equals w.Id
where p.Id == positionValue
select new {
PositionName = p.Name,
WorkerName = w.Name
};
// Get Distinct Names
r.Distinct().Dump();
}
// Define other methods and classes here
public class Position
{
public int Id { get; set; }
public string Name { get; set; }
}
public class PositionSkill
{
public int Id { get; set; }
public string Skill { get; set; }
public int PositionId { get; set; }
}
public class Worker
{
public int Id { get; set; }
public string Name { get; set; }
}
public class WorkerSkill
{
public int Id { get; set; }
public string Skill { get; set; }
public int WorkerId { get; set; }
}

How can I make a nested projection in a Linq query when using the group by clause?

I'm trying to work with grouped data coming back from SQL.
The method I'm writing is to provide the data for a "Case Status Overview" screen.
It must produce a nested XML document.
Now, I could do it the easy way, but I'm trying to learn whether it's possible to use the linq "group by" statement and then to project the data already nested. (the easy way would be just to pull back the data in a tabular fashion from the database and then for-loop through it forming the Xml document for output)
Here is the data hierarchy:
Every Case has a DebtType and every DebtType has a Client.
Here is the SQL that retrieves the data:
SELECT ClientNames.ClientID ,
ClientNames.ClientCode ,
ClientNames.ClientName ,
DebtTypes.DebtTypeID ,
DebtTypes.DebtTypeShortDesc ,
DebtTypes.DebtTypeLongDesc ,
Cases.CurrentStateCode ,
SUM(1 - CAST(Cases.CaseClosed AS INT)) AS OpenCaseCount ,
SUM(CAST(Cases.CaseClosed AS INT)) AS ClosedCaseCount ,
SUM(CAST(Cases.CaseOnHold AS INT)) AS OnHoldCaseCount ,
SUM(CAST(Cases.CaseReferred AS INT)) AS ReferredCaseCount ,
COUNT(Cases.CaseID) AS TotalCaseCount ,
SUM(Cases.CaseTotalPaid) AS TotalAmountPaid ,
SUM(Cases.CaseCurrentOutstandingAmount) AS TotalAmountOutstanding,
SUM(Cases.CaseTotalDebtWrittenOff) AS TotalAmountWrittenOff ,
SUM(Cases.CaseTotalDebtCancelled) AS TotalAmountCancelled
FROM ClientNames
INNER JOIN ClientDebtTypes
ON ClientNames.ClientID = ClientDebtTypes.ClientID
INNER JOIN DebtTypes
ON ClientDebtTypes.DebtTypeID = DebtTypes.DebtTypeID
INNER JOIN Cases
ON ClientDebtTypes.ClientDebtTypeID = Cases.CaseClientDebtTypeID
GROUP BY ClientNames.ClientID ,
ClientNames.ClientCode ,
ClientNames.ClientName ,
DebtTypes.DebtTypeID ,
DebtTypes.DebtTypeShortDesc,
DebtTypes.DebtTypeLongDesc ,
Cases.CurrentStateCode
ORDER BY ClientNames.ClientID,
DebtTypes.DebtTypeID,
CurrentStateCode
Using Linqer it converts it to:
from clientnames in db.ClientNames
join clientdebttypes in db.ClientDebtTypes on clientnames.ClientID equals clientdebttypes.ClientID
join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID
join cases in db.Cases on new { ClientDebtTypeID = clientdebttypes.ClientDebtTypeID } equals new { ClientDebtTypeID = cases.CaseClientDebtTypeID }
group new {clientnames, debttypes, cases} by new {
clientnames.ClientID,
clientnames.ClientCode,
clientnames.ClientName1,
debttypes.DebtTypeID,
debttypes.DebtTypeShortDesc,
debttypes.DebtTypeLongDesc,
cases.CurrentStateCode
} into g
orderby
g.Key.ClientID,
g.Key.DebtTypeID,
g.Key.CurrentStateCode
select new {
ClientID = (System.Int32?)g.Key.ClientID,
g.Key.ClientCode,
g.Key.ClientName1,
DebtTypeID = (System.Int32?)g.Key.DebtTypeID,
g.Key.DebtTypeShortDesc,
g.Key.DebtTypeLongDesc,
g.Key.CurrentStateCode,
OpenCaseCount = (System.Int64?)g.Sum(p => 1 - Convert.ToInt32(p.cases.CaseClosed)),
ClosedCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseClosed)),
OnHoldCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseOnHold)),
ReferredCaseCount = (Int32?)g.Sum(p => Convert.ToInt32(p.cases.CaseReferred)),
TotalCaseCount = (Int64?)g.Count(p => p.cases.CaseID != null),
TotalAmountPaid = (System.Decimal?)g.Sum(p => p.cases.CaseTotalPaid),
TotalAmountOutstanding = (System.Decimal?)g.Sum(p => p.cases.CaseCurrentOutstandingAmount),
TotalAmountWrittenOff = (System.Decimal?)g.Sum(p => p.cases.CaseTotalDebtWrittenOff),
TotalAmountCancelled = (System.Decimal?)g.Sum(p => p.cases.CaseTotalDebtCancelled)
}
Now as I mentioned, I could stop there and right a for loop to create the Xml data.
But I'm trying to create a nested group (IGrouping<ClientName,IGrouping<DebtType,SummaryClass>>)
and then project the data in a nested format.
Now we're using LinqToXsd to create strong type wrappers for out Xml documents, but essentially all this means is that out output type is:
private class ClientSummary
{
public string ClientName { get; set; }
public IList<DebtTypeSummary> DebtTypes { get; set; }
}
private class DebtTypeSummary
{
public string DebtType { get; set; }
public IList<StateCodeSummary> StateCodes { get; set; }
}
private class StateCodeSummary
{
public string StateCode { get; set; }
public int TotalCount { get; set; }
public decimal TotalAmountPaid { get; set; }
//etc
//etc
//etc
}
Now I got as far as writing the following Linq:
var grouping = from cases in db.Cases
join clientdebttypes in db.ClientDebtTypes on cases.CaseClientDebtTypeID equals clientdebttypes.ClientID
join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID
group cases by new ClientDebtTypePair() { ClientDebtType = clientdebttypes, DebtType = debttypes } into casesByClientDebtTypes
join clientnames in db.ClientNames on casesByClientDebtTypes.Key.ClientDebtType.ClientName equals clientnames
group casesByClientDebtTypes by clientnames;
var projected = from casesByClientDebtTypes in grouping
let client = casesByClientDebtTypes.Key
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = new Client()
{
ClientID = client.ClientID,
DisplayName = client.ClientName1,
},
DebtTypes = from cases in casesByClientDebtTypes
let debttype = cases.Key.DebtType
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType()
{
DebtType = new DebtType()
{
DebtTypeID = debttype.DebtTypeID,
Description = debttype.DebtTypeLongDesc,
DisplayName = debttype.DebtTypeShortDesc,
},
StatesCodes = from cases2 in cases
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType.StatesCodesLocalType()
{
ClosedCasesCount = cases2.Sum(p => Convert.ToInt32(p.cases.CaseClosed))
which joins and groups the database tables and then tries to project the result a ClientSummary (the class names are different but that's because the above is a simplified view of the output classes). I fail completely when I've drilled all the way down to the Cases table and I find that I don't really understand how to do agregate functions. They appear to only be available on IGrouping<K, T>s and it seems I've just got confused.
I need to also ensure that the summaries are calculated server side, pulling back millions of cases would be bad.
Can anybody help me with this one? Is this even possible?
Regards,
James.
-------### UPDATE 1 ###-------
OK, been working on this again today.
I decided to use Linq2SQL to pull pack 2D data and then reformat it using Linq2Objects.
Here is what I started with:
var sql = from clientnames in db.ClientNames
join clientdebttypes in db.ClientDebtTypes on clientnames.ClientID equals clientdebttypes.ClientID
join debttypes in db.DebtTypes on clientdebttypes.DebtTypeID equals debttypes.DebtTypeID
join cases in db.Cases on new { ClientDebtTypeID = clientdebttypes.ClientDebtTypeID } equals new { ClientDebtTypeID = cases.CaseClientDebtTypeID }
group new { clientnames, debttypes, cases } by new
{
clientnames.ClientID,
clientnames.ClientCode,
clientnames.ClientName1,
debttypes.DebtTypeID,
debttypes.DebtTypeShortDesc,
debttypes.DebtTypeLongDesc,
cases.CurrentStateCode
} into g
orderby
g.Key.ClientID,
g.Key.DebtTypeID,
g.Key.CurrentStateCode
select new
{
Client = new Client{ ClientID = g.Key.ClientID, DisplayName = g.Key.ClientName1 },
DebtType = new DebtType{ DebtTypeID = g.Key.DebtTypeID, DisplayName = g.Key.DebtTypeShortDesc, Description = g.Key.DebtTypeLongDesc },
StateSummary = new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType.StatesCodesLocalType()
{
StateCode = g.Key.CurrentStateCode,
OpenCasesCount = g.Sum(p => 1 - Convert.ToInt32(p.cases.CaseClosed)),
ClosedCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseClosed)),
OnHoldCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseOnHold)),
ReferredCasesCount = g.Sum(p => Convert.ToInt32(p.cases.CaseReferred)),
TotalCasesCount = g.Count(p => p.cases.CaseID != null),
TotalAmountPaid = g.Sum(p => p.cases.CaseTotalPaid),
TotalAmountOutstanding = g.Sum(p => p.cases.CaseCurrentOutstandingAmount),
TotalAmountWrittenOff = g.Sum(p => p.cases.CaseTotalDebtWrittenOff),
TotalAmountCancelled = g.Sum(p => p.cases.CaseTotalDebtCancelled),
}
};
var res = sql.ToList();
output.Clients = (from results in res
group results by results.Client into resultsByClient
from resultsByDebtType in
(from results in resultsByClient
group results by results.DebtType)
group resultsByDebtType by resultsByClient.Key into resultsByDebtTypeByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = resultsByDebtTypeByClient.Key,
DebtTypes = (from resultsByDebtType in resultsByDebtTypeByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType.DebtTypesLocalType()
{
DebtType = resultsByDebtType.Key,
StatesCodes = (from results in resultsByDebtType
let summary = results.StateSummary
select results.StateSummary).ToList()
}).ToList()
}).ToList();
That runs, but produces one Client/DebtType/Summary set for every result. So even though there is only one client in this case, I end up with 1300 clients, all identical.
I simplified it to the following:
output.Clients = (from results in res
group results by results.Client into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = resultsByClient.Key,
DebtTypes = null,
}).ToList();
That produces 1300 clients. Next I tried this:
output.Clients = (from results in res
group results by results.Client.ClientID into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = new Client { ClientID = resultsByClient.Key },
DebtTypes = null,
}).ToList();
And THAT produces ONE client (hurray!). Except I loose all the client information (boo!)
Guessing that as it's comparing client by refernce instead of by content I wrote the following:
public partial class Client
{
public static bool operator ==(Client left, Client right)
{
return left.ClientID == right.ClientID;
}
public static bool operator !=(Client left, Client right)
{
return left.ClientID != right.ClientID;
}
public override int GetHashCode()
{
return ClientID;
}
}
That did nothing. It repeatedly calls GetHashCode(), which I fudged to force it to return the same hash code for any matching ClientID, but it still created 1300 Client groups.
Regards,
James.
-------### UPDATE 2 ###-------
OK, I thought I would have a go at making the Linq2Sql output only simple values for grouping by:
g.Key.ClientID,
g.Key.ClientName1,
g.Key.DebtTypeID,
g.Key.DebtTypeShortDesc,
g.Key.DebtTypeLongDesc,
And then changed the test Linq2Objects to:
output.Clients = (from results in res
group results by new { ClientID = results.ClientID, DisplayName = results.ClientName1 } into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = new Client { ClientID = resultsByClient.Key.ClientID, DisplayName = resultsByClient.Key.DisplayName },
DebtTypes = null,
}).ToList();
That works. So anonymous types compare in the way I want them to, by content not reference (apparently)
This does not:
output.Clients = (from results in res
group results by new SiDemClient { ClientID = results.ClientID, DisplayName = results.ClientName1 } into resultsByClient
select new LoadCaseStatusOverviewScreenOutput.ClientsLocalType()
{
Client = resultsByClient.Key,//new Client { ClientID = resultsByClient.Key.ClientID, DisplayName = resultsByClient.Key.DisplayName },
DebtTypes = null,
}).ToList();
That still creates 1300 groups.
So, anonymous types compare in a magical way that I don't understand. How can I make my Client class compare like an anonymous type?
Regards,
James.
-------### SOLUTION FOUND ###-------
-------### MANY THANKS TO Enigmativity ###-------
I needed to override the Equals() method instead of implementing the == operator.
Now the grouping works and I have a wonderful Xml document to reutrn!
public partial class SiDemClient
{
public override bool Equals(object obj)
{
if (obj is SiDemClient)
{
return this.ClientID.Equals(((SiDemClient)obj).ClientID);
}
return false;
}
public override int GetHashCode()
{
return ClientID;
}
}
Many Thanks,
James.
When you override GetHashCode you must also override Equals. The == & != operators are irrelevant.
Try with this:
public partial class Client
{
public override bool Equals(object obj)
{
if (obj is Client)
{
return this.ClientID.Equals(((Client)obj).ClientID);
}
return false;
}
public override int GetHashCode()
{
return this.ClientID.GetHashCode();
}
}
See if that helps.