EF 4.3 Code First - 向后查询导航属性

时间:2012-06-27 15:18:01

标签: linq entity-framework-4 linq-to-entities ef-code-first

我正在尝试查询一对多关系,但无法弄清楚如何执行此操作。我遇到的问题是我希望过滤的字段的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 =&gt; c.UserClients.Where(uc =&gt; uc.UserId ==“ME”));

但是这会导致以下异常 “ Include路径表达式必须引用在类型上定义的导航属性。使用虚线路径作为参考导航属性,使用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 };

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

干杯 ABS

编辑我:查看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'  

编辑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();

1 个答案:

答案 0 :(得分: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)。

修改

您问题中的最后一个查询很好,但是当您拥有导航属性时,它并不是手动编写连接的EF方式。 SQL和查询结果很可能与:

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

此查询中的Include应转换为您手写的LINQ INNER JOIN生成的Join