Linq to Sql - Count, sub-query and subtraction - linq-to-sql

I am trying to convert the following T-SQL statement into Linq to Sql but am having trouble with the subtraction from the count. The final select will be a single row and single column (int)
I have done the SQL in two ways (sub-query and by JOIN/GROUP) which both return the same result, although I think the former might be the 'easier' option...
SQL 1 using a sub-query...
SELECT e.Places - ( SELECT COUNT(*) FROM [Event Participants] ep WHERE ep.E__ID = x AND ep.EP_STAT IN ('B','C')) AS AvailablePlaces
From Events e
WHERE e.E__ID = x
SQL 2 using GROUP BY and JOIN...
SELECT e.Places - COUNT(ep.E__ID) AS AvailablePlaces
FROM Events e
JOIN [Event Participants] ep ON e.E__ID = ep.E__ID
WHERE e.E__ID = x AND ep.EP_STAT IN ('B','C')
GROUP BY e.Places

Something like
var array = new string[] { "B", "C" };
var result = (from e in Event where e.E__ID == x
let count = (from ep in Event_Participants
where ep.E__ID == e.E__ID &&
array.Contains(ep.EP_Stat)
select ep).Count()
select e.Places - count
)
.Single();
Depending on your model, it might be possible to use navigation properties in the subquery.

Related

Convert sql query with a join on a subselect to a linq statement

