【问题标题】:Entity Framework code first - many to many filtering实体框架代码优先 - 多对多过滤
【发布时间】:2012-04-03 07:00:40
【问题描述】:

EF 小问题。 我在 EF 中映射了多对多关系。 X..Y 因此,当我有一个 X 时,就有一个属性 X.Ys。

现在我想做的是使用 Linq 查询来获取多个 X,但我不想将所有 Y 都包含在选定的 X 中。 我希望在 Y.RegistrationDate > Date.Today 上过滤 Y。

所以当我有一个 X 并遍历 .Y 时,我只会得到未来的 Y。

更新 这有效,导致 S 具有不同的 ug,它的关系仅包含即将发生的事件。 但不要告诉我这不能简化??!!

        var t = (from ug in uof.Ugs.All().ToList()
                 from upcomingEvent in ug.Events
                 where upcomingEvent.Date >= DateTime.Today
                 select new
                 {
                     ug,
                     upcomingEvent
                 }).ToList();

        var s = (from ug in t.Select(x => x.ug).Distinct()
                 select new UG
                 {
                     Id = ug.Id,
                     Name = ug.Name,
                     Description = ug.Description,
                     WebSite = ug.WebSite,
                     Events = ug.Events.Where(x => x.Date >= DateTime.Today).ToList()
                 }).ToList();

更新2

添加的图像显示即使使用基本的上下文操作,我仍然会收到 2 个事件,当我收到 1 个事件时!

exampledebugimage

【问题讨论】:

    标签: entity-framework many-to-many


    【解决方案1】:

    EF 不支持您想要的这种方案,但是您可以这样做:

    var date = DateTime.Date;
    var query = from x in Xs                
                select new 
                {
                    X = x
                    Ys = x.Ys.Where(i = > i.RegistrationDate > date)
                }
    

    这将为您提供一组 X 及其对应的符合您条件的 Y。

    【讨论】:

    • 几乎正确......但现在我得到了几个 X 的双倍!并且在查询之后添加一个 distinct 没有帮助......这是因为 many2many,所以只有当“找到”X 尚未在结果列表中时才会发生 select new。有什么想法吗?
    • @Depechie 和 ntziolis:我认为您必须删除 from y in x.Ys 行并将 i 替换为 y。
    【解决方案2】:

    你试过了吗?:

    var query = Xs
    .Select(x => new { x, yCol = x.YCol.Where(y => y.Date >= DateTime.Today) })
    .AsEnumerable()
    .Select(x => x.x)
    .ToList();
    

    见:http://blogs.msdn.com/b/alexj/archive/2009/10/13/tip-37-how-to-do-a-conditional-include.aspx

    您使用的所有这些 .ToList 将意味着您在过滤之前从数据库加载整个表。所以要小心。

    更新:由于修复不适用于多对多

    正如 Slauma 在 cmets 中提到的,如果您要提交更改,请确保不要使用此技术,因为更改跟踪会认为您更改了集合。或者更好地确保您使用 .AsNoTracking() 无论如何都会提高性能。

    我们可以使用与上述相同的解决方案,但对于多对多来说略有不同。看这个例子:

    [TestClass]
    public class ContextTest
    {
        [TestMethod]
        public void FixupTest()
        {
            Database.SetInitializer(new DropCreateDatabaseAlways<Context>());
    
            using (var db = new Context())
            {
                db.Groups.Add(new Group
                {
                    Name = "G1",
                    Users = new List<User>{
                        new User{ Name = "M"},
                        new User{Name = "S"}
                    }
                });
    
                db.SaveChanges();
            }
    
            using (var db = new Context())
            {
                var group = db.Groups
                    .Select(g => new { g, Users = g.Users.Where(u => u.Name == "M") })
                    .AsEnumerable()
                    .Select(g => {
                        g.g.Users = g.Users.ToList();
                        return g.g;
                    })
                    .First();
    
                Assert.AreEqual(1, group.Users.Count);
            }
        }
    
    
    }
    
    
    
    public class User
    {
        public int ID { get; set; }
        public string Name { get; set; }
    
        public ICollection<Group> Groups { get; set; }
    }
    
    
    
    public class Group
    {
        public int ID { get; set; }
        public string Name { get; set; }
    
        public ICollection<User> Users { get; set; }
    }
    

    测试通过,生成的sql为:

    SELECT 
    [Project1].[ID] AS [ID], 
    [Project1].[Name] AS [Name], 
    [Project1].[C1] AS [C1], 
    [Project1].[ID1] AS [ID1], 
    [Project1].[Name1] AS [Name1]
    FROM ( SELECT 
        [Extent1].[ID] AS [ID], 
        [Extent1].[Name] AS [Name], 
        [Join1].[ID] AS [ID1], 
        [Join1].[Name] AS [Name1], 
        CASE WHEN ([Join1].[Group_ID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
        FROM  [dbo].[Groups] AS [Extent1]
        LEFT OUTER JOIN  (SELECT [Extent2].[Group_ID] AS [Group_ID], [Extent3].[ID] AS [ID], [Extent3].[Name] AS [Name]
            FROM  [dbo].[GroupUsers] AS [Extent2]
            INNER JOIN [dbo].[Users] AS [Extent3] ON [Extent3].[ID] = [Extent2].[User_ID] ) AS [Join1] ON ([Extent1].[ID] = [Join1].[Group_ID]) AND (N'Mikael' = [Join1].[Name])
    )  AS [Project1]
    ORDER BY [Project1].[ID] ASC, [Project1].[C1] ASC
    

    【讨论】:

    • 嘿,迈克,因为它是多对多,不执行 ToList() 并等待查询结束,将导致错误。这是因为多对多的循环引用。但我会试试你的选择,看看这是否仍然有效。
    • 就像我想的那样,错误:“已经有一个打开的 DataReader 与此命令关联,必须先关闭”仍然存在!这是在 y => y.Date... 部分,因为 Y 也有一个 Y.Xs 集合!
    • 我认为这是一个连接字符串问题:见这里social.msdn.microsoft.com/Forums/en-MY/adonetefx/thread/…
    • @Depeche 和 Mikael:不幸的是,这种关系修复技巧不适用于多对多关系,仅适用于一对多。因此,您的 x.Ys 实际上应该是空的。如果它包含 all 未过滤的 Y,这只能是因为延迟加载。
    • 是的,我的意思是“...复制结果...”。必须记住,如果您想要进行更改并保存它们,代码是危险的,因为 EF 的更改跟踪将 AsEnumerable() 之后的所有内容视为附加实体的更改。当您在 First() 之后直接调用 SaveChanges 时,EF 将尝试将 INSERT 写入连接表,因为它“认为”您在组和用户之间添加了 new 关系(应该导致主键约束连接表中的违规)。对于只读代码应该可以正常工作(+1)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-16
    相关资源
    最近更新 更多