在我的ASP.NET MVC Core应用程序中,POST
操作方法Test
未返回预期结果。该网页应用程序使用this official ASP.NET Core site创建并稍作修改。真正的应用程序可以是downloaded from here,并且正在使用最新版本的VS2015
。该应用程序正在使用EF Core
。 如果你下载的项目,你需要做以下步骤测试上面的意想不到的结果:OUTER JOIN在EF Core中未返回预期结果
注意:这些步骤顺序很重要。这是一个非常小的测试项目。 Step2将创建一个名为ASPCore_Blogs的小型SQL Server Db。因此,请确保SQL Server正在乳宁:
- 你下载的项目后,请务必在VS2015(打开项目之前删除项目目录中
.vs
文件夹,如果项目挂起,你可能必须强制其使用近Task Manager
的Windows操作系统,并重新打开它使其工作。这是VS2015中的一个已知问题)。 - 打开
startup.cs
文件,并在Configuration()方法中将数据库实例名称从MyComputer\SQLServerInstance
更改为您正在使用的任何实例。在根目录中的appsettings.json
文件中执行相同的操作。 - 在VS2015 PM窗口,运行PM>更新数据库-context BloggingContext [确保运行SQL Server]
- 然后运行:PM>更新数据库-context ApplicationDbContext
运行的Web应用程序。通过输入登录名/密码信息进行注册。登录需要在Email([email protected])表格中。在左侧主页的:
点击链接
Blog Create
上创建4个博客为:[email protected],[email protected],[email protected],[email protected]- 点击链接
Blogs Index
验证以上所有4个博客是否已创建 - 点击
Test
链接。该视图由GET
动作方法Test
调用。在相应的视图(Test.cshtml
)上,您将在页面上看到Url
列显示所有4个以上博客。并且Title
和Content
列是空格。填写Title
列为:标题1,标题2,标题3,标题4。填写Content
列:内容1,内容2,Content3,Content4 - 现在,到相应的SQL Server数据库称为
ASPCore_BlogsNAxis
和开放Posts
表Edit
模式手动更改PostYear列值:1998,1999,1998,2001分别为(注:1998年重复的目的) - 现在,去
Blogs
表在同一个SQL Server数据库,并输入一个额外的博客[email protected]
- 现在,运行Web应用程序,并单击
Test
链接(左侧的主页)再次。您会看到,Get
动作方法Test
正在使用左外部联接来显示所有5个博客,但第5行中的右侧列(Title
和Content
)值为空白,正如所料,因为左外部联接不满足联接条件为BlogId
为第5博客。到现在为止还挺好。 - 现在,在
Year
下拉上Test.cshtml
视图,选择年份为1998年并点击GO
按钮。根据POST
动作方法Test
的第一个if
条件,应用程序应该只显示三条记录(两个用于1998年,第五个不满足连接条件):第一条,第三条和第五条记录。
但是,这并不是什么情况。当您通过从下拉列表中选择不同的年份重复此操作并单击GO
按钮时,您会看到输出结果不符合预期。
示例数据:
博客表数据:
BlogId Url
1 test1.com
2 test2.com
3 test3.com
4 test4.com
5 test5.com
帖子表数据:
PostId BlogId Content PostYear Title
1 1 Content1 1998 Title1
2 2 Content2 1999 Title2
3 3 Content3 1998 Title3
4 4 Content4 2001 Title4
左外在Test
行动GET甲基JOIN OD应返回:
BlogId Url PostId Content PostYear Title
1 test1.com 1 Content1 1998 Title1
2 test2.com 2 Content2 1999 Title2
3 test3.com 3 Content3 1998 Title3
4 test4.com 4 Content4 2001 Title4
5 test5.com NULL NULL NULL NULL
当你在下拉列表中选择1998年并点击Go按钮,测试(...)后的操作方法查询应该返回。 但它随机返回任何行:
BlogId Url PostId Content PostYear Title
1 test1.com 1 Content1 1998 Title1
3 test3com 3 Content2 1998 Title3
5 test5.com NULL NULL NULL NULL
模式:
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{ }
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
public class Blog
{
public int BlogId { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int PostYear { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
BlogsController:
public class BlogsController : Controller
{
private readonly BloggingContext _context;
public BlogsController(BloggingContext context)
{
_context = context;
}
// GET: Blogs
public async Task<IActionResult> Index()
{
return View(_context.Blogs.ToList());
}
// GET: /Blogs/Test
[HttpGet]
public async Task<IActionResult> Test(string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
ViewBag.YearsList = Enumerable.Range(1996, 29).Select(g => new SelectListItem { Value = g.ToString(), Text = g.ToString() }).ToList();
//return View(await _context.Blogs.Include(p => p.Posts).ToListAsync());
var qrVM = from b in _context.Blogs
join p in _context.Posts on b.BlogId equals p.BlogId into bp
from c in bp.DefaultIfEmpty()
select new BlogsWithRelatedPostsViewModel { BlogID = b.BlogId, PostID = (c == null ? 0 : c.PostId), Url = b.Url, Title = (c == null ? string.Empty : c.Title), Content = (c == null ? string.Empty : c.Content) };
return View(await qrVM.ToListAsync());
}
// POST: /Blogs/Test
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Test(List<BlogsWithRelatedPostsViewModel> list, string GO, int currentlySelectedIndex, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
ViewBag.YearsList = Enumerable.Range(1996, 29).Select(g => new SelectListItem { Value = g.ToString(), Text = g.ToString() }).ToList();
if (!string.IsNullOrEmpty(GO))
{
var qrVM = from b in _context.Blogs
join p in _context.Posts on b.BlogId equals p.BlogId into bp
from c in bp.DefaultIfEmpty()
where c == null? true : c.PostYear.Equals(currentlySelectedIndex)
select new BlogsWithRelatedPostsViewModel { BlogID = b.BlogId, PostID = (c == null ? 0 : c.PostId), Url = b.Url, Title = (c == null ? string.Empty : c.Title), Content = (c == null ? string.Empty : c.Content) };
return View(await qrVM.ToListAsync());
}
else if (ModelState.IsValid)
{
foreach (var item in list)
{
var oPost = _context.Posts.Where(r => r.PostId.Equals(item.PostID)).FirstOrDefault();
if (oPost != null)
{
oPost.Title = item.Title;
oPost.Content = item.Content;
oPost.PostYear = currentlySelectedIndex;
oPost.BlogId = item.BlogID; //according to new post below the blogId should exist for a newly created port - but just in case
}
else
{
if (item.PostID == 0)
{
Post oPostNew = new Post { BlogId = item.BlogID, Title = item.Title, Content = item.Content, PostYear = currentlySelectedIndex }; //need to use currentlySelectedIndex intead of item.FiscalYear in case of adding a record
_context.Add(oPostNew);
}
}
}
await _context.SaveChangesAsync();
//return RedirectToLocal(returnUrl);
return View(list);
}
// If we got this far, something failed, redisplay form
return View();
}
// GET: Blogs/Details/5
public async Task<IActionResult> Details(int? id)
{
if (id == null)
{
return NotFound();
}
var blog = await _context.Blogs.SingleOrDefaultAsync(m => m.BlogId == id);
if (blog == null)
{
return NotFound();
}
return View(blog);
}
// GET: Blogs/Create
[HttpGet]
public IActionResult Create()
{
return View();
}
// POST: Blogs/Create
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create([Bind("BlogId,Url")] Blog blog)
{
if (ModelState.IsValid)
{
_context.Blogs.Add(blog);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(blog);
}
// GET: Blogs/Edit/5
public async Task<IActionResult> Edit(int? id)
{
if (id == null)
{
return NotFound();
}
var blog = await _context.Blogs.SingleOrDefaultAsync(m => m.BlogId == id);
if (blog == null)
{
return NotFound();
}
return View(blog);
}
// POST: Blogs/Edit/5
// To protect from overposting attacks, please enable the specific properties you want to bind to, for
// more details see http://go.microsoft.com/fwlink/?LinkId=317598.
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Edit(int id, [Bind("BlogId,Url")] Blog blog)
{
if (id != blog.BlogId)
{
return NotFound();
}
if (ModelState.IsValid)
{
try
{
_context.Update(blog);
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!BlogExists(blog.BlogId))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToAction("Index");
}
return View(blog);
}
// GET: Blogs/Delete/5
public async Task<IActionResult> Delete(int? id)
{
if (id == null)
{
return NotFound();
}
var blog = await _context.Blogs.SingleOrDefaultAsync(m => m.BlogId == id);
if (blog == null)
{
return NotFound();
}
return View(blog);
}
// POST: Blogs/Delete/5
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteConfirmed(int id)
{
var blog = await _context.Blogs.SingleOrDefaultAsync(m => m.BlogId == id);
_context.Blogs.Remove(blog);
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
private bool BlogExists(int id)
{
return _context.Blogs.Any(e => e.BlogId == id);
}
}
UPDATE:
- 添加步骤2中要求用户在
Test()
的GET/POST操作方法改变来自bp.DefaultIfEmpty(新邮())的连接字符串 - 删除新邮()。但同样的错误仍然存在。
谢谢你试图提供帮助。在我收到意想不到的结果后,增加了新的Post()。但同样的意外行为仍然存在。无论如何,我已经删除了'新的Post()'并且在我的文章中添加了一个** UPDATE **部分。 – nam