Entity Framework中的条件Include()

45

我看到了一些类似问题的答案,但是我似乎无法找出如何将答案应用于我的问题。

var allposts = _context.Posts
            .Include(p => p.Comments)
            .Include(aa => aa.Attachments)
            .Include(a => a.PostAuthor)
            .Where(t => t.PostAuthor.Id == postAuthorId).ToList();

附件可以由作者(类型为Author)或贡献者(类型为Contributor)上传。我想做的是,只获取附件所有者类型为Author的附件。

我知道这样做不起作用并会出错:

.Include(s=>aa.Attachments.Where(o=>o.Owner is Author))

我在这里读到了关于“Filtered Projection”的内容。

编辑-文章链接:http://blogs.msdn.com/b/alexj/archive/2009/10/13/tip-37-how-to-do-a-conditional-include.aspx

但我就是无法理解它。

我不想将筛选器包含在最终的where子句中,因为我想要所有帖子,但我只想检索属于作者的那些帖子的附件。

编辑2:-请求发布架构

public abstract class Post : IPostable
{

    [Key]
    public int Id { get; set; }

    [Required]
    public DateTime PublishDate { get; set; }

    [Required]
    public String Title { get; set; }

    [Required]
    public String Description { get; set; }

    public Person PostAuthor { get; set; }
    public virtual ICollection<Attachment> Attachments { get; set; }
    public List<Comment> Comments { get; set; }
}

请问您能展示一下“Posts”模式吗? - Sateesh Pagolu
@DarkKnight - 请查看编辑 - grayson
@grayson,你想要做的事情是不可能的。Linq2Sql会将你的代码转换成原始的SQL,并通过连接返回子行。你无法在SQL中进行这种条件连接。你唯一的选择是删除.Include(aa => aa.Attachments),然后有第二个查询根据所有者是否为作者/贡献者返回附件。 - Rob
8个回答

57

22

从您发布的链接中,我可以确认该技巧仅适用于一对多(或多对一)关系。在这种情况下,您的帖子-附件应该是一对多关系,因此它完全适用。以下是您应该使用的查询:

//this should be disabled temporarily
_context.Configuration.LazyLoadingEnabled = false;
var allposts = _context.Posts.Where(t => t.PostAuthor.Id == postAuthorId)
                       .Select(e => new {
                           e,//for later projection
                           e.Comments,//cache Comments
                           //cache filtered Attachments
                           Attachments = e.Attachments.Where(a => a.Owner is Author),
                           e.PostAuthor//cache PostAuthor
                        })
                       .AsEnumerable()
                       .Select(e => e.e).ToList();

1
在此调用后,我需要添加“_context.Configuration.LazyLoadingEnabled = true;”以重新启用延迟加载吗? - grayson
1
@grayson 是的,如果你想重新启用它。 - Hopeless
1
@Hopeless 知道这个问题已经很老了,但我的最近的问题被标记为与此重复,我的要求是当 Attachments 有额外的子属性时,它们在结果中不可用。我的问题链接 https://stackoverflow.com/questions/58347487/unable-to-write-an-include-query-with-firstordefault-and-condition - Krtti
1
@Krtti 我不确定你的问题是否可以通过这里的回答解决,实际上我并不太理解你的问题(从你在链接问题中描述的内容来看,至少有两种理解方式)。此外,有人在那里添加了一个答案,看起来对一种理解方式是正确的。我的答案思路非常简单易懂,但当它不起作用时,可能不适用。最后,我已经很长时间没有使用EF和Linq-to-entity了,所以可能无法为你提供太多帮助。 - Hopeless
1
@Hopeless 不用担心,我的问题已经得到解答了,感谢你抽出时间来告知。 - Krtti
还有一件事,我可以在Include语句中引用那些没有定义外键但是它们具有相同列的相关表格吗?我的意思是它们是相关的表格,但实际上它们没有外键定义,我可以在Include语句中使用这些表格吗?谢谢你们所有人的支持,这真的是一个很大的帮助。 - Abdul

4

从你的Attachments导航属性中删除virtual关键字以防止懒加载:

public ICollection<Attachment> Attachments { get; set; }

第一种方法:发出两个单独的查询,一个用于帖子,一个用于附件,并让关系修复完成其余部分:

List<Post> postsWithAuthoredAttachments = _context.Posts
    .Include(p => p.Comments) 
    .Include(p => p.PostAuthor)
    .Where(p => p.PostAuthor.Id == postAuthorId)
    .ToList();

