【问题标题】:Issue On The Linq Query关于 Linq 查询的问题
【发布时间】:2017-08-23 04:30:57
【问题描述】:

我正在尝试用Linq 做一个非常简单的任务,但已经卡住了。这真的很可怕,但我想,最好知道使用这篇文章。我有两张桌子。一个是Products,另一个是Ratings。这是演示脚本:

CREATE TABLE [dbo].[Products](
    [ProductID] [int] IDENTITY(1,1) PRIMARY KEY,
    [ProductName] [varchar](40) NULL,
    [Price] [float] NULL,
    [Details] [varchar](max) NULL,
    [CategoryID] [int] NULL
 )

INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (1, N'Denim', 1200, NULL, 1)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (2, N'Denim 2', 220, NULL, 1)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (3, N'Pringles', 240, NULL, 2)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (4, N'Pringles 2', 260, NULL, 2)
INSERT [dbo].[Products] ([ProductID], [ProductName], [Price], [Details], [CategoryID]) VALUES (5, N'Pringles 3', 240, NULL, 2)

CREATE TABLE [dbo].[Ratings](
    [AutoId] [int] IDENTITY(1,1) PRIMARY KEY,
    [ProductId] [int] NOT NULL,
    [UserRating] [float] NOT NULL
)

INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (4, 2, 1.5)
INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (5, 4, 2.5)
INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (6, 1, 5)
INSERT [dbo].[Ratings] ([AutoId], [ProductId], [UserRating]) VALUES (7, 2, 2.5)

输出应该如下:

ProductId - ProductName - User Rating
1 - Denim - 5
2 - Denim 2 - 2
3 - Pringles - 0
4 - Pringles 2 - 2.5
5 - Pringles 3 - 0

这是一个项目的评级系统,我正在尝试使用Linq 获得上述输出。使用下面的Sql,我得到了结果:

SELECT m.ProductId, m.ProductName, ISNULL(SUM(k.UserRating) / COUNT(k.ProductId), 0) AS 'User Rating' FROM Products m
LEFT JOIN Ratings k ON k.ProductId = m.ProductID
GROUP BY m.ProductID, m.ProductName

不幸的是,使用以下Linq,我只能获得Ratings 表中存在的评级详细信息:

var con = (from c in db.Ratings
           join d in db.Products on c.ProductId equals d.ProductID into ps
           from rt in ps.DefaultIfEmpty()
           group new { c, ps } by new { rt.ProductID, rt.ProductName } into g
           select new ProductRating
           {
              ProductId = g.Key.ProductID,
              ProductName = g.Key.ProductName,
              Total = g.Sum(c => c.c.UserRating) / g.Count()
           }).ToList();

注意:我的目标是获取 Ratings 表中不存在的评分详细信息(产品评分的总和)(如果没有数据)以及存在的评级详细信息。

更新 1 - 类 ProductRating

public class ProductRating {
     public int ProductId { get; set; }
     public string ProductName { get; set; }
     public double Total { get; set; } //Property for the rating
  }

更新 2 - 类产品和评级

 public partial class Products
 {
     public int ProductID { get; set; }
     public string ProductName { get; set; }
     public Nullable<double> Price { get; set; }
     public string Details { get; set; }
     public Nullable<int> CategoryID { get; set; }
     public ICollection<Ratings> Rating { get; set; }
 }

 public partial class Ratings
 {
    public int AutoId { get; set; }
    public int ProductId { get; set; }
    public double UserRating { get; set; }
 }

使用以下Linq 查询并收到此错误 - LINQ to Entities 不支持指定的类型成员“评级”。仅支持初始化器、实体成员和实体导航属性

 var con = db.Products.Select(c => new ProductRating
           {
              ProductId = c.ProductID,
              ProductName = c.ProductName,
              Total = c.Rating.Average(d => (double?)d.UserRating) ?? 0
           }).ToList();

更新 3:现在出现此错误 - 错误 2016:为条件指定的值与成员的类型不兼容,这就是我尝试的方式导航属性:


注意:我已经修改了Andrés Robinet 的查询,尽管使用Join 仍然有效。

【问题讨论】:

  • 您在 LINQ 查询中弄乱了左外连接的左右部分。你没有导航属性,比如Product 类中的public ICollection&lt;Rating&gt; Ratings { get; set; } 吗?什么是使用的 ORM - 例如LINQ to SQL、EF6、EF Core 还是?
  • 我使用的是 EF 6.0,它有一个名为 Total 的属性。在帖子中更新。

