嵌套的 foreach - 逻辑问题

nested foreach - issue with logic

在过去的 3 个小时里,这一直困扰着我……我可能只是累了,但似乎无法理解正确的逻辑。我想做的是;

  1. 获取调查主题列表和评级调查主题列表。
  2. 如果none个主题已经评分,return第一个给用户评分。
  3. 如果他们都被评级了,return 一个观点说 'yay you completed the survey'
  4. 否则确定哪些未被评级并在视图中提供它们。

所有主题一次提供 1 个,每次对主题进行评级时,都会保存,然后我将它们重定向回此控制器。

我认为我的 string.equals 无法正常工作,但似乎无法弄清楚原因。控制器只是继续提供相同的主题。 (我假设它是第一个匹配的记录与不匹配的记录?)

 public ActionResult Index(string page)
    {
        Rating rating = new Rating();

        var surveyItems = (from s in db.Surveys
                           where s.Category.Name.Equals(page)
                           select s).ToList();

        var ratedItems = (from r in db.Ratings
                          where r.Category.Equals(page) && r.UserName.Equals(User.Identity.Name)
                          select r).ToList();

        if (ratedItems.Count() == 0 && surveyItems.Count() > 0)
        {
            ViewBag.Remaining = surveyItems.Count(); 
            rating.Topic = surveyItems.Select(si => si.Topic).FirstOrDefault();
            rating.Category = page;
            return View(rating);
        }
        else if (ratedItems.Count() > 0 && ratedItems.Count() == surveyItems.Count())
        {
            return View("Finished");
        }
        else
        {
            foreach (var si in surveyItems)
            {
                foreach (var ri in ratedItems)
                {
                    if (!si.Topic.Equals(ri.Topic))
                    {

                    rating.Topic = si.Topic;
                    rating.Category = page;
                    ViewBag.Total = surveyItems.Count();
                    ViewBag.Remaining = ViewBag.Total - ratedItems.Count();
                    return View(rating);

                    }
                }
            }

        }

首先,直接回答你的问题,你的内循环总是会失败,因为 2 个列表没有排序 AND 不能保证每个列表中的第 1 项是相同的.即使是,第一个列表中的第二项也不等于第二个列表中的第一项(内循环)。

最好的办法是完全使用 LINQ 来解决这个问题,虽然查询有点难以阅读,但代码更清晰。

var rating = (from s in db.Surveys
    join r in db.Ratingson s.Topic equals r.Topic into rated
    from ri in rated.Where(x => x.Username == User.Identity.Name).DefaultIfEmpty()
    where s.Category.Name.Equals(page) && ri.Topic == null
    select new RatingViewModel {Topic = s.Topic, Category = s.Category, Total = db.SurveyItems.Count(), Rated = rated.Count()}).FirstOrDefault();

if (rating == null)
{
    return View("Finished");
}

return View(rating);

LINQ 查询本质上等同于以下 SQL(给予或接受)

SELECT * FROM Surveys s
LEFT OUTER JOIN Ratings r ON s.Topic = r.Topic AND r.Username = 'user'
WHERE r.Topic IS NULL

您还会注意到查询投射到 RatingsViewModel,我添加这个是因为我注意到您有一些对 ViewBag 以及您的 Rating 实体的引用。

RatingViewModel:

public class RatingViewModel
{
    public string Topic { get; set; }
    public string Category { get; set; }
    public int Total { get; set; }
    public int Rated { get; set; }
    public int Remaining {
        get { return Total - Rated; }
    }

}

编辑

进一步研究查询,这是我能得到的最接近的结果:

// define the where's here so we can use the IQueryable multiple times in LINQ 
var surveys = db.Surveys.Where(x => x.Category.Name.Equals(page));
var ratedItems = db.Ratings.Where(y => y.Username == User.Identity.Name && y.Category.Name.Equals(page));
var rated = ratedItems.Count(); // get the rated count here, otherwise we end up with an exception inside the LINQ query

var surveyTopic =
(from s in surveys

    // LEFT OUTER JOIN to ratings
    join r in ratedItems on s.Topic equals r.Topic into ratedSurveys
    from ri in ratedSurveys.DefaultIfEmpty()

    // define variables
    let total = surveys.Count()
    //let rated = ratedItems.Count()  -- this throws a NotSupportedException... which seems odd considering the line above

    // get non-rated surveys, in this case the RIGHT side of the join (Ratings) is null if there is no rating
    where ri.Topic == null

    // projection
    select new RatingViewModel
    {
        Topic = s.Topic,
        Category = s.Category,
        Rated = rated,
        Total = total
    }).FirstOrDefault();

return surveyTopic == null ? View("Finished") : View(surveyTopic);

不幸的是,这导致了我希望避免的 2 个数据库查询,但这应该更接近您所追求的。

布伦特,

它不喜欢你的解决方案,所以我试着修改了一下,但它仍然不满意。这是我的调整;

var surveyTopic = (from s in db.Surveys.Where(x => x.Category.Name.Equals(page))

let total = s.Topic.Count()
join r in db.Ratings.Where(y => y.UserName == User.Identity.Name) on s.Topic equals r.Topic
let rated = r.Topic.Count()

where r.Topic == null
select new RatingViewModel 
{
    Topic = s.Topic,
    Category = s.Category.Name,
    Rated = rated+1,
    Total = total
}).FirstOrDefault();