2

LINQをうまく利用してLEFTOUTERJOINをいくつかのインスタンスで実行するアプリケーションがあります。ただし、1つのケースでは、期待どおりに機能しません。

LINQPadでのテスト(LINQ-to_SQLを使用)では、正しい結果が得られました。ただし、確実にLINQPadベータバージョン4.42.05に変更し、アプリケーションのDLLとweb.configファイルのconnectionStringを使用して正常に接続しました([接続の追加]ダイアログのとおり)。この場合も、LINQPadは適切な結果を返すことに成功し、TSQLで期待される左外部結合を明確に生成しますが、アプリケーションの同じコードは失敗します。

関数のデバッグ中に、「オブジェクト参照がオブジェクトのインスタンスに設定されていません」というメッセージが表示されます。エラー。次のコードと関連するTSQLの後に追加の説明を参照してください。この関係には、1つ以上の店舗を持つ顧客と、0以上の部門を持つwhoesストアが含まれることに注意してください。したがって、返されるレコードの中には部門がないものもあります(したがって、左外側の結合が必要です)。

次のコードはLINQPadで完全に機能します。

var model = (from h in SalesOrderHeaders
        join c in Customers on h.CustomerId equals c.CustomerId
        join s in Stores on h.StoreId equals s.StoreId
        join d in Departments on h.DepartmentId equals d.DepartmentId into outer
        from o in outer.DefaultIfEmpty()
        select new 
        {
            OrderId = h.SalesOrderHeaderId,
            OrderDetailId = 1,
            SalesOrderDate = h.SalesOrderDate,
            DeliveryDateTime = h.DeliveryDateTime,
            Customer = c.Customer,
            Store = s.Store,
            Department = (o.Department == null) ? "None" : o.Department,
            FullDescription = "None",
            Qty = 0,
            UoM = "None",
        }).OrderBy (m => m.OrderId);

以下のコードをアプリケーションで使用すると、失敗します。

var model = from h in headers
        join c in customers on h.CustomerId equals c.CustomerId
        join s in stores on h.StoreId equals s.StoreId
        join d in departments on h.DepartmentId equals d.DepartmentId into outer
        from o in outer.DefaultIfEmpty()
        select new  SalesOrderGridViewModel
        {
            OrderId = h.SalesOrderHeaderId,
            OrderDetailId = 1,
            SalesOrderDate = h.SalesOrderDate,
            DeliveryDateTime = h.DeliveryDateTime,
            Customer = c.Name,
            Store = s.Name,
            Department = (o.Name == null) ? "None" : o.Name,
            FullDescription = "None",
            Qty = 0,
            UoM = "None",
        };

ただし、次のコードのように、結果の部門フィールドの割り当てのブール値がヘッダー変数(h.DepartmentId == null)のjoin要素を参照するようにアプリケーションのコードを変更すると、次のようになります。

var model = from h in headers
        join c in customers on h.CustomerId equals c.CustomerId
        join s in stores on h.StoreId equals s.StoreId
        join d in departments on h.DepartmentId equals d.DepartmentId into outer
        from o in outer.DefaultIfEmpty()
        select new  SalesOrderGridViewModel
        {
            OrderId = h.SalesOrderHeaderId,
            OrderDetailId = 1,
            SalesOrderDate = h.SalesOrderDate,
            DeliveryDateTime = h.DeliveryDateTime,
            Customer = c.Name,
            Store = s.Name,
            Department = (h.DepartmentId == null) ? "None" : o.Name,
            FullDescription = "None",
            Qty = 0,
            UoM = "None",
        };

期待される結果が返されます。

興味深いことに、元のコードから最初に生成されたTSQLの微妙な違いは次のとおりです。

SELECT [t4].[SalesOrderHeaderId] AS [OrderId], [t4].[SalesOrderDate], 
   [t4].[DeliveryDateTime], [t4].[Customer], [t4].[Store], 
   [t4].[value] AS [Department]
