ASP.NET MVC 局部视图 Post-返回,不一致的更新和奇怪的行为
ASP.NET MVC Partial View Post-Back, Inconsistent Updating and Bizzare Behavior
我正在开发一个 ASP.Net MVC 应用程序,并且 运行 在尝试通过部分 post 返回更新我的数据库中的数据时遇到了一个奇怪的问题。对于 HTTP、AJAX 等,我还是个新手,所以我希望这是一个明显的错误。
基本上,当我尝试更新 table 将内容区域链接到评估时,更新有时有效,有时无效。奇怪的是,在我 post 之后,我直接从 MVC 应用程序 查询数据库 只是为了确保确实进行了预期的更改(这就是所有 ViewBag.DebugInfo's 在下面的代码中执行)。在每种情况下,查询 returns 都是我希望看到的。但是当我通过 SSMS 查询 table 时,我发现更改只是有时会发生。
为什么从我的 MVC 应用程序直接查询 table 显示更新已完成,而我可以清楚地看到它不是通过 SSMS?有静默回滚之类的吗?这太令人抓狂了,我们将不胜感激。
一些信息:
- 当我在 MVC 之外 运行 下面的 assessmentContent class 中使用 "saveTheData" 函数时,它总是成功的。
- 更新总是在第一次 post 时成功。
- 更新在随后的 post 秒内只有大约一半的时间成功。
- 当更新不成功时,来自 MVC 应用程序的直接查询检查似乎确实显示更新一直到 table。
- 我几乎看不出失败的规律。也就是说,似乎每当我尝试更新到更高的 contentId 值时,它都会成功,如果我尝试更新到更低的 contentId,则不会。例如,从值 1(数学)更新到 2(阅读)将始终通过,但反之则不会。如果它是父视图中的第一个 post,或者如果它是通过 Linqpad 更新的,则此模式不会出现。
- 我在数据库 table 上放置了插入、更新和删除触发器,这些触发器写入日志记录 table 以查看更改是否正在回滚。但是失败时日志 table 上没有条目。但我也不知道回滚是否也会撤消触发器。
- 我查询了 dbo.fn_dblog() 过滤操作 = 'LOP_ABORT_XACT',但什么也没有(尽管我没有受过训练的眼睛看那个困难的观点)。
这是我的 class 获取和更新数据的方法:
public class assessmentContent {
public int? assessmentId { get; set; }
public List<short> baseline { get; set; } = new List<short>();
public List<short> comparison { get; set; } = new List<short>();
public assessmentContent() { if (assessmentId != null) refreshTheData(); }
public assessmentContent(int assessmentId) {
this.assessmentId = assessmentId;
refreshTheData();
}
public void saveTheData() {
List<short> upserts = comparison.Except(baseline).ToList();
List<short> deletes = baseline.Except(comparison).ToList();
foreach (var upsert in upserts)
reval.ach.addAssessmentContent(assessmentId, upsert);
foreach (var delete in deletes)
reval.ach.deleteAssessmentContent(assessmentId, delete);
refreshTheData();
}
void refreshTheData() {
baseline = reval.ach.assessmentContent(assessmentId).ToList();
comparison = reval.ach.assessmentContent(assessmentId).ToList();
}
}
当我在 MVC 应用程序之外使用它时,逻辑工作正常。因此,例如,如果我通过 linqpad 使用它,就没有问题。我应该提到 assessmentContent() 可以命名为 'getAssessmentContent()'.
这是我的局部视图控制器和一些相关代码:
public class ContentsModel {
public int? assessmentId { get; set; }
public List<short> comparison { get; set; }
}
public class ContentsController : Controller {
public static string nl = System.Environment.NewLine;
public ActionResult ContentsView(int assessmentId) {
ViewBag.DebugInfo = new List<string>();
var vm = new ContentsModel();
vm.assessmentId = assessmentId;
vm.comparison = reval.ach.assessmentContent(assessmentId).ToList();
return View("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
public ActionResult update(ContentsModel vm) {
ViewBag.DebugInfo = new List<string>();
sqlFetch();
ViewBag.DebugInfo.Add($"VM Pased In {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
var crud = new crud.ach.assessmentContent((int)vm.assessmentId);
ViewBag.DebugInfo.Add($"newly fetched CRUD {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.comparison = vm.comparison;
ViewBag.DebugInfo.Add($"CRUD after crud_comparison = vm_comparison {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.saveTheData();
ViewBag.DebugInfo.Add($"CRUD after save {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
vm.comparison = crud.comparison;
ViewBag.DebugInfo.Add($"VM after vm_comparison = crud_comparison {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
return PartialView("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
void sqlFetch() {
ViewBag.DebugInfo.Add(
"SQL Fetch " +
Sql.ExecuteOneColumn<short>("select contentId from ach.assessmentContent where assessmentId = 12", connections.research).intsJoin()
);
}
}
public static partial class extensions {
public static string intsJoin(this IEnumerable<short> ints) {
var strings = new List<string>();
foreach (int i in ints)
strings.Add(i.ToString());
return string.Join(",", strings);
}
}
我知道我可能没有在这里最好地实现 3 层架构或模型-视图-控制器结构。
您会注意到,在我绝望的时候,我在模型的每个更改点直接检查数据库 table。
局部视图:
@model reval.Views.ach.Contents.ContentsModel
@using reval
@{Layout = "";}
<div id="contentDiv">
<form id="contentForm">
@Html.HiddenFor(m => m.assessmentId)
@Html.ListBoxFor(
m => m.comparison,
new reval.ach.content()
.GetEnumInfo()
.toMultiSelectList(
v => v.Value,
d => d.DisplayName ?? d.Description ?? d.Name,
s => Model.comparison.Contains((short)s.Value)
),
new { id = "contentListBox" }
)
</form>
<br/>
@foreach(string di in ViewBag.DebugInfo) {
@Html.Label(di)
<br/>
}
</div>
<script>
$("#contentListBox").change(function () {
$.ajax({
url: "/Contents/update",
type: "get",
data: $("#contentForm").serialize(),
success: function (result) {
$("#contentDiv").html(result);
},
error: function (request, status, error) {
var wnd = window.open("about:blank", "", "_blank");
wnd.document.write(request.responseText);
}
});
})
</script>
最后,来自主视图的调用:
<div id="testDiv">
@if (Model.assessment != null && Model.assessment.assessmentId != null) {
Html.RenderAction("ContentsView", "Contents", new { assessmentId = Model.assessment.assessmentId });
}
</div>
您确定您对数据库进行的事务已提交或已完成吗?可能同时进行的其他事务可能会回滚您正在执行的事务。
我讨厌解决问题时没有明确说明我做了什么来解决它。但是在处理上面的一些代码之后,我让它正常且一致地工作。
不过,我几乎可以肯定,这个问题与对 asp.net mvc 在服务器与客户端之间来回传递数据时的工作方式的误解有关。也就是说,我有一个想法,当它们的数据被发送到客户端上的 html/asp 时,我在服务器上的 C# viewmodels 和控制器仍然存在。我有一种预感,即客户端数据与 C# 对象不同,但我确实感觉到 ASP.Net MVC 正在为回发时的任何更改更新 C# 对象。现在我很清楚,实际上 C# 对象已完全丢弃并完全实例化(调用构造函数和所有相关结果)并重新填充来自客户端的数据。即使在客户端未进行任何更改也是如此。
我认为实际上是在对数据库进行更新。没有回滚发生。但是在重新实例化时发生了一些事情,导致第二次调用数据库并重置它们的值。这可以解释为什么它在 ASP.net MVC 之外工作得很好。这将解释为什么我在意识到这一点后解决了这个问题。
我认为这个回答是准确的,但并不精确。我的意思是我有信心该指南可以解决问题,即使它没有确定上面有问题的代码的确切行数。由于准确性,我认为将其标记为答案是公平的游戏。由于不精确,如果其他人的回答更准确,我愿意将其标记为答案。不过上面的代码已经不用了,仅供学习使用。
我正在开发一个 ASP.Net MVC 应用程序,并且 运行 在尝试通过部分 post 返回更新我的数据库中的数据时遇到了一个奇怪的问题。对于 HTTP、AJAX 等,我还是个新手,所以我希望这是一个明显的错误。
基本上,当我尝试更新 table 将内容区域链接到评估时,更新有时有效,有时无效。奇怪的是,在我 post 之后,我直接从 MVC 应用程序 查询数据库 只是为了确保确实进行了预期的更改(这就是所有 ViewBag.DebugInfo's 在下面的代码中执行)。在每种情况下,查询 returns 都是我希望看到的。但是当我通过 SSMS 查询 table 时,我发现更改只是有时会发生。
为什么从我的 MVC 应用程序直接查询 table 显示更新已完成,而我可以清楚地看到它不是通过 SSMS?有静默回滚之类的吗?这太令人抓狂了,我们将不胜感激。
一些信息:
- 当我在 MVC 之外 运行 下面的 assessmentContent class 中使用 "saveTheData" 函数时,它总是成功的。
- 更新总是在第一次 post 时成功。
- 更新在随后的 post 秒内只有大约一半的时间成功。
- 当更新不成功时,来自 MVC 应用程序的直接查询检查似乎确实显示更新一直到 table。
- 我几乎看不出失败的规律。也就是说,似乎每当我尝试更新到更高的 contentId 值时,它都会成功,如果我尝试更新到更低的 contentId,则不会。例如,从值 1(数学)更新到 2(阅读)将始终通过,但反之则不会。如果它是父视图中的第一个 post,或者如果它是通过 Linqpad 更新的,则此模式不会出现。
- 我在数据库 table 上放置了插入、更新和删除触发器,这些触发器写入日志记录 table 以查看更改是否正在回滚。但是失败时日志 table 上没有条目。但我也不知道回滚是否也会撤消触发器。
- 我查询了 dbo.fn_dblog() 过滤操作 = 'LOP_ABORT_XACT',但什么也没有(尽管我没有受过训练的眼睛看那个困难的观点)。
这是我的 class 获取和更新数据的方法:
public class assessmentContent {
public int? assessmentId { get; set; }
public List<short> baseline { get; set; } = new List<short>();
public List<short> comparison { get; set; } = new List<short>();
public assessmentContent() { if (assessmentId != null) refreshTheData(); }
public assessmentContent(int assessmentId) {
this.assessmentId = assessmentId;
refreshTheData();
}
public void saveTheData() {
List<short> upserts = comparison.Except(baseline).ToList();
List<short> deletes = baseline.Except(comparison).ToList();
foreach (var upsert in upserts)
reval.ach.addAssessmentContent(assessmentId, upsert);
foreach (var delete in deletes)
reval.ach.deleteAssessmentContent(assessmentId, delete);
refreshTheData();
}
void refreshTheData() {
baseline = reval.ach.assessmentContent(assessmentId).ToList();
comparison = reval.ach.assessmentContent(assessmentId).ToList();
}
}
当我在 MVC 应用程序之外使用它时,逻辑工作正常。因此,例如,如果我通过 linqpad 使用它,就没有问题。我应该提到 assessmentContent() 可以命名为 'getAssessmentContent()'.
这是我的局部视图控制器和一些相关代码:
public class ContentsModel {
public int? assessmentId { get; set; }
public List<short> comparison { get; set; }
}
public class ContentsController : Controller {
public static string nl = System.Environment.NewLine;
public ActionResult ContentsView(int assessmentId) {
ViewBag.DebugInfo = new List<string>();
var vm = new ContentsModel();
vm.assessmentId = assessmentId;
vm.comparison = reval.ach.assessmentContent(assessmentId).ToList();
return View("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
public ActionResult update(ContentsModel vm) {
ViewBag.DebugInfo = new List<string>();
sqlFetch();
ViewBag.DebugInfo.Add($"VM Pased In {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
var crud = new crud.ach.assessmentContent((int)vm.assessmentId);
ViewBag.DebugInfo.Add($"newly fetched CRUD {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.comparison = vm.comparison;
ViewBag.DebugInfo.Add($"CRUD after crud_comparison = vm_comparison {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
crud.saveTheData();
ViewBag.DebugInfo.Add($"CRUD after save {crud.assessmentId} b{crud.baseline.intsJoin()} c{crud.comparison.intsJoin()}");
sqlFetch();
vm.comparison = crud.comparison;
ViewBag.DebugInfo.Add($"VM after vm_comparison = crud_comparison {vm.assessmentId} c{vm.comparison.intsJoin()}");
sqlFetch();
return PartialView("~/Views/ach/Contents/ContentsView.cshtml", vm);
}
void sqlFetch() {
ViewBag.DebugInfo.Add(
"SQL Fetch " +
Sql.ExecuteOneColumn<short>("select contentId from ach.assessmentContent where assessmentId = 12", connections.research).intsJoin()
);
}
}
public static partial class extensions {
public static string intsJoin(this IEnumerable<short> ints) {
var strings = new List<string>();
foreach (int i in ints)
strings.Add(i.ToString());
return string.Join(",", strings);
}
}
我知道我可能没有在这里最好地实现 3 层架构或模型-视图-控制器结构。
您会注意到,在我绝望的时候,我在模型的每个更改点直接检查数据库 table。
局部视图:
@model reval.Views.ach.Contents.ContentsModel
@using reval
@{Layout = "";}
<div id="contentDiv">
<form id="contentForm">
@Html.HiddenFor(m => m.assessmentId)
@Html.ListBoxFor(
m => m.comparison,
new reval.ach.content()
.GetEnumInfo()
.toMultiSelectList(
v => v.Value,
d => d.DisplayName ?? d.Description ?? d.Name,
s => Model.comparison.Contains((short)s.Value)
),
new { id = "contentListBox" }
)
</form>
<br/>
@foreach(string di in ViewBag.DebugInfo) {
@Html.Label(di)
<br/>
}
</div>
<script>
$("#contentListBox").change(function () {
$.ajax({
url: "/Contents/update",
type: "get",
data: $("#contentForm").serialize(),
success: function (result) {
$("#contentDiv").html(result);
},
error: function (request, status, error) {
var wnd = window.open("about:blank", "", "_blank");
wnd.document.write(request.responseText);
}
});
})
</script>
最后,来自主视图的调用:
<div id="testDiv">
@if (Model.assessment != null && Model.assessment.assessmentId != null) {
Html.RenderAction("ContentsView", "Contents", new { assessmentId = Model.assessment.assessmentId });
}
</div>
您确定您对数据库进行的事务已提交或已完成吗?可能同时进行的其他事务可能会回滚您正在执行的事务。
我讨厌解决问题时没有明确说明我做了什么来解决它。但是在处理上面的一些代码之后,我让它正常且一致地工作。
不过,我几乎可以肯定,这个问题与对 asp.net mvc 在服务器与客户端之间来回传递数据时的工作方式的误解有关。也就是说,我有一个想法,当它们的数据被发送到客户端上的 html/asp 时,我在服务器上的 C# viewmodels 和控制器仍然存在。我有一种预感,即客户端数据与 C# 对象不同,但我确实感觉到 ASP.Net MVC 正在为回发时的任何更改更新 C# 对象。现在我很清楚,实际上 C# 对象已完全丢弃并完全实例化(调用构造函数和所有相关结果)并重新填充来自客户端的数据。即使在客户端未进行任何更改也是如此。
我认为实际上是在对数据库进行更新。没有回滚发生。但是在重新实例化时发生了一些事情,导致第二次调用数据库并重置它们的值。这可以解释为什么它在 ASP.net MVC 之外工作得很好。这将解释为什么我在意识到这一点后解决了这个问题。
我认为这个回答是准确的,但并不精确。我的意思是我有信心该指南可以解决问题,即使它没有确定上面有问题的代码的确切行数。由于准确性,我认为将其标记为答案是公平的游戏。由于不精确,如果其他人的回答更准确,我愿意将其标记为答案。不过上面的代码已经不用了,仅供学习使用。