私のASP.NET MVCコアアプリケーションでは、 POST
アクションメソッドTest
は期待された結果を返していません。 Webアプリケーションは、 この公式ASP.NET Coreサイトを使用して作成され、わずかに変更されました。実際のアプリはここからダウンロードでき、 VS2015
最新バージョンをVS2015
ます。アプリはEF Core
を使用しています。プロジェクトをダウンロードする場合、上記の予期しない結果をテストするには、次の手順を実行する必要があります。
注 :これらの手順の順序は重要です。これは非常に小さなテストプロジェクトです。ステップ2では、ASPCore_Blogsという小さなSQL Server Dbが作成されます。したがって、SQL Serverが実行されていることを確認してください。
.vs
プロジェクトを開く前に、プロジェクトディレクトリから.vs
フォルダを削除してください(プロジェクトがハングアップする場合は、Windows OSのTask Manager
を使用して強制終了し、これはVS2015の既知の問題です)。 startup.cs
ファイルを開き、Configuration()メソッドで、データベースインスタンス名をMyComputer\SQLServerInstance
から使用しているインスタンスに変更します。ルートディレクトリのappsettings.json
ファイルで同じ操作をappsettings.json
ます。 Webアプリケーションを実行します。ログイン/パスワード情報を入力して登録する。ログインは電子メール(test@test.com)形式にする必要があります。ホームページの左側にある:
Blog Create
リンクをクリックすると、Blog1@test.com、Blog2@test.com、Blog3@test.com、Blog4@test.comの4つのBlog Create
れます。
Blogs Index
リンクをクリックしてくださいTest
リンクをクリックします。このビューは、 GET
アクションメソッドTest
によって呼び出されます。対応するビュー( Test.cshtml
)には、ページのUrl
列に上記の4つのブログがすべて表示されていることがわかります。 Title
とContent
列は空白です。 Title
列には、Title1、Title2、Title3、Title4と入力します。 Content
列をContent1、Content2、Content3、Content4のように入力します。 ASPCore_BlogsNAxis
と呼ばれる対応するSQL Server DBに移動し、 Edit
モードでPosts
テーブルを開いて、PostYear列の値を手動で変更します:1998,1999,1998,2001(注:1998は目的に合わせて繰り返しています) Blogs
テーブルに行き、余分なブログBlog5@test.com
を入力してBlog5@test.com
Test
]リンク(ホームページの左側にある)をクリックします。 Get
actionメソッドTest
が左外部結合を使用して5つのすべてのブログを表示していますが、左の外部結合が結合を満たさないため、右側の列( Title
とContent
)の値は5行目の空白になります。 BlogId
の5番目のブログの条件。ここまでは順調ですね。 Test.cshtml
ビューのYear
ドロップダウンで、1998年を選択し、 GO
ボタンをクリックします。 POST
アクションメソッドTest
の最初のif
条件によると、アプリケーションは3つのレコード(1998年に2つ、結合条件を満たす5番目のもの)のみを表示します:第1、第3、第5レコード。 しかし、それは起こっていることではありません。ドロップダウンから何年もの年を選択してこのアクションを繰り返し、 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
アクションのLEFT外部ジョインGETメソッドは返されます:
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年を選択して[実行]ボタンをクリックすると、テスト(...)ポストアクションメソッドのクエリが返されます。 しかし、それはランダムに任意の行を返す :
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);
}
}
更新 :
Test()
GET / POSTアクションメソッドのbp.DefaultIfEmpty(new Post())から新しいPost()を削除しました。しかし、同じエラーはまだそこにあります。 linqクエリでは、 DefaultIfEmtpy
呼び出しを行います:
from c in bp.DefaultIfEmpty(new Post())
where c == null? true : c.PostYear.Equals(currentlySelectedIndex)
DefaultIfEmtpy
がヌルを返すのではなく、空のときにnew Post()
インスタンスを返すオーバーロードを使用しました。しかし、あなたのロジックはそれがnull
を返すことを期待していnull
。 snipperの最初の行を、代わりにnull
を返すオーバーロードに置き換えnull
。
from c in bp.DefaultIfEmpty()