1

1対多の関係を照会しようとしていますが、これを行う方法がわかりません。私が抱えている問題は、フィルタリングしたいフィールドのIDが(メインテーブルではなく)結合テーブルに存在することです...

説明するよりも説明する方がおそらく簡単です!!

私が持っている2つのクラスは

public class DbUserClient
{
    public virtual string UserId { get; set; }
    public virtual int ClientId { get; set; }
    public virtual DateTime AssignedOn { get; set; }
    public virtual DateTime? ClearedOn { get; set; }

    // navigation properties
    public virtual DbUser User { get; set; }
    public virtual DbClient Client { get; set; }
}

public class DbClient
{
    public virtual int ClientId {get;set;}
    public virtual string EntityName { get; set; }
    public virtual bool Deleted { get; set; }

    // navigation properties
    public ICollection<DbUserClient> UserClients { get; set; }
}

プログラムには、クライアントを公開するリポジトリがあります。

    public ObservableCollection<DbClient> Clients
    {
        get { return context.Clients.Local; }
    }

私はこれに拘束されています。これが私の「ローカル」コレクションを更新するため、クライアントを介したクエリに熱心な理由です。ただし、UserClientsを含めたり、「where」句を追加したりする方法がわからないようです。

私は次のようなものを試しました

context.Clients.Include(c => c.UserClients.Where(uc => uc.UserId == "ME"));

ただし、これにより、次の例外が発生します。「インクルードパス式は、タイプで定義されたナビゲーションプロパティを参照する必要があります。参照ナビゲーションプロパティには点線のパスを使用し、コレクションナビゲーションプロパティには選択演算子を使用します。パラメーター名:パス

これは機能しますが、残念ながら「ローカル」コレクションは更新されません

from c in context.Clients
from uc in c.UserClients
where uc.ClientId == uc.ClientId && uc.UserId == "ME"
select new { c.ClientId, c.EntityName, uc.AssignedOn };

私がどこで間違っているかについての提案はありますか?

乾杯腹筋

編集I: SQLプロファイラーを見ると、上記のクエリは次のSQLを生成します

SELECT 
[Extent1].[ClientId] AS [ClientId], 
[Extent1].[EntityName] AS [EntityName], 
[Extent2].[AssignedOn] AS [AssignedOn]
FROM  [dbo].[Client] AS [Extent1]
INNER JOIN [dbo].[UserClient] AS [Extent2] ON [Extent1].[ClientId] = [Extent2].  [ClientId]
WHERE ([Extent2].[ClientId] = [Extent2].[ClientId]) AND (N'ME' = [Extent2].[UserId])

これは非常に単純で、SQLを手作りした場合に自分で書いたものとほぼ同じです。

ただし、以下の推奨される式は機能しますが、ご指摘のとおり、ローカルキャッシュにデータが入力されます

context.Clients
  .Where(c => c.UserClients.Any(uc => uc.UserId == userId))
  .Select(c => new { DbClient = c, DbUser = c.UserClients.Where(uc => uc.UserId == userId).FirstOrDefault() }).ToList();

次のSQLを生成します。これは必要以上に複雑に見え、パフォーマンスに影響があると思います

exec sp_executesql N'SELECT 
[Filter2].[ClientId] AS [ClientId], 
[Filter2].[EntityName] AS [EntityName], 
[Filter2].[Deleted] AS [Deleted], 
[Limit1].[UserId] AS [UserId], 
[Limit1].[ClientId] AS [ClientId1], 
[Limit1].[AssignedOn] AS [AssignedOn], 
[Limit1].[ClearedOn] AS [ClearedOn]
FROM   (SELECT [Extent1].[ClientId] AS [ClientId], [Extent1].[EntityName] AS [EntityName], [Extent1].[Deleted] AS [Deleted] 
    FROM [dbo].[Client] AS [Extent1]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM [dbo].[UserClient] AS [Extent2]
        WHERE ([Extent1].[ClientId] = [Extent2].[ClientId]) AND ([Extent2].[UserId] = @p__linq__0)
    ) ) AS [Filter2]
