How can I write the following SQL in LINQ to Entities?
SELECT r.rolename,
( CASE
WHEN ur.username IS NULL THEN 0
ELSE 1
END ) AS isinrole
FROM bgt.roles r
LEFT OUTER JOIN bgt.usersinroles ur
ON ur.rolename = r.rolename
AND ur.username = 'ADMIN'
This worked for me. Thanks for all the suggestions.
var query =
from r in Roles
from ur in UsersInRoles
.Where(v => v.Rolename == r.Rolename && v.Username == "ADMIN")
.DefaultIfEmpty()
select new { Rolename = r.Rolename, IsInRole = (ur.Username != null) };
The generated SQL is as follows
SELECT
1 AS [C1],
[Extent1].[Rolename] AS [Rolename],
CASE WHEN ([Extent2].[Username] IS NOT NULL) THEN cast(1 as bit) WHEN ([Extent2].[Username] IS NULL) THEN cast(0 as bit) END AS [C2]
FROM [bgt].[Roles] AS [Extent1]
LEFT OUTER JOIN [bgt].[UsersInRoles] AS [Extent2] ON ([Extent2].[Rolename] = [Extent1].[Rolename]) AND ('ADMIN' = [Extent2].[Username])
I would do it like this:
from role in db.Roles
let isInRole = role.UsersInRoles.Any(u => u.UserName == "ADMIN")
select new { role.RoleName, isInRole }
Althought the generated SQL is not as nice as yours.
Extension method for Left join (linq to entities):
public static class DbSetExtensions
{
public static IQueryable<TResult2> LeftJoin<TOuter, TInner, TKey, TResult2>(
this IQueryable<TOuter> outerList,
IEnumerable<TInner> innerList,
Expression<Func<TOuter, TKey>> outerKeySelector,
Expression<Func<TInner, TKey>> innerKeySelector,
Expression<Func<LeftJoinTemp<TOuter, TInner>, TInner, TResult2>> resultSelector2)
{
// (tr, historicPrices) => new { tr, historicPrices }
Expression<Func<TOuter, IEnumerable<TInner>, LeftJoinTemp<TOuter, TInner>>> myResultSelector1
= (tr, historicPrices) => new LeftJoinTemp<TOuter, TInner> { outer = tr, inners = historicPrices };
// e => e.historicPrices.DefaultIfEmpty()
Expression<Func<LeftJoinTemp<TOuter, TInner>, IEnumerable<TInner>>> myCollectionSelector
= e => e.inners.DefaultIfEmpty();
//var a = outerList.GroupJoin(innerList, outerKeySelector, innerKeySelector, resultSelector1);
var a = outerList.GroupJoin(innerList, outerKeySelector, innerKeySelector, myResultSelector1);
//return a.SelectMany(collectionSelector, resultSelector2);
var b = a.SelectMany(myCollectionSelector, resultSelector2);
return b;
}
}
public class LeftJoinTemp<TOuter, TInner>
{
public TOuter outer;
public IEnumerable<TInner> inners;
}
example calling, left join of Transaction and HistoricPrice, for transaction you have to write parent.outer
.LeftJoin(db.HistoricPrices,
transaction => new { transaction.InstrumentId, DateId = transaction.Date2Id },
historicPrice => new { historicPrice.InstrumentId, historicPrice.DateId },
(parent, historicPrice) => new
{
parent.outer.Id,
parent.outer.OpeningDate,
parent.outer.InstrumentName,
historicPrice.DateId,
historicPrice.InstrumentId
})
Related
I have this SQL which I am trying to convert to LINQ , how can this be converted?
Is there an equivalent of Lag at all?
I see there is a case statement not sure how to use it
SELECT
ah.AuthHist_ID,
ah.F_ID,
CASE WHEN ah.AuthPIFlg = 1 OR ah.AuthPINVFlg = 1 THEN 'True' ELSE 'False' end AS chkReqPI,
lag(CASE WHEN ah.AuthPIFlg = 1 OR ah.AuthPINVFlg = 1 THEN 'True' ELSE 'False' end, 1, null) OVER (ORDER BY ah.f_id, ah.AuthHist_ID) AS prevChkReqPI,
ah.Cr8Dt,
lag(ah.Cr8Dt, 1, null) OVER (ORDER BY ah.f_id, ah.AuthHist_ID) AS prevCr8Dt,
cu.UserName AS Cr8UserName,
lag(cu.UserName, 1, null) OVER (ORDER BY ah.f_id, ah.AuthHist_ID) AS prevCr8UserName,
fh.UpdtDt,
FROM AuthHist Ah
LEFT JOIN User cu
ON Ah.Cr8User_ID = cu.User_ID
WHERE Ah.F_ID = #fid
return (from a in DbContext.AuthHist
join c DbContext.User on a.UpdtUserId equals c.UserId
where a.FId == fId
select new AuthHistEntity()
{
FId = a.FId,
checkReqPI = a.AuthPIflg = 1 || a.AuthPINVflg = 1 :
});
I have this SQL which I am trying to convert to LINQ , how can this be
converted?
You can use linq to do this, but it will be a bit more complicated.
For the case when in linq, you can directly use the ternary operator(?:) instead.
As for the Lag() function, we need to replace this function with another sql writing as follow:
(This is just to facilitate the understanding of the linq statement below, you do not need to change your sql statement)
select kh.AuthHist_ID,kh.F_ID,
CASE WHEN kh.AuthPIFlg = 1 OR kh.AuthPINVFlg = 1 THEN 'True' ELSE 'False' end AS chkReqPI,
CASE WHEN (ch.AuthPIFlg is null or ch.AuthPINVFlg is null) then Null when (ch.AuthPIFlg = 1 OR ch.AuthPINVFlg = 1) THEN 'True' ELSE 'False' end AS prevChkReqPI ,
kh.Cr8Dt,
ch.Cr8Dt AS prevCr8Dt ,
kh.UserName AS Cr8UserName,
cu.UserName AS prevCr8UserName
from
( select -1+row_number() over (order by ah.f_id,ah.AuthHist_ID) as row1, * from
(select * from AuthHist a left join User u on a.Cr8User_ID = u.User_ID) Ah) kh
left join (select * from AuthHist a left join User u on a.Cr8User_ID = u.User_ID) cu ON kh.row1 = cu.AuthHist_ID
left join AuthHist ch on kh.row1 = ch.AuthHist_ID where kh.F_ID =#fid
Because the types of some fields are uncertain, I have create the AuthHistEntity class as follow, you can modify some details according to your needs.
public class AuthHistEntity
{
public int AuthHist_ID { get; set; }
public int F_ID { get; set; }
public string chkReqPI { get; set; }
public string prevChkReqPI { get; set; }
public string Cr8Dt { get; set; }
public string prevCr8Dt { get; set; }
public string Cr8UserName { get; set; }
public string prevCr8UserName { get; set; }
}
Here is the linq writing, you can try it:
var newUser = (from a in DbContext.AuthHist
join u in DbContext.User on a.Cr8User_ID equals u.User_ID
select new { a, u }).ToList();
var newAuthList = (from t in newUser
select new
{
row_1 = t.a.AuthHist_ID - 1,
data = t
}).ToList();
int fid = 1;
var result = (from dt in
(from ch in newAuthList
join ah in DbContext.AuthHist on ch.row_1 equals ah.AuthHist_ID
into ot from otnew in ot.DefaultIfEmpty()
select new { T1 = ch, T2 = otnew == null ? new AuthHist() : otnew
}).ToList()
join nu in newUser on dt.T1.row_1 equals nu.a.AuthHist_ID
into yG from otnew in yG.DefaultIfEmpty()
where dt.T1.data.a.F_ID == fid
select new AuthHistEntity()
{
AuthHist_ID = dt.T1.data.a.AuthHist_ID,
F_ID = dt.T1.data.a.F_ID,
chkReqPI = (dt.T1.data.a.AuthPIFlg == 1 || dt.T1.data.a.AuthPINVFlg == 1) ? "True" : "False",
prevChkReqPI = (dt.T2.AuthPIFlg == null || dt.T2.AuthPINVFlg == null) ? null : ((dt.T2.AuthPIFlg == 1 || dt.T2.AuthPINVFlg == 1) ? "True" : "Flase"),
Cr8Dt = dt.T1.data.a.Cr8Dt,
prevCr8Dt = dt.T2.Cr8Dt == null ? null : dt.T2.Cr8Dt,
Cr8UserName = dt.T1.data.u.UserName,
prevCr8UserName = otnew == null ? null : otnew.u.UserName
}).ToList();
I'm wondering if ServiceStack.OrmLite's JoinSqlBuilder allow to build the following simple query:
SELECT * FROM Table1 a
INNER JOIN Table2 b ON ...
WHERE a.Column1 = 1 AND (a.Column2 = 2 OR b.Column3 = 3);
The problem is to build (a.Column2 = 2 OR b.Column3 = 3) part.
JoinSqlBuilder has a list of methods such as Where<T>, And<T>, Or<T> that allow to add conditions for a query.
For example, if i do:
builder
.Join(...)
.Where<Table1Poco>(a => a.Column1 == 1)
.And<Table1Poco>(a => a.Column2 == 2)
.Or<Table2Poco>(a => a.Column3 == 3)
...;
I will get:
... WHERE a.Column1 = 1 AND a.Column2 = 2 OR b.Column3 = 3;
Is there any way to build a.Column1 = 1 AND (a.Column2 = 2 OR b.Column3 = 3) with ServiceStack.OrmLite?
I know that i can do it with raw sql but it's not an option as i don't want to lose type safety and dialect independence.
I agree with kunjee that this is not really something a Micro-orm is good for. With that said, I can think of 2 potential options...neither of which are really something I would recommend over a full-blown ORM (EF or nHibernate) as a solution. But, maybe this will help solicit better options.
Option 1 - Build up a 'Where clause string' using reflection to keep some 'type safety'. You will still need to write a little SQL.
Example
var jn = new JoinSqlBuilder<Table1, Table2>();
jn = jn.Join<Table1, Table2>(s => s.Column1, d => d.Field1);
//using ExpressionVisitor because I didn't see a way to allow a Where clause string parameter to be used
//on a JoinSqlBuilder method
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<Table1>();
ev.Where(
SqlHelper.ToSqlField<Table1>(x => x.Column1) + "={0} AND (" +
SqlHelper.ToSqlField<Table1>(x => x.Column2) + "={1} OR " + SqlHelper.ToSqlField<Table2>(x => x.Column3) +
"={2})", "1", "2", "3");
var sql = jn.ToSql() + ev.WhereExpression;
Helper Class
public static class SqlHelper
{
public static string ToSqlField<T>(Expression<Func<T, object>> expression)
{
//This should return something like 'Table1.Column1'
return typeof(T).Name + "." + GetMemberInfo(expression).Name;
}
// Stolen from FluentNHibernate.ReflectionUtility
public static MemberInfo GetMemberInfo<TEntity>(Expression<Func<TEntity, object>> expression)
{
MemberInfo memberInfo = null;
switch (expression.Body.NodeType)
{
case ExpressionType.Convert:
{
var body = (UnaryExpression)expression.Body;
if (body.Operand is MethodCallExpression)
{
memberInfo = ((MethodCallExpression)body.Operand).Method;
}
else if (body.Operand is MemberExpression)
{
memberInfo = ((MemberExpression)body.Operand).Member;
}
}
break;
case ExpressionType.MemberAccess:
memberInfo = ((MemberExpression)expression.Body).Member;
break;
default:
throw new ArgumentException("Unsupported ExpressionType", "expression");
}
if (memberInfo == null) { throw new ArgumentException("Could not locate MemberInfo.", "expression"); }
return memberInfo;
}
}
Option 2 - Mess/Pollute your Classes and turn off Table prefixes in an ExpressionVisitor to allow the correct SQL to be generated. This will completely blow up if 2 classes have the same property and are used in a Where clause.
//Modify Table1 to include a reference to Table2
public class Table1
{
public string Column1 { get; set; }
public string Column2 { get; set; }
[ServiceStack.DataAnnotations.Ignore]
public Table2 Table2 { get; set; }
}
var ev = OrmLiteConfig.DialectProvider.ExpressionVisitor<Table1>();
ev.PrefixFieldWithTableName = false;
var jn = new JoinSqlBuilder<Table1, Table2>();
jn = jn.Join<Table1, Table2>(s => s.Column1, d => d.Field1);
ev.Where(x => x.Column1 == "1");
ev.Where(x => x.Column2 == "2" || ((Table2)x.Table2).Column3 == "3"); //do cast to avoid InvalidOperationException
var sql = jn.ToSql() + ev.WhereExpression;
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.
I have the following Entity Framework 4.1 entities and relationships
Concert ConcertId, AdministratorUserId, Name, IsDeleted
Booking
BookingId, ConcertId, UserId, IsDeleted
UserId, UserId, Name, IsDeleted
Relationships
Concert 1.....M Booking 1....1 User
Now I am trying to select all the Concerts for a particular AdminstratorUserId but also include all the Bookings for each Concert and also the User details for each booking. I would also like to apply a filter where IsDeleted == false for each Concert, Booking and User. I Would like to return a List of Concerts which have their Booking and User details maintained as navigational properties.
In SQL, this is what I am trying to achieve:
SELECT *
FROM concert c, booking b, user u
WHERE c.ConcertId = b.ConcertId AND b.UserId = u.UserId AND c.AdministratorId = 10
AND c.IsDeleted = false AND b.IsDeleted = false AND u.IsDeleted = false
As far as I am aware, using the "Include" method to eager load, doesn't allow filtering or subqueries of the child entity it loads in; it returns all records for that join, so I tried to use an anonymous projection as follows:
int adminId = 10;
var concerts = _context.Concerts
.Where(p => p.AdministratorId == adminId && p.IsDeleted == false)
.Select(p => new {
Concerts = p,
Bookings = p.Bookings
.Where(q => q.IsDeleted == false && q.User.IsDeleted == false)
.Select(r => new {
Bookings = r,
User = r.User
})
.AsEnumerable()
.Select(q => q.Bookings)
})
.AsEnumerable()
.Select(p => p.Concerts)
.ToList();
However, this is still returning all records and not filtering out the ones where IsDeleted = true. Anyone have any ideas, or suggestions how I can clean up this monstrous query?
I have also tried a method similar to this (http://blogs.msdn.com/b/alexj/archive/2009/06/02/tip-22-how-to-make-include-really-include.aspx), which again fails (returns all bookings even deleted ones):
var concertsQuery = (ObjectQuery<Concert>)_context.Concerts
.Where(p => p.UserId == userId
&& p.IsDeleted == false
&& p.Bookings.Any(q => q.IsDeleted == false && q.User.IsDeleted == false)
);
var concerts = concertsQuery.Include("Bookings").Include("Bookings.User").ToList();
Get rid of those AsEnumerable they will turn your query to linq-to-objects:
var concerts = _context.Concerts
.Where(p => p.AdministratorId == adminId && p.IsDeleted == false)
.Select(p => new {
Concerts = p,
Bookings = p.Bookings
.Where(q => q.IsDeleted == false && q.User.IsDeleted == false)
.Select(r => new {
Bookings = r,
User = r.User
})
})
.ToList();
So, the way I've managed to resolve this issue is by running the LINQ query as per Ladislav's answer, but then using the anonymous object to re-attach the individual objects to their correct relational properties as below:
var concertResult = new List<Concert>();
foreach (var concertObject in concerts)
{
var concert = concertObject.Concert;
foreach (var bookingObject in concertObject.Bookings)
{
var booking = bookingObject.Booking;
booking.User = bookingObject.User;
concert.Bookings.Add(booking);
}
concertResult.Add(concert);
}
I would like NHibernate to generate the following SQL but I'm stuggling with the code below
SELECT rt.Id
FROM ClientContact cc
JOIN ClientContact_DefaultRequest d on d.ClientContactID = cc.Id
JOIN RequestType rt on d.RequestTypeId = rt.Id
WHERE
cc.Id = ContactId
public class GetDefaultRequestsForContact : SimpleQuery<IEnumerable<RequestType>>, IGetDefaultRequestsForContact
{
public int ContactId { private get; set; }
public GetDefaultRequestsForContact(ISession session) : base(session) { }
public override IEnumerable<RequestType> Execute()
{
var x = Session.QueryOver<ClientContact>()
.Where(c => c.Id == ContactId)
.JoinQueryOver<RequestType>(c => c.DefaultRequests)
.Select(c => c.DefaultRequests)
.List();
return null;
}
}
The generated SQL is only selecting the parent id (which is the parameter) I want all the child Ids.
SELECT this_.Id as y0_
FROM ClientContact this_
inner join ClientContact_DefaultRequest defaultreq3_
on this_.Id = defaultreq3_.ClientContactID
inner join RequestType requesttyp1_
on defaultreq3_.RequestTypeID = requesttyp1_.Id
WHERE this_.Id = 313706 /* #p0 */
This works but it is not strongly typed (HQL).
var x = Session.CreateQuery("SELECT R.Id FROM ClientContact cc JOIN cc.DefaultRequests R WHERE cc.Id = :contactId")
.SetParameter("contactId",ContactId)
.List<int>();
return x;
Try using Aliases like:
ClientContact Cont = null;
RequestType Req = null;
var x = session.QueryOver<ClientContact>(() => Cont)
.Where(() => Cont.ID == ContactId)
.JoinAlias(() => Cont.DefaultRequests, ()=> Req, JoinType.LeftOuterJoin)
.Select(ignore => Req.Id) // <-- Select wants a parameter
.List<int>();
You have to turn it around. I believe Faber's answer will also work well. So you do not have to turn it around. (But you could.)
ClientContact cAlias = null;
var x = Session.QueryOver<RequestType>()
.JoinAlias(rt => rt.ClientContacts, () => cAlias)
.Where(() => cAlias.Id == ContactId)
.Select(rt => rt.Id) // not sure what you want returned here, Id or the object)
.List();