FROM (
    SELECT [t0].[SalesOrderHeaderId], [t0].[SalesOrderDate], 
       [t0].[DeliveryDateTime], [t1].[Customer], [t2].[Store], 
        (CASE 
            WHEN [t3].[Department] IS NOT NULL THEN [t3].[Department]
            ELSE CONVERT(NVarChar(50),@p0)
         END) AS [value]
    FROM [SalesOrderHeaders] AS [t0]
    INNER JOIN [Customers] AS [t1] ON [t0].[CustomerId] = [t1].[CustomerId]
    INNER JOIN [Stores] AS [t2] ON [t0].[StoreId] = ([t2].[StoreId])
    LEFT OUTER JOIN [Departments] AS [t3] 
      ON [t0].[DepartmentId] = ([t3].[DepartmentId])) AS [t4]
ORDER BY [t4].[SalesOrderHeaderId]

そして、元のヘッダーテーブルのDepartmentIdの値をテストするためにブール値が変更された改訂コード([t3]。[Department]と[t0]。[DepartmentId])から、解決策のように見えます。

SELECT [t4].[SalesOrderHeaderId] AS [OrderId], [t4].[SalesOrderDate], 
   [t4].[DeliveryDateTime], [t4].[Customer], [t4].[Store], 
   [t4].[value] AS [Department]
FROM (
    SELECT [t0].[SalesOrderHeaderId], [t0].[SalesOrderDate], 
       [t0].[DeliveryDateTime], [t1].[Customer], [t2].[Store], 
        (CASE 
            WHEN [t0].[DepartmentId] IS NOT NULL THEN [t3].[Department]
            ELSE CONVERT(NVarChar(50),@p0)
         END) AS [value]
    FROM [SalesOrderHeaders] AS [t0]
    INNER JOIN [Customers] AS [t1] ON [t0].[CustomerId] = [t1].[CustomerId]
    INNER JOIN [Stores] AS [t2] ON [t0].[StoreId] = ([t2].[StoreId])
    LEFT OUTER JOIN [Departments] AS [t3] 
      ON [t0].[DepartmentId] = ([t3].[DepartmentId])) AS [t4]
ORDER BY [t4].[SalesOrderHeaderId]

私はこれを機能させる方法を見つけましたが、LINQPadでも、アプリケーション全体に散在する他の多数のLINQクエリでも正常に機能するため、この1つの場所での元の形式での失敗が懸念されます。

最終的に、左外部結合の戻り値をテストすると、アプリケーションで失敗するように見えます。ただし、これは多くの本や記事で文書化された慣行です。だから私の最後の質問は、なぜこれが起こるのか、そして/またはそれがLINQPadで(アプリケーションDLLを使用して同じDBに対して)どのように機能するのかについて誰かが洞察を持っているかどうかです。

4

2 に答える 2

2

これは、LINQ クエリを作成しない方法の典型的な例です。つまり、SQL で考えてから LINQ に音訳します。

LINQ を使用すると、結合を完全に回避し、次のようにクエリを作成できます。

from h in SalesOrderHeaders
orderby h.OrderId
select new
{
    OrderId = h.SalesOrderHeaderId,
    OrderDetailId = 1,
    SalesOrderDate = h.SalesOrderDate,
    DeliveryDateTime = h.DeliveryDateTime,
    c.Customer.Customer,
    s.Store.Store,
    Department = (h.Department == null) ? "None" : h.Department.Department,
    FullDescription = "None",
    Qty = 0,
    UoM = "None"
}
于 2012-07-07T00:43:35.767 に答える
0

[t0].DepartmentID (SalesOrderHeaders から) が null になる可能性があるようです。LEFT OUTER JOIN はこの値に依存しています。これにより、 [t3].DepartmentID が null と比較されます (場合によっては)。

JOIN の選択からも CASE ステートメントを使用する必要がある場合があります。

于 2012-07-06T16:58:28.877 に答える