LINQ Right Outer Join Problem - sql

I am writing a right outer join query in SQL Server 2005 and it's working fine, but I am not able to convert it to LINQ.
Here is my query:
select b.number, COUNT(*) AS [AudioCount] from audios a
right join months b on DATEPART(Month, a.[RecordedDate]) = b.number
group by number
Please help me convert it to LINQ.
Thanks & Regards,
Anil Saklania
EDIT: Corrected query.

Depending on what you are looking for I have inverted it to be a left join but it is a left join from months to audio. This will enable you to return a count of zero when a month has no audio recordings. Used paolo's original testing data to test this out.
var audioMonths = from month in ListOfMonths
join audio in ListOfAudios on
month.number equals audio.RecordedDate.Month into audioLeftJoin
from audio in audioLeftJoin.DefaultIfEmpty()
select new
{
Month = month.number,
AudioId = audio != null ? audio.someProperty : null //Need some property on the audio object to see if it exists
};
var monthAudioCount = from audioMonth in audioMonths
group audioMonth by audioMonth.Month into grouping
select new
{
Month = grouping.Key,
AudioCount = grouping.Count(audioMonth => audioMonth.AudioId != null)
};

First, some notes from book: LINQ Pocket Reference by J. & B. Albahari:
1. Using an extra from translates to a SelectMany.
2. An into clause translates to a GroupJoin when it appears directly after a join clause.
Both of the excellent solutions above, by Mike and by Paolo, utilize a second, extra from clause in the query because that translates to a SelectMany.
With SelectMany, a “sequence of sequences” ( a sequence of audio sequences ) is converted into a single flat collection result set. Then, to count the audios, that single flat output collection is, in a second step, grouped according to month. In both solutions above, that is done, and it works OK, but it also necessitates careful checking for nulls.
EXPLOITING THE NATURAL HIERARCHY.
A cleaner alternative way is the use a GroupJoin instead of SelectMany. GroupJoin yields a hierarchical result set, rather than the flat result set of SelectMany. The hierarchical result set needs no grouping, of course, so we eliminate the second step.
Best of all, by utilizing the hierarchical result set of GroupJoin, we don’t have to check for nulls.
Thus we achieve another clean left outer join by this code, and borrowing Paolo's data:
static void Main(string[] args)
{
var ListOfAudios = new List<Audio>() {
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 01, 01) },
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 01, 02) },
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 02, 01) },
new Audio() { someProperty = "test", RecordedDate = new DateTime(2011, 02, 02) }
};
var ListOfMonths = new List<Month>() {
new Month() {number=1, someMonthProperty="testMonth"},
new Month() {number=2, someMonthProperty="testMonth"},
new Month() {number=3, someMonthProperty="testMonth"}
};
var q = from month in ListOfMonths
join audio in ListOfAudios on month.number equals audio.RecordedDate.Month
into hierarch
select new
{
MonthNum = month.number,
AudioCnt = hierarch.Count()
};
foreach (var m in q)
{
Console.WriteLine("{0} - {1}", m.MonthNum,m.AudioCnt);
}
Console.ReadLine();
}

As per some of the comments to your question there are probably more straightforward ways to do what you want than translating your query to linq. However, just as an exercise, here's a way to write it:
var res = from audio in ListOfAudios
join month in ListOfMonths
on audio.RecordedDate.Month equals month.number into joinAudioMonth
from j in joinAudioMonth.DefaultIfEmpty()
group j by j.number into g
select new
{
number = g.Key,
cnt = g.Count()
};
EDIT:
the code above does not do a RIGHT JOIN as you requested, here's a revised one based on Mike's answer. This one does not rely on a property of the Audio object (that might be null even if the object itself exists). But I'm being nitpicky, Mike's answer is basically the correct one.
var audioMonths =
from month in ListOfMonths
join audio in ListOfAudios on
month.number equals audio.RecordedDate.Month into monthAudioJoin
from joined in monthAudioJoin.DefaultIfEmpty()
select new
{
Month = month.number,
J = joined
};
var res = from audioMonth in audioMonths
group audioMonth by audioMonth.Month into grouping
select new
{
number = grouping.Key,
cnt = grouping.Count(a => a.J != null)
};
and here's how I tested it:
public class Audio
{
public string someProperty {get; set;}
public DateTime RecordedDate {get; set; }
}
public class Month
{
public string someMonthProperty {get; set;}
public int number {get; set; }
}
public static void Main (string[] args)
{
var ListOfAudios = new List<Audio>() {
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,01,01)},
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,01,02)},
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,02,01)},
new Audio(){someProperty="test", RecordedDate=new DateTime(2011,02,02)}
};
var ListOfMonths = new List<Month>() {
new Month() {number=1, someMonthProperty="testMonth"},
new Month() {number=2, someMonthProperty="testMonth"},
new Month() {number=3, someMonthProperty="testMonth"}
// ...
};
var audioMonths =
from month in ListOfMonths
join audio in ListOfAudios on
month.number equals audio.RecordedDate.Month into monthAudioJoin
from joined in monthAudioJoin.DefaultIfEmpty()
select new
{
Month = month.number,
J = joined
};
var res = from audioMonth in audioMonths
group audioMonth by audioMonth.Month into grouping
select new
{
number = grouping.Key,
cnt = grouping.Count(a => a.J != null)
};
foreach(var r in res)
{
Console.WriteLine("{0} - {1}", r.number, r.cnt);
}

