I'm having a heckuva time figuring out how to translate a simple SQL LEFT OUTER JOIN with a two condition where clause into a working Linq-to-Entities query. There are only two tables. I need values for all rows from Table1, regardless of matches in Table2, but the WHERE clause uses fields from Table2. In SQL, the two parameters would be Table2WhereColumn1 and Table2WhereColumn2, and the query (which works) looks like this:
SELECT t1.Table1Id,
t1.FieldDescription,
t2.FieldValue
FROM Table1 t1 WITH (NOLOCK)
LEFT JOIN Table2 t2 WITH (NOLOCK) ON t1.Table1Id = t2.Table1Id
WHERE (t2.Table2WhereColumn1 = #someId OR t2.Table2WhereColumn1 IS NULL)
AND (t2.Table2WhereColumn2 = #someOtherId OR t2.Table2WhereColumn2 IS NULL)
ORDER BY t1.OrderByColumn
I've tried using Group Join with DefaultIfEmpty(), as well as an implicit join (without the actual Join keyword), and I only get rows for items that have values in Table2. I'm sure this won't help, but here's an example of the Linq I've been trying that doesn't work:
Public Shared Function GetProfilePreferencesForCedent(ByVal dc As EntityContext, _
ByVal where1 As Int32, _
ByVal where2 As Int32) _
As IQueryable(Of ProjectedEntity)
Return From t1 In dc.Table1
Group Join t2 In dc.Table2 _
On t1.Table1Id Equals t2.Table1Id _
Into t2g1 = Group _
From t2gx In t2g1.DefaultIfEmpty(Nothing)
Where (t2gx.Table2Where1 = where1 Or t2gx.Table2Where1 = Nothing) _
And (t2gx.Table2Where2 = where2 Or t2gx.Table2Where2 = Nothing)
Order By t1.SortOrder
Select New ProjectedEntity With {
.Table1Id = t1.Table1Id, _
.FieldDescription = t1.FieldDescription, _
.FieldValue = If(t2gx Is Nothing, String.Empty, t2gx.FieldValue) _
}
End Function
Have a go at these queries and tell me if they work for you. I haven't set up the data to test, but they should be fine.
Please excuse my mix of C# & VB.NET. I used to be a VB.NET developer, but in the last couple of years I've mostly worked in C#, so I now feel more comfortable there.
Here are the classes I created for Table1 & Table2:
public class Table1
{
public int Table1Id { get; set; }
public string FieldDescription { get; set; }
public int OrderByColumn { get; set; }
}
public class Table2
{
public int Table1Id { get; set; }
public string FieldValue { get; set; }
public int Table2WhereColumn1 { get; set; }
public int Table2WhereColumn2 { get; set; }
}
Now the query in C# should be:
var query =
from t1 in Table1
join t2 in Table2 on t1.Table1Id equals t2.Table1Id into _Table2
from _t2 in _Table2.DefaultIfEmpty()
where _t2 == null ? true :
_t2.Table2WhereColumn1 == #someId
&& _t2.Table2WhereColumn2 == #someOtherId
orderby t1.OrderByColumn
select new
{
t1.Table1Id,
t1.FieldDescription,
FieldValue = _t2 == null ? "" : _t2.FieldValue,
};
And the translation into VB.NET:
Dim query = _
From t1 In Table1 _
Group Join t2 In Table2 On t1.Table1Id Equals t2.Table1Id Into _Table2 = Group _
From _t2 In _Table2.DefaultIfEmpty() _
Where If(_t2 Is Nothing, True, _t2.Table2WhereColumn1 = someId AndAlso _
_t2.Table2WhereColumn2 = someOtherId) _
Order By t1.OrderByColumn _
Select New With { _
.Table1Id = t1.Table1Id, _
.FieldDescription = t1.FieldDescription, _
.FieldValue = If(_t2 Is Nothing, "", _t2.FieldValue) _
}
Let me know if they work. Fingers crossed. :-)
Personally if there are where conditions for the right hand side of a left join I generally prefer to put them into the join criteria
In this case the SQL would look like:
SELECT t1.Table1Id,
t1.FieldDescription,
t2.FieldValue
FROM Table1 t1 WITH (NOLOCK)
LEFT JOIN Table2 t2 WITH (NOLOCK) ON t1.Table1Id = t2.Table1Id
AND t2.Table2WhereColumn1 = #someId
AND t2.Table2WhereColumn2 = #someOtherId
ORDER BY t1.OrderByColumn
The LINQ code for this (in C#) would look like:
var query =
from t1 in Table1
join t2 in Table2 on new{a = t1.Table1Id, b = someId, c = someotherId}
equals new {a = t2.Table1Id b = t2.Table2WhereColumn1, c = Table2WhereColumn2}
into _Table2
from _t2 in _Table2.DefaultIfEmpty()
orderby t1.OrderByColumn
select new
{
t1.Table1Id,
t1.FieldDescription,
FieldValue = _t2 == null ? "" : _t2.FieldValue,
};
not tested it - but should work
I won't take credit for this answer but it's gorgeous: LINQ to SQL - Left Outer Join with multiple join conditions
Essentially, use extension method where clause on the subquery but you must use it before DefaultIfEmpty():
from p in context.Periods
join f in context.Facts on p.id equals f.periodid into fg
from fgi in fg.Where(f => f.otherid == 17).DefaultIfEmpty()
where p.companyid == 100
select f.value
Related
In this VB.NET query, how can I get access to the fields in the new statement?
Dim query =
From t1 In tbl1
Join t2 In tbl2 On t1.CAMPAIGNID Equals t2.CAMPAIGNID
Group By t1.CAMPAIGNID Into Group
Select New With {
.id = CAMPAIGNID,
.CALLS = Group.Sum(Function(a) t2.CALLS),
.count = Group.Count(Function(a) t1.TERMCD = "Refused")
}
You can access the fields via the passed in parameter a. The parameter a is of the anonymous type you created via New With {...}
Dim query =
From t1 In tbl1
Join t2 In tbl2 On t1.CAMPAIGNID Equals t2.CAMPAIGNID
Group By t1.CAMPAIGNID Into Group
Select New With {
.id = CAMPAIGNID,
.CALLS = Group.Sum(Function(a) a.t2.CALLS),
.count = Group.Count(Function(a) a.t1.TERMCD = "Refused")
}
Dim ID_Section as Int32 = 10
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section On CInt(Book1.ID_Section) Equals Section.ID_section _
And Section.ID_section Equals (ID_Section) Into Section_join = Group
From Section In Section_join.DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
The error appears in the variable id_Section, since the Linq does not accept values from the outside, as it seems to me of course.
Here Error :
And Section.ID_section Equals (ID_Section)
In SQL Query Use At :
Declare #ID_Section int
SELECT Book.ID_Book, Book.Name_Book, Section.ID_section, Section.Name_Section
FROM Book LEFT OUTER JOIN
Section ON Book.ID_Section = Section.ID_section and Section.ID_section = #ID_Section
where Book.ID_Book =1
Using Where on db.Section with lambda syntax:
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section.Where(Function(s) s.ID_section = ID_Section)
On CInt(Book1.ID_Section) Equals Section.ID_section _
Into Section_join = Group
From Section In Section_join.DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
Alternatively you can apply the Where to the Join results:
Dim Query = From Book1 In db.Book1
Group Join Section In db.Section
On CInt(Book1.ID_Section) Equals Section.ID_section _
Into Section_join = Group
From Section In Section_join.Where(Function(s) s.ID_section = ID_Section).DefaultIfEmpty()
Select
Book1.ID_Book,
Book1.Name_Book,
ID_section = Section.ID_section,
Name_Section = Section.Name_Section
I'm trying to join a table to itself using a group join and VB.NET. The code below works and sorts the outer (parent) rows but I'd like to guarantee the sequence of the inner (child) rows:
Dim queryEthnicities = From aParentEthnicity In edata.Ethnicity _
Group Join aChildEthnicity In edata.Ethnicity On aChildEthnicity.ParentID Equals aParentEthnicity.EthnicityID Into EthnicityList = Group _
Where aParentEthnicity.RoundID.Equals(aRoundID) Order By aParentEthnicity.Sort
I found something similar for C# at Ordering inner keysource in simple/unnamed C# LINQ group join
but haven't found a way to do this in VB - grateful for any ideas.
Here's what you need to provide when asking a question:
Public Class edata
Public Ethnicity As Ethnicity() = _
{ _
New Ethnicity() With { .EthnicityID = 4, .ParentId = 1, .RoundID = 3, .Sort = 8 }, _
New Ethnicity() With { .EthnicityID = 3, .ParentId = 1, .RoundID = 4, .Sort = 5 }, _
New Ethnicity() With { .EthnicityID = 1, .RoundID = 2 } _
}
End Class
Public Class Ethnicity
Public ParentID As Integer
Public EthnicityID As Integer
Public RoundID As Integer
Public Sort As Integer
End Class
Sub Main
Dim edata = New edata()
Dim aRoundID = 2
Dim queryEthnicities = _
From aParentEthnicity In edata.Ethnicity _
Group Join aChildEthnicity In edata.Ethnicity On aChildEthnicity.ParentID Equals aParentEthnicity.EthnicityID Into EthnicityList = Group _
Where aParentEthnicity.RoundID.Equals(aRoundID) _
Order By aParentEthnicity.Sort
End Sub
When this code runs it produces:
To ensure the order of the inner data you need to change the query like so:
Dim queryEthnicities = _
From aParentEthnicity In edata.Ethnicity _
Group Join aChildEthnicity In edata.Ethnicity.OrderBy(Function (x) x.Sort) On aChildEthnicity.ParentID Equals aParentEthnicity.EthnicityID Into EthnicityList = Group _
Where aParentEthnicity.RoundID.Equals(aRoundID) _
Order By aParentEthnicity.Sort
Now it produces:
I can't figure out that linq to entity query syntax. My problem is that if the value of the Calls table is null then noting comes up, I want to make something like a left join to get 'all' rows from the Calls table.
I tried to group it but I can't figure out the correct way to write it.
Dim TicketQuery As ObjectQuery = From c In EnData.Customer _
Join t In EnData.Calls On t.CustomerID Equals c.CustomerID _
Join Status In EnData.Lists On t.Status Equals Status.ListValue _
Join Project In EnData.Lists On t.Project Equals Project.ListValue _
Join Priorty In EnData.Lists On t.Priority Equals Priorty.ListValue _
Where c.Status > -1 And t.Status > -1 And Status.ListType = 1 And Project.ListType = 3 And Priorty.ListType = 2 _
Select New With {c.CustName, t.CallID, t.CallDate, t.CallTime, t.Description, Key .Status = Status.ListText, Key .Project = Project.ListText, t.DateModified, Key .Priority = Priorty.ListText}
How can I fix that?
Similar question: Linq to Sql: Multiple left outer joins
Microsoft Documentation: http://msdn.microsoft.com/en-us/library/bb918093.aspx#Y916
LINQ Examples from: http://msdn.microsoft.com/en-us/vbasic/bb737909
Left Outer Join
A so-called outer join can be expressed with a group join. A left outer joinis like a cross join, except that all the left hand side elements get included at least once, even if they don't match any right hand side elements. Note how Vegetables shows up in the output even though it has no matching products.
Public Sub Linq105()
Dim categories() = {"Beverages", "Condiments", "Vegetables", "Dairy Products", "Seafood"}
Dim productList = GetProductList()
Dim query = From c In categories _
Group Join p In productList On c Equals p.Category Into Group _
From p In Group.DefaultIfEmpty() _
Select Category = c, ProductName = If(p Is Nothing, "(No products)", p.ProductName)
For Each v In query
Console.WriteLine(v.ProductName + ": " + v.Category)
Next
End Sub
For left join in VB.net we can use Let
Dim q =
(From item In _userProfileRepository.Table
Let Country = (From p In _countryRepository.Table Where p.CountryId = item.CurrentLocationCountry Select p.Name).FirstOrDefault
Let State = (From p In _stateRepository.Table Where p.CountryId = item.CurrentLocationCountry Select p.Name).FirstOrDefault
Let City = (From p In _stateRepository.Table Where p.CountryId = item.CurrentLocationCountry Select p.Name).FirstOrDefault
Where item.UserId = item.ProfileId.ToString)
After the left join we can use group by
Dim q2 =
(From p In q Group p By p.Country, p.State, p.City Into Group
Select New With
{
.Country = Country,
.State = State,
.City = City,
.Count = Group.Count
}).ToList()
How can i rewrite the below SQL query to its equivalent LINQ 2 SQL expression (both in C# and VB.NET)
SELECT t1.itemnmbr, t1.locncode,t1.bin,t2.Total
FROM IV00200 t1 (NOLOCK)
INNER JOIN
IV00112 t2 (NOLOCK)
ON t1.itemnmbr = t2.itemnmbr
AND t1.bin = t2.bin
AND t1.bin = 'MU7I336A80'
EDIT: I noticed you asked for both C# and VB.Net, so I added a VB example.
C#
using (var txn = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadUncommitted
}
))
{
var results = from t1 in db.IV00200
join t2 in db.IV00112
on new { t1.itemnmbr, t1.bin }
equals new { t2.itemnmbr, t2.bin }
where t1.bin == 'MU7I336A80'
select new {t1.itemnmbr, t1.locncode, t1.bin, t2.Total};
// Do something with your results
}
VB.Net
Dim results
Dim transOptions = New TransactionOptions
transOptions.IsolationLevel = IsolationLevel.ReadUncommitted
Using txn As New TransactionScope(TransactionScopeOption.Required, transOptions)
results = From t1 In db.IV00200 _
Join t2 In db.IV00112 _
On New With {.itemnmbr = t1.itemnmbr, .bin = t1.bin} _
Equals New With {.itemnmbr = t2.itemnmbr, .bin = t2.bin} _
Where t1.bin = "MU7I336A80" _
Select New With {.itemnmbr = t1.itemnmbr, .locncode = t1.locncode, .bin = t1.bin, .Total = t2.Total}
' Do something with your results
End Using
You will need to add System.Transactions to your project's references.
The TransactionScope block recreates the nolock conditions in your original query.
[TransactionScope / NoLock Source: http://madprops.org/blog/linq-to-sql-and-nolock-hints/]