如何在不实际删除的情况下从数据库中删除项目?
How to delete an item from database without actually deleting it?
我一直在开发一个 ASP.NET MVC 5 Web 应用程序,使用 Entity Framework 6 作为我的业务编程 II class 的作业。尽管我对编程知之甚少,但我一直在进步,但我却运行陷入了困境。我应该为基于 Northwind Traders 数据库的在线店面编写 CRUD 操作。我已经有了从数据库读取以及在数据库中添加和更新项目的工作代码。我挣扎的地方是删除项目。作业描述中列出了以下要求:
Delete a product by making it discontinued so that the information is displayed in the database. Do NOT actually delete a product from the database.
我尝试了一些方法来尝试完成这项工作,但由于各种原因都失败了。
这是我当前 Delete
视图的代码(忽略任何奇怪的 HTML 格式决定,现在我专注于实现此功能):
@model NWTradersWeb.Models.Product
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Product: @Html.DisplayFor(model => model.ProductName)</h4>
<hr />
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-actions">
<input type="submit" value="Yes" class="btn btn-dark" /> |
@Html.ActionLink("Back to List", "Index")
</div>
}
</div>
我尝试编辑我的 ProductsController.cs
以手动将 Discontinued
属性设置为 true,如下所示:
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Product product = db.Products.Find(id);
product.Discontinued = true;
db.SaveChanges();
return RedirectToAction("Index");
}
此 有效 ,但如果我对同一产品进行 运行 Edit
操作,我将无法撤消更改。我可以取消选中“已停产”复选框,但在我提交更改后它不会保存,并且“索引”页面仍将产品显示为已停产。
这是我的 Edit
视图代码和相应的 ProductsController.cs
方法,我不确定这些是否与我的问题有关,但无论如何我都会包括它们:
查看:
@model NWTradersWeb.Models.Product
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product: @Html.DisplayFor(model => model.ProductName)</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.ProductID)
<div class="form-group">
@Html.LabelFor(model => model.SupplierID, "SupplierID", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("SupplierID", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.SupplierID, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.CategoryID, "CategoryID", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("CategoryID", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.CategoryID, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.QuantityPerUnit, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.QuantityPerUnit, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.QuantityPerUnit, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.UnitPrice, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.UnitPrice, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UnitPrice, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.UnitsInStock, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.UnitsInStock, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UnitsInStock, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.UnitsOnOrder, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.UnitsOnOrder, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UnitsOnOrder, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ReorderLevel, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.ReorderLevel, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.ReorderLevel, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Discontinued, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
@Html.EditorFor(model => model.Discontinued)
@Html.ValidationMessageFor(model => model.Discontinued, "", new { @class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
控制器方法:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued")] Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
我的教授还提到将 Delete
操作重定向到一个更简单的 Edit
页面,我们可以在其中切换 Discontinued 属性。我认为他可能暗示了一种片面的观点,但据我所知我们还没有涵盖这一点。
请注意:在编程方面,我认为自己是新手。我学过其他 classes,但讲师更注重语法而不是概念,因此我的基础非常薄弱。我可能对其他人认为理所当然的某些事情一无所知。我想毕业自学后回去好好研究基础知识,但这是一个几乎与编程完全无关的学位必修课class。任何提示、提示,甚至是正确方向的推动,我们都将不胜感激。
您的删除逻辑似乎没问题。我会更详细地查看您的编辑。
总的来说,我不喜欢在服务器和视图之间传递实体,尤其是从视图中接受实体。这通常是一种不好的做法,因为您信任来自视图的数据,这些数据很容易被篡改。将数据从视图传递到服务器时也是如此,这可能会导致通过一些“草率”JavaScript 或将模型转换为 JSON 模型检查 client-side。最近一名记者因在密苏里州政府网站上通过浏览器调试器发现额外信息而被指控“黑客攻击”的案例概述了当 server-side 代码有可能发送太多信息时可能出现的那种胡说八道浏览器的详细信息。
无论如何,当您在停用 Discontinued 标志后接受绑定的 Product 时,在您的 Edit 方法中,实体模型中的值是什么?例如,如果您使用 Delete 将 Discontinued 设置为“True”,然后转到该产品的编辑视图和 un-check 输入控件并提交表单,在编辑页面中出现的“产品”中,什么是product.Discontinued?
的状态
如果该值仍然为“True”,那么您的页面绑定可能存在问题,其中 EditorFor 未正确链接到该标志,或者该值未反序列化到 Product 实体中。 (一个 private
或缺少 setter?)
如果返回的值应该是正确的,那么我会考虑更改更新实体的方式。代码如下:
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
... 本质上是危险的,因为“产品”不是实体,它是用于填充实体 class 的一组反序列化值。理想情况下,在更新数据时,您将提供一个不会与实体混淆的 ViewModel class,并且只包含允许更新的字段。尽管将实体 class 用作该视图模型,但使用您当前的代码我会建议更像:
var dataProduct = db.Products.Single(x => x.Id == product.Id);
dataProduct.ProductName = product.ProductName;
dataProduct.Discontinued = product.Discontinued;
// ...
db.SaveChanges();
当涉及到可能允许用户更改类别之类的 FK 时,您应该提前加载这些关系,然后比较 FK ID,然后加载 re-associate 加载实体中的那些“新”关系从数据状态。 (不要只替换 FK 值。)
这样做而不是附加状态并将状态设置为已修改的原因:
- 我们在加载实体时执行验证。如果我们返回一个不存在的 Id,我们可以处理该异常。我们还可以过滤数据以确保当前用户实际上有权查看所请求的 ID,并且可以在看起来有人正在篡改数据时结束会话。
- 我们只更新我们允许更改的值,而不是实体中的所有内容。我们还可以在进行更改之前进行验证以确保提供的值符合目的。
- 跨复制值时,EF 只会为实际更改的值生成
UPDATE
语句(如果有任何实际更改)。将实体状态附加和设置为 Modified
或使用 Update
将始终生成一个 UPDATE
语句替换 所有 值,无论是否有任何更改。 (可能会对 DbContext 中的触发器或挂钩产生负面影响,例如审计)
我一直在开发一个 ASP.NET MVC 5 Web 应用程序,使用 Entity Framework 6 作为我的业务编程 II class 的作业。尽管我对编程知之甚少,但我一直在进步,但我却运行陷入了困境。我应该为基于 Northwind Traders 数据库的在线店面编写 CRUD 操作。我已经有了从数据库读取以及在数据库中添加和更新项目的工作代码。我挣扎的地方是删除项目。作业描述中列出了以下要求:
Delete a product by making it discontinued so that the information is displayed in the database. Do NOT actually delete a product from the database.
我尝试了一些方法来尝试完成这项工作,但由于各种原因都失败了。
这是我当前 Delete
视图的代码(忽略任何奇怪的 HTML 格式决定,现在我专注于实现此功能):
@model NWTradersWeb.Models.Product
@{
ViewBag.Title = "Delete";
}
<h2>Delete</h2>
<h3>Are you sure you want to delete this?</h3>
<div>
<h4>Product: @Html.DisplayFor(model => model.ProductName)</h4>
<hr />
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-actions">
<input type="submit" value="Yes" class="btn btn-dark" /> |
@Html.ActionLink("Back to List", "Index")
</div>
}
</div>
我尝试编辑我的 ProductsController.cs
以手动将 Discontinued
属性设置为 true,如下所示:
public ActionResult Delete(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
return View(product);
}
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Product product = db.Products.Find(id);
product.Discontinued = true;
db.SaveChanges();
return RedirectToAction("Index");
}
此 有效 ,但如果我对同一产品进行 运行 Edit
操作,我将无法撤消更改。我可以取消选中“已停产”复选框,但在我提交更改后它不会保存,并且“索引”页面仍将产品显示为已停产。
这是我的 Edit
视图代码和相应的 ProductsController.cs
方法,我不确定这些是否与我的问题有关,但无论如何我都会包括它们:
查看:
@model NWTradersWeb.Models.Product
@{
ViewBag.Title = "Edit";
}
<h2>Edit</h2>
@using (Html.BeginForm())
{
@Html.AntiForgeryToken()
<div class="form-horizontal">
<h4>Product: @Html.DisplayFor(model => model.ProductName)</h4>
<hr />
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
@Html.HiddenFor(model => model.ProductID)
<div class="form-group">
@Html.LabelFor(model => model.SupplierID, "SupplierID", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("SupplierID", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.SupplierID, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.CategoryID, "CategoryID", htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("CategoryID", null, htmlAttributes: new { @class = "form-control" })
@Html.ValidationMessageFor(model => model.CategoryID, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.QuantityPerUnit, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.QuantityPerUnit, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.QuantityPerUnit, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.UnitPrice, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.UnitPrice, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UnitPrice, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.UnitsInStock, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.UnitsInStock, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UnitsInStock, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.UnitsOnOrder, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.UnitsOnOrder, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.UnitsOnOrder, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.ReorderLevel, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.ReorderLevel, new { htmlAttributes = new { @class = "form-control" } })
@Html.ValidationMessageFor(model => model.ReorderLevel, "", new { @class = "text-danger" })
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.Discontinued, htmlAttributes: new { @class = "control-label col-md-2" })
<div class="col-md-10">
<div class="checkbox">
@Html.EditorFor(model => model.Discontinued)
@Html.ValidationMessageFor(model => model.Discontinued, "", new { @class = "text-danger" })
</div>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<input type="submit" value="Save" class="btn btn-default" />
</div>
</div>
</div>
}
<div>
@Html.ActionLink("Back to List", "Index")
</div>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
控制器方法:
public ActionResult Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
Product product = db.Products.Find(id);
if (product == null)
{
return HttpNotFound();
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Edit([Bind(Include = "ProductID,ProductName,SupplierID,CategoryID,QuantityPerUnit,UnitPrice,UnitsInStock,UnitsOnOrder,ReorderLevel,Discontinued")] Product product)
{
if (ModelState.IsValid)
{
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.CategoryID = new SelectList(db.Categories, "CategoryID", "CategoryName", product.CategoryID);
ViewBag.SupplierID = new SelectList(db.Suppliers, "SupplierID", "CompanyName", product.SupplierID);
return View(product);
}
我的教授还提到将 Delete
操作重定向到一个更简单的 Edit
页面,我们可以在其中切换 Discontinued 属性。我认为他可能暗示了一种片面的观点,但据我所知我们还没有涵盖这一点。
请注意:在编程方面,我认为自己是新手。我学过其他 classes,但讲师更注重语法而不是概念,因此我的基础非常薄弱。我可能对其他人认为理所当然的某些事情一无所知。我想毕业自学后回去好好研究基础知识,但这是一个几乎与编程完全无关的学位必修课class。任何提示、提示,甚至是正确方向的推动,我们都将不胜感激。
您的删除逻辑似乎没问题。我会更详细地查看您的编辑。
总的来说,我不喜欢在服务器和视图之间传递实体,尤其是从视图中接受实体。这通常是一种不好的做法,因为您信任来自视图的数据,这些数据很容易被篡改。将数据从视图传递到服务器时也是如此,这可能会导致通过一些“草率”JavaScript 或将模型转换为 JSON 模型检查 client-side。最近一名记者因在密苏里州政府网站上通过浏览器调试器发现额外信息而被指控“黑客攻击”的案例概述了当 server-side 代码有可能发送太多信息时可能出现的那种胡说八道浏览器的详细信息。
无论如何,当您在停用 Discontinued 标志后接受绑定的 Product 时,在您的 Edit 方法中,实体模型中的值是什么?例如,如果您使用 Delete 将 Discontinued 设置为“True”,然后转到该产品的编辑视图和 un-check 输入控件并提交表单,在编辑页面中出现的“产品”中,什么是product.Discontinued?
的状态如果该值仍然为“True”,那么您的页面绑定可能存在问题,其中 EditorFor 未正确链接到该标志,或者该值未反序列化到 Product 实体中。 (一个 private
或缺少 setter?)
如果返回的值应该是正确的,那么我会考虑更改更新实体的方式。代码如下:
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
... 本质上是危险的,因为“产品”不是实体,它是用于填充实体 class 的一组反序列化值。理想情况下,在更新数据时,您将提供一个不会与实体混淆的 ViewModel class,并且只包含允许更新的字段。尽管将实体 class 用作该视图模型,但使用您当前的代码我会建议更像:
var dataProduct = db.Products.Single(x => x.Id == product.Id);
dataProduct.ProductName = product.ProductName;
dataProduct.Discontinued = product.Discontinued;
// ...
db.SaveChanges();
当涉及到可能允许用户更改类别之类的 FK 时,您应该提前加载这些关系,然后比较 FK ID,然后加载 re-associate 加载实体中的那些“新”关系从数据状态。 (不要只替换 FK 值。)
这样做而不是附加状态并将状态设置为已修改的原因:
- 我们在加载实体时执行验证。如果我们返回一个不存在的 Id,我们可以处理该异常。我们还可以过滤数据以确保当前用户实际上有权查看所请求的 ID,并且可以在看起来有人正在篡改数据时结束会话。
- 我们只更新我们允许更改的值,而不是实体中的所有内容。我们还可以在进行更改之前进行验证以确保提供的值符合目的。
- 跨复制值时,EF 只会为实际更改的值生成
UPDATE
语句(如果有任何实际更改)。将实体状态附加和设置为Modified
或使用Update
将始终生成一个UPDATE
语句替换 所有 值,无论是否有任何更改。 (可能会对 DbContext 中的触发器或挂钩产生负面影响,例如审计)