OUTER APPLY  (SELECT TOP (1) 
    [Extent3].[UserId] AS [UserId], 
    [Extent3].[ClientId] AS [ClientId], 
    [Extent3].[AssignedOn] AS [AssignedOn], 
    [Extent3].[ClearedOn] AS [ClearedOn]
    FROM [dbo].[UserClient] AS [Extent3]
    WHERE ([Filter2].[ClientId] = [Extent3].[ClientId]) AND ([Extent3].[UserId] = @p__linq__1) ) AS [Limit1]',N'@p__linq__0 nvarchar(4000),@p__linq__1 nvarchar(4000)',@p__linq__0=N'ME',@p__linq__1=N'ME'  

編集II:もう少し遊んだ後、私は自分の要件を満たしているように見える解決策を見つけました。SQLプロファイラーを見ると、生成されたSQLに満足しています。これは私の元のクエリのそれに似ています。

exec sp_executesql N'SELECT 
[Extent1].[ClientId] AS [ClientId], 
[Extent1].[EntityName] AS [EntityName], 
[Extent1].[Deleted] AS [Deleted], 
[Extent2].[UserId] AS [UserId], 
[Extent2].[ClientId] AS [ClientId1], 
[Extent2].[AssignedOn] AS [AssignedOn], 
[Extent2].[ClearedOn] AS [ClearedOn]
FROM  [dbo].[Client] AS [Extent1]
INNER JOIN [dbo].[UserClient] AS [Extent2] ON [Extent1].[ClientId] = [Extent2].[ClientId]
WHERE [Extent2].[UserId] = @p__linq__0',N'@p__linq__0 nvarchar(4000)',@p__linq__0=N'ME'

ここでは遅延読み込みはないと想定しています。誰かが確認できれば私は感謝します

context.Clients.Join
  (
    context.UserClients, 
    c => c.ClientId, 
    uc => uc.ClientId, 
    (user, usrclient) => new { DbClient = user, DbUserClient = usrclient }
  ).Where(uc => uc.DbUserClient.UserId == userId).Load();
4

1 に答える 1

0

UserId="ME"で少なくとも1人のユーザーがいるクライアントをロードできます。

var clients = context.Clients
    .Where(c => c.UserClients.Any(uc => uc.UserId == "ME"))
    .ToList();

これにより正しいクライアントがロードされますが、ユーザーは含まれません。

ユーザーを含めると...

var clients = context.Clients.Include(c => c.UserClients)
    .Where(c => c.UserClients.Any(uc => uc.UserId == "ME"))
    .ToList();

...正しくフィルタリングされたクライアントを取得しますが、ユーザー「ME」だけでなく、すべてのユーザーが含まれます。

ユーザーをフィルタリングするためには、最後のアプローチであるプロジェクションが最善の方法です。

var clientsWithUser = context.Clients
    .Where(c => c.UserClients.Any(uc => uc.UserId == "ME"))
    .Select(c => new
    {
        Client = c,
        User = c.UserClients.Where(uc => uc.UserId == "ME").FirstOrDefault()
    })
    .ToList();

匿名オブジェクトリストに完全なエンティティ(および)をLocalロードしているため、これによってコレクションも更新されます。ClientUser

編集

質問の最後のクエリは問題ありませんが、ナビゲーションプロパティがある場合、手動で結合を作成するEFの方法ではありません。SQLとクエリの結果は、ほとんどの場合、次のものと同じです。

context.UserClients.Include(uc => uc.Client)
    .Where(uc => uc.UserId == userId)
    .Load();

このクエリのは、手書きのLINQが生成するものIncludeと同じに変換される必要があります。INNER JOINJoin

于 2012-06-27T18:05:56.213 に答える