###前提・実現したいこと
SQL (SQL Server) で書く際、LEFT JOIN の結合条件に テーブル同士の値で大小比較 を入れているケースがあるのですが、
これを LINQ to Entities (VB.NET) で書きたいです。
sql
1SELECT * 2FROM MAIN m 3LEFT JOIN AAA a ON ( 4 a.ID = m.ID 5 AND a.IsDeleted = 0 6 AND a.BeginYmd <= m.Ymd AND m.Ymd <= a.EndYmd -- ★こういう条件 7)
###発生している問題
LEFT JOIN の結合条件に「テーブル同士の値で大小比較」を指定する方法が分かりません。
###試したソースコード
以下の書き方だと、★の部分で
'Equals' の両辺で、少なくとも 1 つの範囲変数を参照しなければなりません。
'Equals' 演算子の一方で 1 つ以上の範囲変数 'm' を参照し、他方で 1 つ以上の範囲変数 'a' を参照する必要があります。
というエラーになります。
VBnet
1Dim query1 = ( 2 From m In dbContext.MAIN 3 Group Join a In dbContext.AAA On New With { 4 m.ID, 5 .IsDeleted = CType(0, Integer), 6 .CheckYmd = True 7 } Equals New With { 8 a.ID, 9 a.IsDeleted, 10 .CheckYmd = (a.BeginYmd <= m.Ymd AndAlso m.Ymd <= a.EndYmd) ← ★ m.*** でコンパイルエラー 11 } 12 Into _a = Group From a In _a.DefaultIfEmpty() 13 Select New With { 14 .ID = m.ID, 15 .StoreId = a.StoreID, 16 .StartYmd = a.StartYmd, 17 .EndYmd = a.EndYmd 18 } 19)
リテラル値 (IsDeletedの部分) を指定するのと同じよう名前を付けてあげればいけるかと思ったのですが、違うようでした。
検索してみたのですが、解決方法と思われるものを見つけられませんでした。
↓ これは通る
VBnet
1 ... 2 Group Join a In dbContext.AAA On New With { 3 m.ID, 4 .IsDeleted = CType(0, Integer), 5 .CheckYmd = True 6 } Equals New With { 7 a.ID, 8 a.IsDeleted, 9 .CheckYmd = True ← ★リテラル値なら問題ない 10 } 11 ...
↓ これも通る
VBnet
1 ... 2 Group Join a In dbContext.AAA On New With { 3 m.ID, 4 .IsDeleted = CType(0, Integer), 5 .CheckYmd = True 6 } Equals New With { 7 a.ID, 8 a.IsDeleted, 9 .CheckYmd = (a.BeginYmd <> "") ← ★a の値を見るのも問題ない 10 } 11 ...
↓ これは駄目
VBnet
1 ... 2 Group Join a In dbContext.AAA On New With { 3 m.ID, 4 .IsDeleted = CType(0, Integer), 5 .CheckYmd = True 6 } Equals New With { 7 a.ID, 8 a.IsDeleted, 9 .CheckYmd = (m.Ymd <> "") ← ★ここで m を見ようとすると駄目 10 } 11 ...
↓ これは駄目
VBnet
1 ... 2 Group Join a In dbContext.AAA On New With { 3 m.ID, 4 .IsDeleted = CType(0, Integer), 5 .CheckYmd = (a.Ymd <> "") ← ★左辺と右辺を入れ替えると、逆に m はOKだが a が駄目になる 6 } Equals New With { 7 a.ID, 8 a.IsDeleted, 9 .CheckYmd = True 10 } 11 ...
なんとなく駄目なパターンはわかったのですが、どうすれば目的の条件で抽出できるのかわかりません。
###追記:IEquatable を実装してみた版(うまくいかず)
VBnet
1Public Class JoinParamMainAndAAA : Implements IEquatable(Of Object) 2 Public Property ID As String = Nothing 3 Public Property StartYmd As String = Nothing 4 Public Property EndYmd As String = Nothing 5 Public Property Ymd As String = Nothing 6 Public Enum TableTypes 7 Main 8 AAA 9 End Enum 10 Public Property TableType As TableTypes 11 12 Public Overloads Function Equals(ByVal obj As Object) As Boolean Implements IEquatable(Of Object).Equals 13 ' 想定している型でなければ抜ける 14 If obj Is Nothing OrElse obj.GetType() IsNot GetType(JoinParamMainAndAAA) Then 15 Return False 16 End If 17 Dim chk = CType(obj, JoinParamMainAndAAA) 18 19 Dim m As JoinParamMainAndAAA = Nothing 20 Dim a As JoinParamMainAndAAA = Nothing 21 Select Case(Me.TableType) 22 Case TableTypes.Main: 23 ' 自分がMainの場合は相手はAAA 24 If chk.TableType = TableTypes.AAA Then 25 m = Me 26 a = chk 27 End If 28 Case TableTypes.AAA: 29 ' 自分がAAAの場合は相手はMain 30 If chk.TableType = TableTypes.Main Then 31 m = chk 32 a = Me 33 End If 34 End Select 35 If m Is Nothing OrElse a Is Nothing Then Return False 36 37 ' 比較 38 If m.ID = a.ID AndAlso 39 a.StartYmd <= m.Ymd AndAlso m.Ymd <= a.EndYmd Then 40 Return True 41 End If 42 43 Return False 44 End Function 45 46 Public Overrides Function GetHashCode() As Integer 47 Return CType(Me.ID.GetHashCode() ^ Me.Ymd.GetHashCode() ^ Me.StartYmd.GetHashCode() ^ Me.EndYmd.GetHashCode(), Integer) 48 End Function 49End Class
VBnet
1 ... 2 Group Join a In dbContext.AAA On New JoinParamMainAndAAA With { 3 .TableType = JoinParamMainAndAAA.TableTypes.Main, 4 .ID = m.ID, 5 .Ymd = m.Ymd, 6 .StartYmd = Nothing, 7 .EndYmd = Nothing 8 } Equals New JoinParamMainAndAAA With { 9 .TableType = JoinParamMainAndAAA.TableTypes.AAA, 10 .ID = a.ID, 11 .Ymd = Nothing, 12 .StartYmd = a.StartYmd, 13 .EndYmd = a.EndYmd 14 } 15 ... 16 17 ' 不要な値にもNothingをセットしているのは、そうしないと以下のエラーが出た為 18 ' The type 'TestTest.JoinParamMainAndAAA' appears in two structurally incompatible initializations within a single LINQ to Entities query. A type can be initialized in two places in the same query, but only if the same properties are set in both places and those properties are set in the same order.
実際に発行されたSQLを確認したところ、LEFT JOIN の結合が 1 = 0 となっていました。
というかEqualsの先頭にブレイクポイントをはっても止まりませんでしたが… 実装がおかしい?
SQL
1SELECT 2 1 AS [C1], 3 [Extent1].[ID] AS [ID], 4 [Extent2].[StartYmd] AS [StartYmd], 5 [Extent2].[EndYmd] AS [EndYmd] 6 FROM [dbo].[MAIN] AS [Extent1] 7 LEFT OUTER JOIN [dbo].[AAA] AS [Extent2] ON 1 = 0
追記:StackOverflowで見つけた内容
http://stackoverflow.com/questions/10642421/using-equal-and-not-equal-in-a-linq-join
A join clause performs an equijoin. In other words, you can only base matches on the equality of two keys.
Other types of comparisons such as "greater than" or "not equals" are not supported.
join節が等価結合を実行します。 言い換えれば、2つのキーが等しいかどうかに基づいて照合を行うことができます。
「より大きい」または「等しくない」などの他のタイプの比較はサポートされていません。
上記の質問内容は LEFT JOIN AAA ON (AAA.RoleName <> 'Admin')
みたいなのはできないの?という内容で 私の問題とは少し違うのですが… 系統的には似てるかなと。
結局 単純な等価評価しかサポートしていないということでしょうか。
# EntityFramework を使う人は適用期間を見たりするSQLとかって組まないのでしょうか…?
###一応考えた方法
Where 句で見る…? というのを一応考えたのですが、他に方法がないものかと思い質問させて頂きました。
VBnet
1Dim query1 = ( 2 From m In dbContext.MAIN 3 Group Join a In dbContext.AAA On New With { 4 m.ID, 5 .IsDeleted = CType(0, Integer) 6 } Equals New With { 7 a.ID, 8 a.IsDeleted 9 } 10 Into _a = Group From a In _a.DefaultIfEmpty() 11 Where (a.ID = Nothing OrElse (a.BeginYmd <= m.Ymd AndAlso m.Ymd <= a.EndYmd)) 12 Select New With { 13 .ID = m.ID, 14 .StoreId = a.StoreID, 15 .StartYmd = a.StartYmd, 16 .EndYmd = a.EndYmd 17 } 18)
###補足情報(言語/FW/ツール等のバージョンなど)
開発環境等
Windows10
Visual Studio 2013
.NET Framework 4
VB.NET
足りない情報等ありましたらご指摘ください。
御存知の方がいらっしゃいましたらよろしくお願いします。

回答2件
あなたの回答
tips
プレビュー
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2017/04/24 03:05
2017/04/24 03:15
2017/04/24 07:44