嵌套的 foreach - 逻辑问题
nested foreach - issue with logic
在过去的 3 个小时里,这一直困扰着我……我可能只是累了,但似乎无法理解正确的逻辑。我想做的是;
- 获取调查主题列表和评级调查主题列表。
- 如果none个主题已经评分,return第一个给用户评分。
- 如果他们都被评级了,return 一个观点说 'yay you completed the survey'
- 否则确定哪些未被评级并在视图中提供它们。
所有主题一次提供 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();
在过去的 3 个小时里,这一直困扰着我……我可能只是累了,但似乎无法理解正确的逻辑。我想做的是;
- 获取调查主题列表和评级调查主题列表。
- 如果none个主题已经评分,return第一个给用户评分。
- 如果他们都被评级了,return 一个观点说 'yay you completed the survey'
- 否则确定哪些未被评级并在视图中提供它们。
所有主题一次提供 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();