Related

Convert SQL Query to query in C#

I've currently got the SQL code below:
WITH region_list
AS (SELECT r.StateProvinceRegion,
r.CafeId,
s.Longitude,
s.Latitude,
ROW_NUMBER() OVER(PARTITION BY r.StateProvinceRegion
ORDER BY s.Longitude DESC) AS row_no
FROM CafeAddress r
inner join Restaurant s on s.CafeId = r.CafeId
)
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS ID,
StateProvinceRegion,
Longitude,
Latitude
FROM region_list
WHERE row_no = 1;
How would I go about adding this query? In my method below I've implemented something similar but I don't understand how to add the WITH clause in.
public VersionResponse GetCafeRegion()
{
var regionList = from cafeAddress in _context.CafeAddress
join cafe in _context.Cafe on cafeAddress.CafeId equals cafe.CafeId
select new { cafeAddress.StateProvinceRegion, cafeAddress.CafeId, cafe.Longitude, cafe.Latitude };
return new VersionResponse()
{
Data = regionList
};
}
Try the below code once.
public VersionResponse GetCafeRegion()
{
var CafeAddress = new List<CafeAddress>();
var Cafe = new List<Cafe>();
var regionList = from cafeAddress in _context.CafeAddress
join cafe in _context.Cafe on cafeAddress.CafeId equals cafe.CafeId
group new { cafeAddress.StateProvinceRegion, cafeAddress.CafeId, cafe.Longitude, cafe.Latitude } by cafeAddress.StateProvinceRegion into g
select g;
List<object> finalResult = new List<object>();
int index = 1;
foreach(var gg in regionList)
{
var groupRecord = gg.OrderByDescending(x => x.Longitude).FirstOrDefault();
finalResult.Add(new
{
ID = index++,
groupRecord.StateProvinceRegion,
groupRecord.CafeId,
groupRecord.Latitude,
groupRecord.Longitude
});
}
return new VersionResponse()
{
Data = finalResult
};
}

Query syntax in entity framework

I'm doing a query (see below), but I do not know how to retrieve all data from a select.
var model = new dbContext();
var query = from mp in model.matiere_premiere join req in (from stk in model.stock_mp
join ms in model.matiere_premiere
on stk.matiere_premiere_code equals
ms.code
where stk.date <= DateTime.Today
orderby stk.date descending
select new new { stk.qte, stk.matiere_premiere_code })
on mp.code equals req.matiere_premiere_code
group mp by new { mp.code } into grp
orderby grp.Key
select new
{
grp.Key,
grp.First().designation,
grp.Last().frns
};
The equivalent sql query is:
SELECT matiere_premiere.code,matiere_premiere.designation,
"matiere_premiere.unite, matiere_premiere.frns ,IF(ISNULL(REQ.qte), '0.00', REQ.qte) AS qte
FROM matiere_premiere LEFT JOIN (SELECT qte,matiere_premiere_code FROM stock_mp
JOIN matiere_premiere ON matiere_premiere.code = matiere_premiere_code
WHERE DATE <= CURRENT_DATE() ORDER BY DATE DESC)
AS REQ ON REQ.matiere_premiere_code = matiere_premiere.code
GROUP BY matiere_premiere.code ORDER BY matiere_premiere.code
it's simple, the group is also an enumerator, so you should return
select grp;
then, for each group, you can do a foreach of the values
foreach(var group in query)
{
Console.WriteLine("Key: " + group.Key);
foreach(var v in group)
{
Console.WriteLine("Value: " + v.Property);
}
}

get list of decision for a specific meetingtitle using linq asp.net