I am trying to convert the following sql query to LINQ statement
SELECT t.*
FROM (
SELECT Unique_Id, MAX(Version) mversion
FROM test
GROUP BY Unique_Id
) m INNER JOIN
test t ON m.Unique_Id = t.Unique_Id AND m.mversion = t.Version
LINQ statement
var testalt = (from altt in CS.test
group altt by altt.Unique_Id into g
join bp in CS.alerts on g.FirstOrDefault().Unique_Id equals bp.Unique_Id
select new ABCBE
{
ABCName= bp.Name,
number = bp.Number,
Unique_Id = g.Key,
Version = g.Max(x=>x.Version)
});
I am getting an error of where clause. Please help
SQL FIDDLE
This is not an easy straight forward conversion but you can accomplish the same thing using linq method syntax. The first query is executed to an expression tree, then you are joining that expression tree from the grouping against CS.alerts. This combines the expression tree from CS.test query into the expression tree of CS.alerts to join the two expression trees.
The expression tree is evaluated to build the query and execute said query upon enumeration. Enumeration in this case is the ToList() call but anything that gets a result from the enumeration will execute the query.
var query1 = CS.test.GroupBy(x => x.Unique_Id);
var joinResult = CS.alerts.Join(query1,
alert => new { ID = alert.Unique_Id, Version = alert.Version },
test => new { ID = test.Key, Version = test.Max(y => y.Version },
(alert, test) => new ABCBE {
ABCName = alert.Name,
number = alert.Number,
Unique_Id = test.Key,
Version = test.Max(y => y.Version)
}).ToList();
Because query1 is still an IQueryable and you are using CS.alerts (which I'm guessing CS is your data context) it should join and build the query to execute upon the ToList() enumeration.

Linq to SQL : Left joining a grouped set makes a Cross Apply/Outer Apply

Basically I'm making a report type query where I am aggregating data from multiple tables and left joining it to a single table.
it looks a bit like this:
var docs = from x in DB.Docs
group x by x.PersonId into g
select new {
g.Key,
totalSent = g.Sum(x => x.SentDate.HasValue ? 1 : 0),
lastSent = g.Max(x => x.SentDate)
...
};
var summary = from x in DB.People
from y in docs.Where(y => y.Key == x.Id).DefaultIfEmpty()
select new {
x.Id,
x.Name,
y.totalSent,
y.lastSent
}
I would expect that this created sql that left joined DB.People to the results of docs but instead I get some crazy CROSS APPLY(( SELECT NULL AS [EMPTY]) as [t1] OUTER APPLY ... stuff.
I've tried every variant of the left join syntax I can think, I've even wrapped docs in another query, and I get the same thing.
What am I missing?
from x in DB.People
from y in docs.Where(y => y.Key == x.Id).DefaultIfEmpty()
The above will clearly generate cartesian results which are later filtered.
Perhaps you meant to join:
from x in DB.People
join y2 in docs on x.Id equals y2.Key into g
from y in g.DefaultIfEmpty()
How about this:
from x in DB.People
let g = x.Docs
select new
{
x.Id,
x.Name,
totalSent = g.Sum(y => y.SentDate.HasValue ? 1 : 0),
lastSent = g.Max(y => y.SentDate)
}

Group By and Sum clauses in LINQ

I've written a simple linq query as follows:
var query = from c in context.ViewDeliveryClientActualStatus
join b in context.Booking on c.Booking equals b.Id
join bg in context.BookingGoods on c.Booking equals bg.BookingId
select new { c, b, bg };
I have filtered the previous query with a number of premises and then needed to group by a set of fields and get the sum of some of them, as so:
var rows = from a in query
group a by new {h = a.c.BookingRefex, b = a.c.ClientRefex, c = a.b.PickupCity, d = a.b.PickupPostalCode} into g
select new
{
Booking_refex = g.Key.h,
Client_refex = g.Key.b,
//Local = g.
Sum_Quan = g.Sum(p => p.bg.Quantity),
};
I'd like to get a few values from a which I haven't included in the group by clause. How can I get those values? They're not accessible through g.
The g in your LINQ expression is an IEnumerable containing a's with an extra property Key. If you want to access fields of a that are not part of Key you will have to perform some sort of aggregation or selection. If you know that a particular field is the same for all elements in the group you can pick the value of the field from the first element in the group. In this example I assume that c has a field named Value:
var rows = from a in query
group a by new {
h = a.c.BookingRefex,
b = a.c.ClientRefex,
c = a.b.PickupCity,
d = a.b.PickupPostalCode
} into g
select new {
BookingRefex = g.Key.h,
ClientRefex = g.Key.b,
SumQuantity = g.Sum(p => p.bg.Quantity),
Value = g.First().c.Value
};
However, if c.Value is the same within a group you might as well include it in the grouping and access it using g.Key.cValue.
Just add those field in the
new {h = a.c.BookingRefex, b = a.c.ClientRefex, c = a.b.PickupCity, d = a.b.PickupPostalCode}
they will be accessible in g then.

Dynamically searching using LINQ and dynamically created predicates

I have some predicates being dynamically built that have the following signature passes through as parameters into a function:
Expression<Func<TblTableR, bool>> TableRPredicate,
Expression<Func<TblTableN, bool>> suspectNamesPredicate,
Expression<Func<TblTableS, bool>> TableSPredicate,
Expression<Func<TblTableI, bool>> suspectTableIPredicate,
I am trying to query using the following:
var registries = (from r in db.TblTableR.Where(TableRPredicate)
join s in db.TblTableS.Where(TableSPredicate)
on r.TblTableRID equals s.TblTableSTableRID into ss
from suspects in ss.DefaultIfEmpty()
join si in db.TblTableI.Where(suspectTableIPredicate)
on suspects.TblTableSIndexCardID equals si.TblTableIID into sisi
from suspectTableI in sisi.DefaultIfEmpty()
join sn in db.TblTableN.Where(suspectNamesPredicate)
on suspectTableI.TblTableIID equals sn.TblTableNIndexCardID into snsn
from suspectNames in snsn.DefaultIfEmpty()
select r.TblTableRID).Distinct();
This has the result of putting any generated "where" clause into the JOIN statement eg:
left outer join tblTableI on tblTableITableRID = tblTableRID
AND (expression created by predicate)
What is actually happening is that the final SQL that is created is incorrect. It is creating the following type of sql
select * from table1 left outer join table2 on field1 = field2
AND field3 = 'CRITERIA'
It is this AND clause on the end that is the problem - ending up returning too many rows. Essentially I would like to get the where clause into the statement and not have it stick the extra condition into the join.
Something like this:
select * from table1 left outer join table2 on field1 = field2
WHERE field3 = 'CRITERIA'
I have tried adding a Where clause in as follows:
...
...
...
select r.TblTableRID).Where(TableRPredicate).Distinct();
but that will not compile because of the generic parameters on each predicate.
If I modify my LINQ query to only select from one table and use a predicate, the WHERE clause is generated correctly.
Any ideas?
(edited post clarification)
Step 1; change the final select to select all three entities into an anonymous type; for me (testing on Northwind), that is:
select new {emp, cust, order};
Step 2; apply your filters to this using the extension method I've added below; for me this filtering looks like:
var qry2 = qry.Where(x => x.emp, employeeFilter)
.Where(x => x.cust, custFilter)
.Where(x => x.order, orderFilter);
Step 3; now select the entity/entities you actually want from this filtered query:
var data = qry2.Select(x => x.order)
And here's the extension method:
static IQueryable<T> Where<T,TValue>(
this IQueryable<T> source,
Expression<Func<T, TValue>> selector,
Expression<Func<TValue, bool>> predicate)
{
var row = Expression.Parameter(typeof (T), "row");
var member = Expression.Invoke(selector, row);
var lambda = Expression.Lambda<Func<T, bool>>(
Expression.Invoke(predicate, member), row);
return source.Where(lambda);
}

What's the Linq to SQL equivalent to TOP or LIMIT/OFFSET?

How do I do this
Select top 10 Foo from MyTable
in Linq to SQL?
Use the Take method:
var foo = (from t in MyTable
select t.Foo).Take(10);
In VB LINQ has a take expression:
Dim foo = From t in MyTable _
Take 10 _
Select t.Foo
From the documentation:
Take<TSource> enumerates source and yields elements until count elements have been yielded or source contains no more elements. If count exceeds the number of elements in source, all elements of source are returned.
In VB:
from m in MyTable
take 10
select m.Foo
This assumes that MyTable implements IQueryable. You may have to access that through a DataContext or some other provider.
It also assumes that Foo is a column in MyTable that gets mapped to a property name.
See http://blogs.msdn.com/vbteam/archive/2008/01/08/converting-sql-to-linq-part-7-union-top-subqueries-bill-horst.aspx for more detail.
Use the Take(int n) method:
var q = query.Take(10);
The OP actually mentioned offset as well, so for ex. if you'd like to get the items from 30 to 60, you would do:
var foo = (From t In MyTable
Select t.Foo).Skip(30).Take(30);
Use the "Skip" method for offset.
Use the "Take" method for limit.
#Janei: my first comment here is about your sample ;)
I think if you do like this, you want to take 4, then applying the sort on these 4.
var dados = from d in dc.tbl_News.Take(4)
orderby d.idNews descending
select new
{
d.idNews,
d.titleNews,
d.textNews,
d.dateNews,
d.imgNewsThumb
};
Different than sorting whole tbl_News by idNews descending and then taking 4
var dados = (from d in dc.tbl_News
orderby d.idNews descending
select new
{
d.idNews,
d.titleNews,
d.textNews,
d.dateNews,
d.imgNewsThumb
}).Take(4);
no ? results may be different.
This works well in C#
var q = from m in MyTable.Take(10)
select m.Foo
Whether the take happens on the client or in the db depends on where you apply the take operator. If you apply it before you enumerate the query (i.e. before you use it in a foreach or convert it to a collection) the take will result in the "top n" SQL operator being sent to the db. You can see this if you run SQL profiler. If you apply the take after enumerating the query it will happen on the client, as LINQ will have had to retrieve the data from the database for you to enumerate through it
I do like this:
var dados = from d in dc.tbl_News.Take(4)
orderby d.idNews descending
select new
{
d.idNews,
d.titleNews,
d.textNews,
d.dateNews,
d.imgNewsThumb
};
You would use the Take(N) method.
Taking data of DataBase without sorting is the same as random take
Array oList = ((from m in dc.Reviews
join n in dc.Users on m.authorID equals n.userID
orderby m.createdDate descending
where m.foodID == _id
select new
{
authorID = m.authorID,
createdDate = m.createdDate,
review = m.review1,
author = n.username,
profileImgUrl = n.profileImgUrl
}).Take(2)).ToArray();
I had to use Take(n) method, then transform to list, Worked like a charm:
var listTest = (from x in table1
join y in table2
on x.field1 equals y.field1
orderby x.id descending
select new tempList()
{
field1 = y.field1,
active = x.active
}).Take(10).ToList();
This way it worked for me:
var noticias = from n in db.Noticias.Take(6)
where n.Atv == 1
orderby n.DatHorLan descending
select n;