标签: linq sql-server-2008 asp.net-mvc-5


【解决方案1】:
from c in db.Ratings
join d in db.Products on c.ProductId equals d.ProductID into ps
from rt in ps.DefaultIfEmpty()

是 SQL Ratings LEFT OUTER JOIN Products 的 LINQ 等效项,即与您的 SQL 查询完全相反。

虽然可以修改 LINQ 查询以匹配 SQL 查询,但这没有任何意义,因为 EF 提供了一种更好的方法,它完全消除了在 LINQ 查询中使用连接的需要——所谓的导航属性(请参阅@ 987654321@)。

通常Product 类会有一个集合导航属性

public ICollection<Rating> Ratings { get; set; }

表示ProductRating 之间的一对多关系,使用它的等效LINQ 查询将是:

var result = db.Products
    .Select(p => new ProductRating
    {
        ProductId = p.ProductId,
        ProductName = p.ProductName,
        Total = p.Ratings.Average(r => (double?)r.UserRating) ?? 0
    }).ToList();

更新:如果您没有在实体模型中正确设置关系,您可以通过将导航属性访问器替换为 group join 来使用与上述查询等效的手动连接:

var result = (
    from p in db.Products
    join r in db.Ratings on p.ProductId equals r.ProductId into ratings
    select new ProductRating
    {
        ProductId = p.ProductId,
        ProductName = p.ProductName,
        Total = ratings.Average(r => (double?)r.UserRating) ?? 0
    }).ToList();

【讨论】:

  • 我已经尝试过您的解决方案,它看起来很有希望@Ivan Stoev。现在,我收到此错误 - LINQ to Entities 不支持指定的类型成员“Rating”。仅支持初始化程序、实体成员和实体导航属性。 我浏览了链接并手动更新了 Product 类添加导航属性。在帖子中更新。
  • 好吧,看看你的更新 2,public ICollection&lt;Ratings&gt; Rating { get; set; } 绝对是实体导航属性,所以你不应该得到那个错误。除非它没有在 edmx(数据库优先)或流式 API/数据注释(代码优先)中正确映射。完整图片的某些部分丢失了,而您是唯一拥有它的人。
  • 我首先使用的是 EF 数据库,希望这没有任何区别。虽然我已经在Products 类中手动进行了更改,但它会有效还是需要做任何替代?
  • 虽然这种情况有所不同。您不应该手动进行,而是通过 edmx 设计器中的关系进行。
  • 我已经更新了帖子,并附上了一张我如何处理导航属性的图片。
【解决方案2】:

您必须从Product 开始。此外,Linq 表达式中的连接顺序与 SQL 中的顺序相反。而且,group ... by ... 只需要 rt.UserRating 作为 值选择器

var con = (from d in db.Products
           join c in db.Ratings on d.ProductId equals c.ProductId into ps
           from rt in ps.DefaultIfEmpty()
           group new { rt.UserRating } by new { d.ProductId, d.ProductName } into g
           select new ProductRating
           {
               ProductId = g.Key.ProductId,
               ProductName = g.Key.ProductName,
               Total = g.Sum(r => r.UserRating) / g.Count()
           })
           .ToList();

此外,如果g.Count() 为零,这将中断。但无论如何你有几个选择:

  • 为查询添加更多复杂性并祈祷 EF 能够将其转换为 SQL

    select new ProductRating
    {
        ProductName = g.Key.ProductName,
        Total = g.Count() > 0 ? (g.Sum(r => r.UserRating) / g.Count()) : 0
    }
    
  • 重新定义ProductRating 或使用帮助程序DTO,使其具有SumCount 属性(计算平均按需,假设您仍然可以拥有Total 属性)

    select new ProductRating
    {
        ProductName = g.Key.ProductName,
        SumRating = g.Sum(r => r.UserRating),
        CountRating = g.Count()
    }   
    

【讨论】:

  • 我已经尝试过您的解决方案@Andrés Robinet,它运行良好。但我只得到Ratings 表中存在的评级值。虽然试图让它发挥作用。谢谢。
【解决方案3】:

您的 LINQ 语句有一个内连接,而您的 SQL 有一个左连接。

【讨论】:

  • 是的。在Linq 中,我尝试将这个from rt in ps.DefaultIfEmpty() 用于LEFT JOIN。但是没有用。
猜你喜欢
  • 2011-07-29
  • 2011-08-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-14
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多