I have a database table. What I want is to get data using group by clause as I have used in below code.
Note that Decision is another table. now I want that all the decisions related to a specific Meeting Title should be shown in list.like
meetingtitle1=decision1,decison2,decison3
meetingtitle2=decision1,decison2
but below code returns only one decisiontitle.
public List<NewMeetings> GetAllMeetings()
{
var xyz = (from m in DB.MeetingAgenda
//join mp in Meeting on m.MeetingId equals mp.MeetingId
//where m.MeetingId == 2
group m by new { m.Meeting.MeetingTitle } into grp
select new NewMeetings
{
// meetingid = grp.Key.MeetingId,
meetingtitle = grp.Key.MeetingTitle,
decision = grp.Select(x => x.Decision.DecisionTitle).FirstOrDefault(),
total = grp.Count()
}).ToList();
List<NewMeetings> list = xyz.ToList();
return list;
}
public class NewMeetings
{
public int meetingid;
public string meetingtitle;
public string decision;
public int total;
}
Can somebody please tell me how to return a list of decisions to a specific Meetingtitle?
You are doing a FirstOrDefault on the list of decisions which obviously means you are only getting a single value. Instead you can join them all together into one longer string (separated by commas as you indicated in the question) by changing this line:
decision = grp.Select(x => x.Decision.DecisionTitle).FirstOrDefault(),
To this:
decision = string.Join(",", grp.Select(x => x.Decision.DecisionTitle)),
However, as the string.Join is not recognised by Linq to Entities, you need to do the string.Join after the data has been retrieved (i.e. after the ToList):
var xyz = (from m in DB.MeetingAgenda
group m by new { m.Meeting.MeetingTitle } into grp
select new
{
meetingtitle = grp.Key.MeetingTitle,
decisions = grp.Select(x => x.Decision.DecisionTitle),
total = grp.Count()
})
.ToList()
.Select(m => new NewMeetings
{
meetingtitle = m.meetingtitle,
decision = string.Join(",", m.decisions),
total = m.total
});

dynamically change LINQ to Entity query

int year = 2009; // get summ of TONS2009 column
var query = from ODInfo in DataContext.CIMS_TRUCKS
where pLocationIDs.Contains(ODInfo.OID)
group ODInfo by ODInfo.OID into g
select new
{
OID = g.Key,
TotalTons = g.Sum( ODInfo => ODInfo.TONS2009)
};
IN the expression 'ODInfo => ODInfo.TONS2009', how do I change TONS2009 to TONS2010 or TONS2011 based on the method parameter 'int year' ?
K06a's answer is close but won't work server-side. Try this:
IEnumerable<OutputType> myQuery(IEnumerable<InputType> data, Expression<Func<InputType,decimal>> expr)
{
return from ODInfo in DataContext.CIMS_TRUCKS
where pLocationIDs.Contains(ODInfo.OID)
group ODInfo by ODInfo.OID into g
select new OutputType
{
OID = g.Key,
TotalTons = g.AsQueryable().Sum(expr)
};
}
var query = myQuery(DataContext.CIMS_TRUCKS, ODInfo => ODInfo.TONS2009);
I haven't tried this, but did something similar here.
UPDATE
If you really need to translate input strings (like "2009") to expressions, it's still possible:
string year = "2009";
Type ODInfoType = typeof(ODINFOTYPE); // substitute with the type of ODInfo
ParameterExpression pe = ParameterExpression.Parameter(ODInfoType, "ODInfo");
MemberInfo mi = ODInfoType.GetProperty("TONS" + year);
MemberExpression me = Expression.MakeMemberAccess(pe, mi);
var expr = Expression.Lambda<Func<ODINFOTYPE, decimal>>(me, pe);
Be aware that this is a patch to the extremly evil structure of your database.
You can try something like that:
TotalTons = g.Sum( ODInfo => (year == 2009) ? ODInfo.TONS2009 : ((year == 2010)
? ODInfo.TONS2010 : ODInfo.TONS2011))
Or make it more readable and use { } to split that lambda expression into more then one line and use eg. switch statement.
The best solution is to break this up into multiple querys that you can compose to a final query:
int year = 2009; // get summ of TONS2009 column
var odInfos =
year == 2009 ? DataContext.CIMS_TRUCKS.Select(x => new { x.OID, TONS = x.TONS2009 })
year == 2010 ? DataContext.CIMS_TRUCKS.Select(x => new { x.OID, TONS = x.TONS2010 })
year == 2011 ? DataContext.CIMS_TRUCKS.Select(x => new { x.OID, TONS = x.TONS2011 })
: null;
var query = from ODInfo in odInfos
where pLocationIDs.Contains(ODInfo.OID)
group ODInfo by ODInfo.OID into g
select new
{
OID = g.Key,
TotalTons = g.Sum(ODInfo => ODInfo.TONS)
};
This will specialize to three possible queries at runtime, thereby giving the best possible performance. It is better than a case-switch.
Try this way:
IEnumerable<OutputType> myQuery(IEnumerable<InputType> data, Func<InputType,decimal> func)
{
return from ODInfo in data
where pLocationIDs.Contains(ODInfo.OID)
group ODInfo by ODInfo.OID into g
select new OutputType
{
OID = g.Key,
TotalTons = g.Sum(func)
};
}
var query = myQuery(DataContext.CIMS_TRUCKS, ODInfo => ODInfo.TONS2009);
Using DynamicLinq which works with EF also:
int year = 2009; // get summ of TONS2009 column
var query = from ODInfo in DataContext.CIMS_TRUCKS
where pLocationIDs.Contains(ODInfo.OID)
group ODInfo by ODInfo.OID into g
select g;
var projectedGroups = query.Select("new (Key as OID, Sum(TONS" + year + ") as TotalTons)");

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.