1

我正在尝试查询一对多关系,但无法弄清楚如何做到这一点。我遇到的问题是我要过滤的字段的 ID 存在于连接表(不是主表)中......

它可能更容易说明而不是解释!

我的两门课是

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"));

但这会导致以下异常“包含路径表达式必须引用在类型上定义的导航属性。使用虚线路径作为引用导航属性,使用 Select 运算符作为集合导航属性。参数名称:路径

这可行,但不幸的是不会更新我的“本地”收藏

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 };

关于我哪里出错的任何建议?

干杯腹肌

编辑我:查看 SQL Profiler 上面的查询生成以下 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'  

编辑二:在玩了一些之后,我找到了一个似乎满足我要求的解决方案。查看 SQL Profiler,我对生成的 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"加载至少有一个用户的客户端:

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)。

编辑

您问题中的最后一个查询很好,尽管当您拥有导航属性时,手动编写 Join 并不是真正的 EF 方式。SQL 和查询结果很可能与以下内容相同:

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

Include查询中的 应转换为INNER JOIN您的手写 LINQJoin生成的相同。

于 2012-06-27T18:05:56.213 回答