简体   繁体   中英

EF custom query with related tables

I have a code first data model where I need to do a complex query returning some aggregate information from related tables. First my simplified model:

public class Forum 
{
    public virtual int ForumID { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Topic> Topics { get; set; }
}

[NotMapped] 
public class ForumWith : Forum 
{
    public virtual int topics { get; set; }
    public virtual int messages { get; set; }
    public virtual DateTime lastDate { get; set; }
    public virtual int lastUser { get; set; }
}

public class Topic 
{
    public virtual int TopicID { get; set; }
    public virtual string Subject { get; set; }
    public virtual int ForumID { get; set; }
    [ForeignKey("ForumID")]
    public virtual Forum Forum { get; set; }
    public virtual ICollection<Message> Messages { get; set; }
}

public class Message
{
    public virtual int MessageID { get; set; }
    public virtual int TopicID { get; set; }
    [ForeignKey("TopicID")]
    public virtual Topic Topic { get; set; }
    public virtual string Text { get; set; }
    public virtual DateTime CreatedDate { get; set; }
    public virtual User CreatedBy { get; set; }
} 

What I want to do is populate the "ForumWith" object with the number of topics, the number of messages, and thedate and user from the last created message.

My first solution to do this was using a couple of loops over the forum.topics and forum.topics.messages collection. This works just fine, but the performance is abysmal.

To fix this I've created a SQL query doing a left join on the 3 tables, with group by and sum/max aggregates. This SQL i execute with context.Database.SqlQuery(sql); and the data is populated just fine, and the performance is very good.

My questions are:

  1. Is there any way I can replace the "int lastUser" from ForumWith with an actual user object "User lastUserObject" without having to loop over the result set and populate it manualy from the context.Users.

  2. Is there a better way to do this kind of aggregations through linq/EF while keeping the performance?

SQL:

SELECT  dbo.Forum.ForumID ,
        dbo.Forum.Name ,
        COUNT(DISTINCT dbo.Topics.TopicID) AS topics,
        COUNT(DISTINCT MessageID) AS messages, 
        MAX(dbo.Messages.CreatedDate) AS lastDate, 
        (SELECT UserName FROM [User] where UserId = 
        CONVERT(INT,SUBSTRING(max(CONVERT(varchar,dbo.Messages.CreatedDate,20) + '#' + CONVERT(VARCHAR,dbo.Messages.CreatedBy_UserId,20)),21,10))) AS lastUser
FROM dbo.Forum 
    LEFT JOIN dbo.Topics ON dbo.Forum.ForumID = dbo.Topics.ForumID
    LEFT JOIN dbo.Messages ON dbo.Topics.TopicID = dbo.Messages.TopicID
GROUP BY  dbo.Forum.ForumID ,
        dbo.Forum.Name 

There are a couple Linq 2 EF ways to accomplish this. The first shown below is likely the slowest of the two, but without your schema and populated data I can't be certain. Both assume list contains the populated list of Forums (most likely will be pulled from your context, ie dbContext.Forums)

Method 1

list.Select(
            x =>
            new ForumWith
                {
                    ForumID = x.ForumID,
                    Name = x.Name,
                    topics = x.Topics.Count,
                    messages = x.Topics.SelectMany(y => y.Messages).Count(),
                    lastDate = x.Topics.SelectMany(y => y.Messages).OrderByDescending(y => y.CreatedDate).First().CreatedDate,
                    lastUser = x.Topics.SelectMany(y => y.Messages).OrderByDescending(y => y.CreatedDate).First().CreatedBy
                });

Method 2

from forum in list
let latest = forum.Topics.SelectMany(x => x.Messages).OrderByDescending(x => x.CreatedDate).First()
select
    new ForumWith
        {
            ForumID = forum.ForumID,
            Name = forum.Name,
            topics = forum.Topics.Count,
            messages = forum.Topics.SelectMany(y => y.Messages).Count(),
            lastDate = latest.CreatedDate,
            lastUser = latest.CreatedBy
        };

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM