2016-09-14 74 views
0

在我的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正在乳宁:

  1. 你下载的项目后,请务必在VS2015(打开项目之前删除项目目录中.vs文件夹,如果项目挂起,你可能必须强制其使用近Task Manager的Windows操作系统,并重新打开它使其工作。这是VS2015中的一个已知问题)。
  2. 打开startup.cs文件,并在Configuration()方法中将数据库实例名称从MyComputer\SQLServerInstance更改为您正在使用的任何实例。在根目录中的appsettings.json文件中执行相同的操作。
  3. 在VS2015 PM窗口,运行PM>更新数据库-context BloggingContext [确保运行SQL Server]
  4. 然后运行:PM>更新数据库-context ApplicationDbContext
  5. 运行的Web应用程序。通过输入登录名/密码信息进行注册。登录需要在Email([email protected])表格中。在左侧主页的:

  6. 点击链接Blog Create上创建4个博客为:[email protected][email protected][email protected][email protected]

  7. 点击链接Blogs Index验证以上所有4个博客是否已创建
  8. 点击Test链接。该视图由GET动作方法Test调用。在相应的视图(Test.cshtml)上,您将在页面上看到Url列显示所有4个以上博客。并且TitleContent列是空格。填写Title列为:标题1,标题2,标题3,标题4。填写Content列:内容1,内容2,Content3,Content4
  9. 现在,到相应的SQL Server数据库称为ASPCore_BlogsNAxis和开放PostsEdit模式手动更改PostYear列值:1998,1999,1998,2001分别为(注:1998年重复的目的)
  10. 现在,去Blogs表在同一个SQL Server数据库,并输入一个额外的博客[email protected]
  11. 现在,运行Web应用程序,并单击Test链接(左侧的主页)再次。您会看到,Get动作方法Test正在使用左外部联接来显示所有5个博客,但第5行中的右侧列(TitleContent)值为空白,正如所料,因为左外部联接不满足联接条件为BlogId为第5博客。到现在为止还挺好。
  12. 现在,在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

  1. 添加步骤2中要求用户在Test()的GET/POST操作方法改变来自bp.DefaultIfEmpty(新邮())的连接字符串
  2. 删除新邮()。但同样的错误仍然存​​在。

回答

0
在LINQ查询

,在那里你做DefaultIfEmtpy电话:

from c in bp.DefaultIfEmpty(new Post()) 
where c == null? true : c.PostYear.Equals(currentlySelectedIndex) 

你用来代替返回null过载,其中DefaultIfEmtpy将返回new Post()实例时,它是空的。但是你的逻辑预计它会返回null。用取代null的过载代替snipper的第一行:

from c in bp.DefaultIfEmpty() 
+0

谢谢你试图提供帮助。在我收到意想不到的结果后,增加了新的Post()。但同样的意外行为仍然存在。无论如何,我已经删除了'新的Post()'并且在我的文章中添加了一个** UPDATE **部分。 – nam