How to reference the same table twice? - linq

First of all I have to admit I'm very much a novice in Linq and Lambda expressions.
I'm trying to get the following SQL statement into a Linq statement (using lamda expressions):
select *
from dbo.tblStockTransfers t1,
dbo.tblSuppliers t2
where t1.SupplierID = t2.SupplierID
and t2.WarehouseID in (1,2,3)
and t1.GoodsPickedUp = 1
and Not exists
(select 1 from dbo.tblStockTransfers t3
where t3.TransferOutID = t1.TransferID and t3.TransferConfirm = 1)
My class StockTransfer is an aggregate root and has it's own repository.
Now so far I got the following (the variable allowedWarehouses contains the list of warehouse IDs):
Return GetObjectSet().Where(Function(st) allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True)
This works fine, but obviously is missing the " and not exists ..." part (the last 3 lines of the SQL code at the top of this posting).
I know that Linq doesn't have a "not exists", but you can use the "Any" method for this.
Here's a working example of this elsewhere in my code:
Return GetObjectSet().Where(Function(sw) sw.Active = True And Not sw.Suppliers.Any(Function(sp) sp.WarehouseID = sw.Id))
This works fine and will give me any warehouses which are not linked to a supplier yet.
As you can see in the above example this is fine as I'm referring to the related table "Suppliers".
However, in the SQL code I'm now trying to convert into Linq, the "not exists" is not on a linked table but on itself. Is there a way I can create a 2nd reference on the main table and use that in a ".. not ..any" part. Maybe something like:
Return GetObjectSet().Where(Function(st) allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True And Not st2.Any(st2.TransferOutID = st.TransferId and st2.TransferConfirm = true)
But I don't know how to define st2 (i.e. in this case st2 would be a 2nd alias to StockTransfer).
Any Help would be greatly appreciated.

This is not the answer to the question, but it is a work-around which does get me the result I need:
Dim st1 As List(Of StockTransfer) = GetObjectSet.Where(Function(st) allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True).ToList
Dim st2 As List(Of StockTransfer) = GetObjectSet.Where(Function(st) st.TransferConfirm = True).ToList
For Each st As StockTransfer In st2
st1.RemoveAll(Function(x) x.Id = st.TransferOutID)
Next
Return st1
I'm obviously cheating by splitting out the query in 2 parts, where each part ends up in a list and then I remove from list 1 any items which I've got in list 2 (removing the ones which would normally be ignored by the "not exists" part).
However, I would love to hear it if anyone can come up with a working solution using Linq and lambda expressions (as this does feel a bit like a cheat).

I would do it something like this:
Dim lsWareHouseIds As New List(Of Integer)() From {1,2,3}
dim obj= ( _
From t1 in db.tblStockTransfers _
join t2 in db.tblSuppliers _
on t1.SupplierID equals company.SupplierID _
where lsWareHouseIds.Contains(t2.WarehouseID) _
andalso t1.GoodsPickedUp =1 _
andalso Not _
(
from t3 in db.tblStockTransfers _
where t3.TransferConfirm=1 _
select t3.TransferOutID _
).Contains(t1.TransferID) _
select t1 _
)
I did see you comment and you answer. Can't you do like this:
GetObjectSet.Where(Function(st) _
allowedWarehouses.Contains(st.Supplier.WarehouseID) And st.GoodsPickedUp = True _
Andalso Not _
GetObjectSet.Where(Function(st) _
st.TransferConfirm = True).Any(Function(x) x.Id = st.TransferOutID)).ToList

Related

Linq Compiled Queries and int[] as parameter

I'm using the following LINQ to SQL compiled query.
private static Func<MyDataContext, int[], int> MainSearchQuery =
CompiledQuery.Compile((MyDataContext db, int[] online ) =>
(from u in db.Users where online.Contains(u.username)
select u));
I know it is not possible to use sequence input paramter for a compiled query and im getting “Parameters cannot be sequences” error when running it.
On another post here related , I saw that there is some solution but I couldn't understand it.
Does anyone know to use complied query with array as input paramter?
Please post example if you do.
Like the post that you referenced, it's not really possible out of the box. The post also references creating your own query provider, but it's a bit of overhead and complexity that you probably don't need.
You have a few options here:
Don't use a compiled query. Rather, have a method which will create a where clause from each item in the array resulting in something like this (psuedo-code):
where
online[0] == u.username ||
online[1] == u.username ||
...
online[n] == u.username
Note that you would have to use expression here to create each OR clause.
If you are using SQL Server 2008, create a scalar valued function which will take a table-valued parameter and a value to compare againt. It will return a bit (to indicate if the item is in the values in the table). Then expose that function through LINQ-to-SQL on your data context. From there, you should be able to create a CompiledQuery for that. Note that in this case, you should take an IEnumerable<string> (assuming username is of type string) instead of an array, just because you might have more than one way of representing a sequence of strings, and to SQL server for this operation, it won't matter what the order is.
One solution that I have found myself doing (for MS SQL 2005/2008). And I'm not sure if it is appropriate in all scenarios is to just write dynamic sql and execute it against the datacontext using the ExecuteQuery method.
For example, if I have an unbounded list that I am trying to pass to a query to do a contains...
' Mock a list of values
Dim ids as New List(of Integer)
ids.Add(1)
ids.Add(2)
' ....
ids.Add(1234)
Dim indivs = (From c In context.Individuals _
Where ids.Contains(c.Id) _
Select c).ToList
I would modify this query to create a SQL string to execute against the database directly like so...
Dim str As New Text.StringBuilder("")
Dim declareStmt as string = "declare #ids table (indivId int) " & vbcrlf)
For i As Integer = 0 To ids.Count - 1
str.Append("select " & ids(i).ToString() & " & vbcrlf)
If i < ids.Count Then
str.Append("union " & vbcrlf)
End If
Next
Dim selStatement As String = "select * From " & context.Mapping.GetTable(GetType(Individuals)).TableName & _
" indiv " & vbcrlf & _
" inner join #ids ids on indiv.id = ids.id"
Dim query = declareStmt & str.ToString & selStatement
Dim result = context.ExecuteQuery(of Individual)(query).ToList
So barring any syntax errors or bugs that I coded (the above is more or less psuedo code and not tested), the above will generate a table variable in SQL and execute an inner join against the desired table (Individuals in this example) and avoid the use of a "IN" statement.
Hope that helps someone out!

Need help with designing a query in ELinq

This is my query:
Dim vendorId = 1, categoryId = 1
Dim styles = From style In My.Context.Styles.Include("Vendor") _
Where style.Vendor.VendorId = vendorId _
AndAlso (From si In style.StyleItems _
Where si.Item.Group.Category.CategoryId = _
categoryId).Count > 0 _
Distinct
I have the feeling that I can improve the performance, cuz the above query is (correct me if I am wrong) performs 2 round-trips to the server; 1 time by the Count and then when it's executed.
I want to send this Count thing to the DB so it should be only one round trip to the server.
Even it's not the exact thing, this is actually what I need:
SELECT DISTINCT Style.*
FROM Style INNER JOIN
Vendor ON Style.VendorId = Vendor.VendorId INNER JOIN
StyleItem ON Style.StyleId = StyleItem.StyleId INNER JOIN
Item ON StyleItem.ItemId = Item.ItemId INNER JOIN
[Group] ON Item.GroupId = [Group].GroupId INNER JOIN
Category ON [Group].CategoryId = Category.CategoryId
WHERE (Style.VendorId = #vendorid) AND (Category.CategoryId = #CategoryId)
I wish I could use this SPROC (i.e. function import etc.), but I need to Include("Vendor"), which constraints me to do it with Linq.
Any kind of suggestion will be really welcommed!
It is probably not doing two trips to the database. It will get optimized before it is executed, and nothing gets executed until you try the read the data.
Normally I check the SQL that is created using SQL Profiler. I have also found LinqPad to be very usefull.

Confusing LINQ statement

I am trying to craft a LINQ statement as follows:
The result should follow between two dates (today basically) OR
the result should have a "batchstatus" column that equals false (hasn't been verified yet)
the result should have a "ready" column that equals true (is ready to be verified).
So verifiers can see all data from today regardless if its been verified or not, BUT shouldn't see any that the users are not ready to be seen yet.
I have tried this several different ways such as:
Dim p = From t In db.batches _
Where t.bDate > day1 And t.bDate < day2 And t.Ready = True Or t.BatchStatus = False _
Order By t.BatchStatus Ascending _
Select t
Please help me keep my hair; I have a handful now & I don't know how much longer I can keep from yanking it out!!!
Thanks!
Use brackets. It'll make things a lot simpler. Additionally, you can use Let clauses to make your query simpler, effectively giving you "local variables" in the query. Try this - the syntax may be slightly off (I'm not a VB guy) and the logic may very well not be what you want, but it's easier to understand IMO, so should be easier to modify.
Dim p = From t In db.batches _
Let createdToday = t.bDate > day1 And t.bDate < day2 _
Where createdToday And (t.Ready Or Not t.BatchStatus) _
Order By t.BatchStatus Ascending
What about:
Dim p = From t In db.batches _
Where (t.bDate.Date = DateTime.Today Or t.BatchStatus = False) _
And t.Ready = True _
Order By t.BatchStatus Ascending _
Select t
Thanks Jon!!! It was parenthesis in my case:
Dim p = From t In db.batches _
Where (t.bDate > day1 And t.bDate < day2 And t.Ready = True) Or (t.BatchStatus = False And t.Ready = True) _
Order By t.BatchStatus Ascending _
Select t
Thanks again....hair slowly being removed from death grip...

Linq Join Question

I have a problem trying to do a couple of things with linq joins... currently I have a
group in linq that gives two columns, basically a count of tickets by location. Well now I'm trying to add a join that will join on the ticketID columns of two different tables Tickets and Comments.
I'm having a hell of a time trying to convert the sql join into Linq, less alone merging that into my original total count linq statement...somebody please help!
Original Linq statement:
From p In NewTickets _
Where p.TDate > "06/01/2009 12:00:00AM" And p.TDate < "07/01/2009 12:00:00PM" _
Group p By p.LocationID _
Into Count() _
Select _
LocationID, _
NoOfTickets = Count _
Order By NoOfTickets Descending
Join I need merged into Linq statement:
SELECT *
FROM NewTickets as p
LEFT OUTER JOIN NewComments AS c ON p.TicketID = c.TicketID
WHERE (p.TDate > '06/01/2009 12:00:00AM') And (p.TDate < '07/01/2009 12:00:00PM')
AND c.Comment Like '%ali%'
THANK YOU!
If you add a relationship in the linq to sql designer between NewTickets and NewComments, properties will be created on those classes to navigate.
Queries that use those properties will automatically translate into the join. For example:
from t in db.NewTickets
where t.NewComments.Any(nc => nc.Comment.Contains("ali"))
group t by t.LocationID into g
select new {LocationID = g.Key, NoOfTickets = g.Count()} into x
order x by x.NoOfTickets descending
select x;
Apologies for the C# code examples.
Also, I'd like to point out that the left join in your sql is moot - tickets that have no comments will be removed by the ali criteria. An inner join will do fine.
Something like this
var movies = NewTickets.Join(NewComments, x => x.TicketID, y => y.TicketID, (x, y) => x).ToList();
That was it...for the most part...I'm going to have to attack this a different way as now I'm getting a count that apparently from comments as my total has ballooned from under 200 to almost 1300...each ticket will have on average around 5 or so comments so that's why I assuming this just shooting from the hip...
Thank you David and no problem with the C# (as much that I have translated, you think I'd be using it by now).
For anyone using VB that would like to see the same in VB, here you go:
Dim q = From e In db.NewTickets _
Where e.NewComments.Any(Function(nc) nc.Comment.Contains("ali")) _
Group e By e.LocationID _
Into Count() _
Select _
LocationID, _
NoOfTickets = Count _
Order By NoOfTickets Descending

How to combine 2 LINQ into one

I am trying to populate a treeview control with some info and for that I am using 2 separate LINQ statements as follows;
Dim a = From i In CustomerTable _
Group By ShipToCustName = i.Item(6), BillToCustName = i.Item(4) Into Details = Group, Count() _
Order By ShipToCustName
Dim b = From x In a _
Group By ShipToName = x.ShipToCustName Into Group, Count() _
Order By ShipToName
For Each item In b
Console.WriteLine("Shitp To Location: " + item.ShipToName)
For Each x In item.Group
Console.WriteLine("...BillTo:" + x.BillToCustName)
For Each ticket In x.Details
Console.WriteLine("......TicketNum" + ticket.Item(0).ToString)
Next
Next
Next
Is there a way to combine A and B into one query ? Any help please ...
Well, really B is a single query. Remember that what comes back from a Linq statement is an IQueryable, not the actual result. This means that B combines its own expressions with that of A, but retrieval is not performed until you enumerate B. So B really is just a single query.

Resources