List<Attachment> filteredAttachments = _context.Attachments
    .Where(a => a.Post.PostAuthor.Id == postAuthorId)
    .Where(a => a.Owner is Author)
    .ToList()

关系修复意味着您可以通过帖子的导航属性访问这些已过滤的附件。

第二种方法:一次查询数据库,然后进行内存查询:

var query = _context.Posts
    .Include(p => p.Comments) 
    .Include(p => p.PostAuthor)
    .Where(p => p.PostAuthor.Id == postAuthorId)
    .Select(p => new 
        {
            Post = p,
            AuthoredAttachments = p.Attachments
                Where(a => a.Owner is Author)
        }
    );

我会在这里使用匿名类型。

var postsWithAuthoredAttachments = query.ToList()

我会创建一个ViewModel类来避免使用匿名类型:
List<MyDisplayTemplate> postsWithAuthoredAttachments = 
     //query as above but use new PostWithAuthoredAttachments in the Select

或者,如果您真的想拆开帖子:

List<Post> postsWithAuthoredAttachments = query.//you could "inline" this variable
    .AsEnumerable() //force the database query to run as is - pulling data into memory
    .Select(p => p) //unwrap the Posts from the in-memory results
    .ToList()

3
你可以使用这个实现的扩展方法(例如)Include2()。之后,你可以调用:
_context.Posts.Include2(post => post.Attachments.Where(a => a.OwnerId == 1))

上面的代码仅包含Attachment.OwnerId == 1的附件。


嗨,当帖子没有附件时,似乎这个不起作用。我是不是漏掉了什么? - grayson

2

针对 .NET Core

https://learn.microsoft.com/ru-ru/ef/core/querying/related-data/explicit

var allposts = _context.Posts
        .Include(p => p.Comments)
        .Include(a => a.PostAuthor)
        .Where(t => t.PostAuthor.Id == postAuthorId).ToList();

_context.Entry(allposts)
        .Collection(e => e.Attachments)
        .Query()
        .Where(e=> e.Owner is Author)
        .Load();

它会向 SQL 发起两个查询。

3
这不起作用是因为您尝试在帖子列表上使用显式加载。但是.Entry()只能用于单个实体。 - Retrogott

1
尝试这个。
var allposts = _context.Posts
        .Include(p => p.Comments)
        .Include(a => a.PostAuthor)
        .Where(t => t.PostAuthor.Id == postAuthorId).ToList();

_context.Attachments.Where(o=>o.Owner is Author).ToList();

-3

Include() 中的 Lambda 只能指向一个属性:

.Include(a => a.Attachments)
.Include(a => a.Attachments.Owner);

你的条件对我来说没有意义,因为Include()的意思是join,你要么这样做,要么不这样做。而不是有条件地。

你会如何在原始SQL中编写这个?


为什么不只是这样呢:
context.Attachments
       .Where(a => a.Owner.Id == postAuthorId &&
                   a.Owner.Type == authorType);

?


谢谢。我对这些都很新。我可以做到获取所需类型的附件,但我不知道如何将其合并回帖子中。我的 DisplayTemplate 派生自帖子并显示帖子附件。 - grayson
你会如何写原始SQL:select * from table1 x join table2 y on x.fkey=y.fkey and (condition with y) - Austin_Anderson
@Austin_Anderson:这不是条件连接,对吧? - abatishchev
3
连接操作总是有条件的,通常是基于主键进行条件连接。但是,您也可以在主键和其他条件上进行总连接。事实上,由于连接操作通常用于大型数据集,因此推荐使用连接操作。 - DiscipleMichael
@DiscipleMichael:连接(Join)从来不是有条件的,它要么呈现要么不呈现。并且执行连接可能会很昂贵。优化性能的技巧是消除你知道不会产生任何结果的连接。在 U-SQL 中,通常使用动态 SQL 来实现。但这里的问题是关于 EF 的。 - abatishchev

-5
假设“a”是“YourType”类型,条件包含可以通过使用方法扩展来解决,例如:
public static class QueryableExtensions
{
    public static IQueryable<T> ConditionalInclude<T>(this IQueryable<T> source, bool include) where T : YourType
    {
        if (include)
        {
            return source
                .Include(a => a.Attachments)
                .Include(a => a.Attachments.Owner));
        }

        return source;
    }
}

...然后就像使用.Include一样使用它,例如:

bool yourCondition;

.ConditionalInclude(yourCondition)

7
这是个笑话还是什么? - Mauro Sampietro
太聪明了..! - ReZa
这对我很有帮助! - E-A

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接