在 dbcontext 中的请求之间存储数据
Posted
技术标签:
【中文标题】在 dbcontext 中的请求之间存储数据【英文标题】:Storing data between requests in dbcontext 【发布时间】:2018-12-28 09:24:06 【问题描述】:我有一个使用 razor 进行服务器端渲染的页面,您可以在其中添加来自不同列表的几个元素,填充一些字段并在提交时从中创建请求。
每次从任何列表中添加/获取项目时,我都会发送带有提交按钮的帖子到特定操作,例如“客户选择”。我这样做是因为我需要为添加的项目重新创建其他视图组件。在这些方法中,我想将添加的对象添加到数据库上下文中,所以在提交时我可以只说SaveChanges()
而不必在同一个方法中组装所有东西。但是在 .net core db 上下文中是每个请求的,建议保持这种方式。在这种情况下,如何在请求之间存储这些临时实体对象,以便稍后如果有人决定提交它们,我可以说 SaveChanges()
或以其他方式丢弃它们?
我想要这样的东西:
public IActionResult CustomerAdded(int customerId)
var customer = _context.Customers.First(c => c.IdCustomer == customerId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Customers.Add(customer);
return View();
public IActionResult ItemAdded(int itemId)
var item = _context.Items.First(c => c.IdItem == itemId);
var message = _context.Messages.First(m => m.IdMessage = _idMessage);
message.Items.Add(item);
return View();
public IActionResult Submit()
_context.SaveChanges();
return View();
如果这不可能,那么我正在考虑在每个方法中添加单个元素并将它们保存在那里,然后在提交时我将构建最后一个最终元素。但是,如果有人在没有提交的情况下关闭了浏览器,那么我的数据库中的数据不完整。我必须运行某种工作来删除它们,对于这样一个简单的任务来说似乎太多了。
【问题讨论】:
你可以尝试让那些 post 方法无效并阻止返回任何东西吗?使用 ajax 运行该方法,因此它不会更改页面。当用户点击提交 SaveChanges 并返回到您的页面时。 ? 请您尝试一下:services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")),ServiceLifetime.Singleton);
@TanvirArjel 我不想将 dbcontext 设置为单例
@FCin 更好的解决方案,您可以将数据存储在缓存中并在提交方法上检索?成功保存更改后删除缓存。
@FCin 我明白你的要求了! Session和Cache不是一回事!如果您需要任何帮助,您可以通过 Team Viewer Visual Studio Live 共享等远程访问共享您的代码。
【参考方案1】:
在这种情况下使用服务器资源来跟踪更改并不是一个好主意。在购物篮、列表或批量编辑等场景中,最好在客户端跟踪更改。
您要求在服务器端生成视图并不意味着您需要跟踪DbContext
中的更改。从服务器获取索引视图并创建视图,但在客户端跟踪更改。然后保存,将所有数据发布到服务器,以根据您拥有的跟踪信息保存更改。
客户端更改跟踪的机制取决于需求和场景,例如您可以使用 html 输入跟踪更改,可以使用 cookie 跟踪更改,您可以使用浏览器内存中的 javascript 对象(如角度场景)跟踪更改。
这是这篇文章,我将展示一个使用 html 输入和模型绑定的示例。要了解有关此主题的更多信息,请查看 Phill Haack 的这篇文章:Model Binding To A List。
示例
在下面的示例中,我描述了一个客户列表的列表编辑场景。为了简单起见,我想:
您有一个要在客户端编辑的客户列表。您可能想要添加、编辑或删除项目。 添加新项目时,新行的行模板应来自服务器。 删除时,您可以通过单击行上的复选框将项目标记为已删除。 添加/编辑时,您希望在单元格附近显示验证错误。 您想在最后保存更改,请单击“保存”按钮。要实现上述场景然后你需要创建以下模型、动作和视图:
可追踪
这个类是帮助我们进行客户端跟踪和列表编辑的模型:
public class Trackable<T>
public Trackable()
public Trackable(T model) Model = model;
public Guid Index get; set; = Guid.NewGuid();
public bool Deleted get; set;
public bool Added get; set;
public T Model get; set;
客户模型
客户模型:
public class Customer
[Display(Name ="Id")]
public int Id get; set;
[StringLength(20, MinimumLength = 1)]
[Required]
[Display(Name ="First Name")]
public string FirstName get; set;
[StringLength(20, MinimumLength = 1)]
[Required]
[Display(Name ="Last Name")]
public string LastName get; set;
[EmailAddress]
[Required]
[Display(Name ="Email Name")]
public string Email get; set;
Index.cshtml 视图
Index 视图负责渲染List<Trackable<Customer>>
。在渲染每条记录时,我们使用RowTemplate
视图。添加新项目时使用的相同视图。
在这个视图中,我们有一个用于保存的提交按钮和一个用于添加新行的按钮,该按钮调用使用 ajax 的 Create 操作。
这是索引视图:
@model IEnumerable<Trackable<Customer>>
<h2>Index</h2>
<form method="post" action="Index">
<p>
<button id="create">New Customer</button>
<input type="submit" value="Save All">
</p>
<table class="table" id="data">
<thead>
<tr>
<th>
Delete
</th>
<th>
@Html.DisplayNameFor(x => x.Model.FirstName)
</th>
<th>
@Html.DisplayNameFor(x => x.Model.LastName)
</th>
<th>
@Html.DisplayNameFor(x => x.Model.Email)
</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
await Html.RenderPartialAsync("RowTemplate", item);
</tbody>
</table>
</form>
@section Scripts
<script>
$(function ()
$('#create').click(function (e)
e.preventDefault();
$.ajax(
url: 'Create',
method: 'Get',
success: function (data)
$('#data tbody tr:last-child').after(data);
,
error: function (e) alert(e);
);
);
);
</script>
RowTemplate.cshtml 视图
此视图负责呈现客户记录。在这个视图中,我们首先将Index
渲染为隐藏状态,然后为字段设置前缀[index]
,然后再渲染字段,包括索引、添加、删除和模型id:
这是 RowTemplate 视图:
@model Trackable<Customer>
<tr>
<td>
@Html.HiddenFor(x => x.Index)
@Html.ViewData.TemplateInfo.HtmlFieldPrefix = $"[Model.Index]";
@Html.HiddenFor(x => x.Index)
@Html.HiddenFor(x => x.Model.Id)
@Html.HiddenFor(x => x.Added)
@Html.CheckBoxFor(x => x.Deleted)
</td>
<td>
@Html.EditorFor(x => x.Model.FirstName)
@Html.ValidationMessageFor(x => x.Model.FirstName)
</td>
<td>
@Html.EditorFor(x => x.Model.LastName)
@Html.ValidationMessageFor(x => x.Model.LastName)
</td>
<td>
@Html.EditorFor(x => x.Model.Email)
@Html.ValidationMessageFor(x => x.Model.Email)
</td>
</tr>
客户控制器
public class CustomerController : Controller
private static List<Customer> list;
它将有以下动作。
[GET] 索引操作
在此操作中,您可以从数据库加载数据并将其整形为 List<Trackable<Customer>>
并传递给 Index
视图:
[HttpGet]
public IActionResult Index()
if (list == null)
list = Enumerable.Range(1, 5).Select(x => new Customer()
Id = x,
FirstName = $"Ax",
LastName = $"Bx",
Email = $"Ax@Bx.com"
).ToList();
var model = list.Select(x => new Trackable<Customer>(x)).ToList();
return View(model);
[GET] 创建操作
这个动作负责返回新的行模板。它将由索引视图中的按钮使用 ajax 调用:
[HttpGet]
public IActionResult Create()
var model = new Trackable<Customer>(new Customer()) Added = true ;
return PartialView("RowTemplate", model);
[POST] 索引操作
该动作负责从客户端接收跟踪的项目并保存它们。它接收的模型是List<Trackable<Customer>>
。它首先去除已删除行的验证错误消息。然后删除那些既被删除又被添加的。然后检查模型状态是否有效,尝试对数据源应用更改。
Deleted
属性为 true 的项目被删除,Added
为 true 和 Deleted
为 false 的项目为新项目,其余项目被编辑。然后不需要从数据库中加载所有项目,只需使用 for 循环,为每个项目调用 db.Entry
并设置它们的状态,最后保存更改。
[HttpPost]
public IActionResult Index(List<Trackable<Customer>> model)
//Cleanup model errors for deleted rows
var deletedIndexes = model.
Where(x => x.Deleted).Select(x => $"[x.Index]");
var modelStateDeletedKeys = ModelState.Keys.
Where(x => deletedIndexes.Any(d => x.StartsWith(d)));
modelStateDeletedKeys.ToList().ForEach(x => ModelState.Remove(x));
//Removing rows which are added and deleted
model.RemoveAll(x => x.Deleted && x.Added);
//If model state is not valid, return view
if (!ModelState.IsValid)
return View(model);
//Deleted rows
model.Where(x => x.Deleted && !x.Added).ToList().ForEach(x =>
var i = list.FindIndex(c => c.Id == x.Model.Id);
if (i >= 0)
list.RemoveAt(i);
);
//Added rows
model.Where(x => !x.Deleted && x.Added).ToList().ForEach(x =>
list.Add(x.Model);
);
//Edited rows
model.Where(x => !x.Deleted && !x.Added).ToList().ForEach(x =>
var i = list.FindIndex(c => c.Id == x.Model.Id);
if (i >= 0)
list[i] = x.Model;
);
//Reditect to action index
return RedirectToAction("Index");
【讨论】:
【参考方案2】:使用 javascript 并使用 type="hidden"
或 visibility
的动态表单怎么样?
然后一次性发送所有内容
或者使用带有重定向的 TempData 并在其他视图(表单)中重用该数据作为 input type="hidden"
流程:
Form1 ->
Controller的方法将数据保存在TempData中并重定向到Form2 View/或ViewData并返回Form2 View? ->
Form2 已将 TempData 插入到隐藏输入下的表单中 ->
同时提交两个
【讨论】:
【参考方案3】:饼干!
public class HomeController : Controller
public string Index()
HttpCookie cookie = Request.Cookies["message"];
Message message = null;
string json = "";
if (cookie == null)
message = new Message();
json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
cookie = new HttpCookie("message", json);
Response.Cookies.Add(cookie);
return json;
public string CustomerAdded(int id)
HttpCookie cookie = Request.Cookies["message"];
Message message = null;
string json = "";
if (cookie == null || string.IsNullOrEmpty(cookie.Value))
message = new Message();
json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
cookie = new HttpCookie("message", json);
else
json = cookie.Value;
message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
if (message.Customers == null) message.Customers = new List<int>();
if (message.Items == null) message.Items = new List<int>();
if (!message.Customers.Contains(id))
message.Customers.Add(id);
json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
cookie = new HttpCookie("message", json);
Response.Cookies.Add(cookie);
return json;
public string ItemAdded(int id)
HttpCookie cookie = Request.Cookies["message"];
Message message = null;
string json = "";
if (cookie == null || string.IsNullOrEmpty(cookie.Value))
message = new Message();
json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
cookie = new HttpCookie("message", json);
else
json = cookie.Value;
message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
if (message.Customers == null) message.Customers = new List<int>();
if (message.Items == null) message.Items = new List<int>();
if (!message.Items.Contains(id))
message.Items.Add(id);
json = new System.Web.Script.Serialization.JavaScriptSerializer().Serialize(message);
cookie = new HttpCookie("message", json);
Response.Cookies.Add(cookie);
return json;
public string Submit()
HttpCookie cookie = Request.Cookies["message"];
Message message = null;
string json = "";
if (cookie == null || string.IsNullOrEmpty(cookie.Value))
return "no data";
else
json = cookie.Value;
message = new System.Web.Script.Serialization.JavaScriptSerializer().Deserialize<Message>(json);
Response.Cookies["message"].Value = "";
Response.Cookies["message"].Expires = DateTime.Now.AddDays(-1);
return "Submited";
示例链接
http://localhost:58603/Home/CustomerAdded/1
http://localhost:58603/Home/CustomerAdded/2
http://localhost:58603/Home/Submit
【讨论】:
以上是关于在 dbcontext 中的请求之间存储数据的主要内容,如果未能解决你的问题,请参考以下文章
从 BackgroundService 创建 DbContext 租户时配置它
ASP.NET MVC,EntityFramework,DBContext,不同项目中的存储库[关闭]
Entity Framework 4.1 DbContext API 中的接口和存储库抽象中断子查询?