【问题标题】:Translating SQL group-by query to LINQ query将 SQL 分组查询转换为 LINQ 查询
【发布时间】:2017-04-12 04:10:31
【问题描述】:

我在尝试将 SQL 查询转换为 LINQ 时遇到问题。 假设,我们有如下数据结构:

class Movie
{
    public Guid ID { get; set; }

    // navigation properties
    public virtual ICollection<Commercial> Commercials { get; set; }
    public virtual ICollection<Spectator> Spectators { get; set; }
}

class Commercial
{
    public Guid ID  { get; set; }
    public Guid MovieID { get; set; }
    public string ProductType { get; set; }

    // navigation property
    public virtual Movie Movie { get; set; }
}

class Spectator
{
    public Guid ID  { get; set; }
    public Guid MovieID { get; set; }
    public int Age { get; set; }

    // navigation property
    public virtual Movie Movie { get; set; }
}

现在假设,我想知道有多少观众看到了某个产品类别的广告。在 SQL 中,它看起来像这样:

select Commercial.ProductType, count(distinct Spectator.ID)
from Spectator
join Movie on Spectator.MovieID = Movie.ID
join Commercial on Commercial.MovieID = Movie.ID
where Spectator.Age > 60 # optional filter
group by Commercial.ProductType;

首先,我尝试使用GroupBy()函数,但由于多对多关系,我没有找到按广告产品类型对观众进行分组的方法。

然后我尝试了类似的东西:

var query = db.Commercials.Where(x => x.Age > 60).GroupJoin(
    db.Spectators,
    c => c.MovieID,
    s => s.MovieID,
    (c, g) => new { ProductType = c.ProductType, Count = g.Distinct().Count() });

这看起来很有希望,但没有返回预期的结果。

【问题讨论】:

  • 对不起。我的第一个示例过于简单,甚至与我实际遇到的问题不匹配。我希望我的新示例包含所需的所有相关信息。

标签: c# sql-server linq group-by


【解决方案1】:

您拥有所有这些不错的导航属性,因此您无需加入 LINQ。导航属性可以被视为硬编码连接,它可以防止重复、冗长和容易出错的代码(例如,使用错误的连接属性)。

知道了这一点,你就可以考虑用查询来获取你的数据了。它并不像看起来那么微不足道(和我最初想的那样)。

一部电影中可能有 n 广告,因此如果您只计算每部电影和广告的观众人数,则结果太高(n 乘以观众人数)。您必须计算独特的观众。这些计数应按ProductType 分组。这将我们带到了这个查询:

var query = from c in db.Commercials
            group c by c.ProductType into cgroup
            select new
            {
                ProductType = cgroup.Key,
                NumberOfSpectators = cgroup.SelectMany(c => c.Movie.Spectators
                     .Where(s => s.Age > 60)
                     .Select(s => s.Id)).Distinct()).Count()
            };

【讨论】:

  • 这不会为同一产品类型的两个广告创建两个结果吗?
【解决方案2】:

我做了小样本。

public class Letter
{
    public int Id { get; set; }
    public int SenderId { get; set; }
}
public class Sender
{
    public int Id { get; set; }
    public string Country { get; set; }
}
public class Receiver
{
    public int Id { get; set; }
    public int LetterId { get; set; }
    public string Country { get; set; }
}
class StackOverflow_SQLtoLinq
{
    static void Main(string[] args)
    {
        List<Letter> lstLetters = new List<Letter>() { 
        new Letter(){Id=1,SenderId=1},
        new Letter(){Id=2,SenderId=2},
        new Letter(){Id=3,SenderId=3}
        };

        List<Sender> lstSenders = new List<Sender>() {
        new Sender(){Id=1,Country="IND"}, 
        new Sender(){Id=2,Country="US"},  
        new Sender(){Id=3,Country="NZ"}
        };

        List<Receiver> lstReceivers = new List<Receiver>() { 
        new Receiver(){Id=1,LetterId=1,Country="IND"},
        new Receiver(){Id=2,LetterId=11,Country="US"},
        new Receiver(){Id=3,LetterId=1,Country="NZ"},
        };

        var data = (from receiver in lstReceivers
                   join letter in lstLetters on receiver.LetterId equals letter.Id 
                   join sender in lstSenders on letter.SenderId equals sender.Id
                    group sender by new { id = sender.Id, country = sender.Country } into finalData
                   select new
                   {
                       country = finalData.Key.country,
                       Count = finalData.Distinct().Count()
                   }).ToList();

    }
}

终于在 data 变量中有一个数据。

【讨论】:

  • 对不起!我试图尽可能简化我遇到的问题。但我意识到,我过于简单化了。在我的第一个示例中,一封信可能有多个发件人,这没有任何意义。我试图找到另一个更合适的例子。无论如何,谢谢你的回答!
【解决方案3】:

你的数据库之间有关系吗? 如果是,您的实体框架生成的模型必须是这样的:

 public class Receiver
    {
        public int LetterId { set; get; }
        public Letter Letter { set; get; }
        public Country Country { set; get; }
    }
    public class Letter
    {
        public int Id { set; get; }
        public int SenderId { set; get; }
        public Sender Sender { set; get; }
        public IEnumerable<Receiver> Receivers { set; get; }

    }
    public class Sender
    {
        public int Id { set; get; }
        public Country Country { set; get; }
        public IEnumerable<Letter> Letters { set; get; }
    }
    public class Country
    {
        public int Id { set; get; }
    }

那么你的上下文中有 3 种类型:

IEnumerable<Receiver> receivers = new List<Receiver>();
IEnumerable<Letter> leters = new List<Letter>();
IEnumerable<Sender> senders = new List<Sender>();

所以你的回答是这样的:

var results = from receiver in receivers
        from letter in leters
        from sender in senders
        where receiver.LetterId == letter.Id &&
              sender.Id == letter.SenderId
        select
            new Result
            {
                Country = sender.Country,
                CountOfCountry = sender.Letters.Select(x => x.Receivers).Distinct().Count()
            };

ResultClass 是:

public class Result
    {
        public Country Country { set; get; }
        public int CountOfCountry{ set; get; }
    }

如果你把你的类图我可以帮你更好!

【讨论】:

  • 对不起!我试图尽可能简化我遇到的问题。但我意识到,我过于简单化了。在我的第一个示例中,一封信可能有多个发件人,这没有任何意义。我试图找到另一个更合适的例子。无论如何,谢谢你的回答!
猜你喜欢
  • 2014-08-14
  • 1970-01-01
  • 1970-01-01
  • 2015-11-29
  • 1970-01-01
  • 1970-01-01
  • 2012-02-17
  • 2017-02-23
相关资源
最近更新 更多