ASP.NET MVC 编辑复合主键的值






4.86/5 (16投票s)
ASP.NET MVC 编辑复合主键的值
引言
关于如何在更新数据库中的主键值时处理复合键,网上有很多争论。
使用 ASP.NET MVC,您无法更新表中的主键,这迫使具有传统数据库技能的人员为其复合键表引入代理键。我个人不喜欢为我的复合键表添加代理键。
架构
我搜索了很多解决方案,但找不到任何答案。所以我尝试自己解决这个谜团。经过一天的努力,我找到了一个非常简单的解决方案,并想在本文中与您分享。
我有一个 5 张表,如下所示。我想要一个可以为某个地点添加日程的表,并且希望能够灵活地更改日程的日期、时间和语言。我的 Schedules
表有四个复合键。
基本的 Visual Studio MVC 项目
创建一个新的 MVC 项目
为我们的五张表添加五个模型
- 地点
- 天数
- 时间
- 语言
- 计划
按如下所示添加地点、日期、时间、语言和日程表的字段
public class Locations
{
[Key]
public int LocationID { get; set; }
public string Location { get; set; }
}
public class Days
{
[Key]
public int DayID { get; set; }
public string Day { get; set; }
}
public class Times
{
[Key]
public int TimeID { get; set; }
public string Time { get; set; }
}
public class Languages
{
[Key]
public string LanguageID { get; set; }
public string Language { get; set; }
}
public class Schedule
{
[Key]
[Column(Order = 0)]
public int LocationID { get; set; }
[Key]
[Column(Order = 1)]
public int DayID { get; set; }
[Key]
[Column(Order = 2)]
public int TimeID { get; set; }
[Key]
[Column(Order = 3)]
public string LanguageID { get; set; }
public string Notes { get; set; }
public virtual Locations Location { get; set; }
public virtual Days Day { get; set; }
public virtual Times Time { get; set; }
public virtual Languages Language { get; set; }
}
从“工具”->“库包管理器”->“程序包管理器”打开 Visual Studio 程序包管理器控制台。
键入“Install-Package EntityFramework”以为此项目安装 EntityFramework。
安装完成后,在 models 文件夹中添加另一个类,并将其命名为“LocationScheduleContext
”,并确保将其继承 DbContext
,如下所示
public class LocationScheduleContext: DbContext
{
}
然后,在类中为每个表添加 DbSet
,如下所示
public class LocationScheduleContext : DbContext
{
public DbSet<Locations> Locations { get; set; }
public DbSet<Languages> Languages { get; set; }
public DbSet<Days> Days { get; set; }
public DbSet<Times> Times { get; set; }
public DbSet<Schedule> Schedule { get; set; }
}
生成您的项目(CTRL + SHFT + B)。
现在,为所有表添加控制器,并确保选择“使用 Entity Framework 的控制器(带视图)”。
添加 LocationsController
、DaysController
、TimesController
、LanguageController
和最后的 ScheduleController
作为控制器。
确保选择与控制器名称匹配的正确模型类。(例如,如下面的 LocationsController
使用的是 Locations Model 类)。
别忘了选择我们创建的上下文 Class
“LocationScheduelContext
”。
确保选中所有其他复选框,如下所示,然后单击“添加”。
您的控制器应该如下所示
Visual Studio 会自动为您创建所有视图。
选择您喜欢的浏览器运行项目并单击运行。
项目将打开一个 ASP.NET MVC 默认页面。因此,在 URL 中添加“Days”以查看 Days 页面,如下所示
单击“创建新”按钮,并添加一周中的所有七天(星期日、星期一、星期二……),如下所示
现在,通过在 URL 中将 Days 更改为 Times 来打开 Times 页面,并按如下所示添加一些时间
打开 Languages 页面并按如下所示添加一些语言。(确保将 LanguageID
设置为 en 代表英语,es 代表西班牙语……等)
确保转到 locations 页面并按如下所示添加一些地点
现在转到 Schedule 页面并按如下所示添加一些日程
尝试单击“编辑”、“详细信息”或“删除”按钮……您将看到“Bad Request”错误。这是因为您没有将 ID 传递给 View…
转到 Views 并打开 Schedule Index 页面,并按如下所示添加复合主键
<td>
@Html.ActionLink("Edit", "Edit", new { LocationID = item.LocationID, DayID = item.DayID, TimeID = item.TimeID, LanguageId = item.LanguageID}) |
@Html.ActionLink("Details", "Details", new { LocationID = item.LocationID, DayID = item.DayID, TimeID = item.TimeID, LanguageId = item.LanguageID }) |
@Html.ActionLink("Delete", "Delete", new { LocationID = item.LocationID, DayID = item.DayID, TimeID = item.TimeID, LanguageId = item.LanguageID })
</td>
我们需要按如下所示将这些复合键传递给 Schedule 控制器中的 Edit、Delete 和 Details 函数
public ActionResult Edit(int LocationID, int DayID, int TimeID, string LanguageID)
{
Schedule schedule = db.Schedule.Find(LocationID, DayID, TimeID, LanguageID);
if (schedule == null)
{
return HttpNotFound();
}
ViewBag.DayID = new SelectList(db.Days, "DayID", "Day", schedule.DayID);
ViewBag.LanguageID = new SelectList(db.Languages, "LanguageID", "Language", schedule.LanguageID);
ViewBag.LocationID = new SelectList(db.Locations, "LocationID", "Location", schedule.LocationID);
ViewBag.TimeID = new SelectList(db.Times, "TimeID", "Time", schedule.TimeID);
return View(schedule);
}
运行您的应用程序,并尝试编辑现有行。您会发现所有复合键值都已添加到查询字符串中,您会发现只能向 notes 字段添加内容,并且没有能力更改主键值,也无法更改日程表中的 Day、Time、Language。
由于我们没有代理键,因此无法更改复合键表的 LocationID
、DayID
、TimeId
和 LanguageID
的值。但是我们有一个变通方法可以实现这一点。
从 Schedule 文件夹中打开 Create.cshtml 和 Edit.cshtml 文件。从 Create.cshtml 中,复制 LocaionID
、DayID
、TimeID
和 LanguageID
的 form-group 字段。现在打开 Edit.cshtml 并将它们粘贴到隐藏字段(LocaionID
、DayID
、TimeID
和 LanguageID
)上方,如下所示。请确保隐藏字段未被删除。
<div class="form-horizontal">
<h4>Schedule</h4>
<hr />
@Html.ValidationSummary(true)
<div class="form-group">
@Html.LabelFor(model => model.LocationID, "LocationID", new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("LocationID", String.Empty)
@Html.ValidationMessageFor(model => model.LocationID)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.DayID, "DayID", new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("DayID", String.Empty)
@Html.ValidationMessageFor(model => model.DayID)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.TimeID, "TimeID", new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("TimeID", String.Empty)
@Html.ValidationMessageFor(model => model.TimeID)
</div>
</div>
<div class="form-group">
@Html.LabelFor(model => model.LanguageID, "LanguageID", new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.DropDownList("LanguageID", String.Empty)
@Html.ValidationMessageFor(model => model.LanguageID)
</div>
</div>
@Html.HiddenFor(model => model.LocationID)
@Html.HiddenFor(model => model.DayID)
@Html.HiddenFor(model => model.TimeID)
@Html.HiddenFor(model => model.LanguageID)
<div class="form-group">
@Html.LabelFor(model => model.Notes, new { @class = "control-label col-md-2" })
<div class="col-md-10">
@Html.EditorFor(model => model.Notes)
@Html.ValidationMessageFor(model => model.Notes)
</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>
现在运行程序,并尝试更改 Time 或 Day 的一个下拉值,您将收到“System.Data.Entity.Core.OptimisticConcurrencyException
”错误。这基本上表明您无法更改主键值。
现在打开 Schedule Controller,我们将向 Edit Post 函数添加几行代码,以捕获查询字符串值,并在将新的选定值添加到数据库之前将其从数据库中删除。当表单提交时,我们将使用查询字符串值删除数据,并更新我们在表单上进行的新更改。
如果您查看下面的 ScheduleController
,您可以看到当 ModelState.IsValid
为真时,我捕获了查询字符串值,如 OLocationID
、OTimeID
、ODayID
和 OLanguageID
,在数据库中进行查询并将其附加到 services 变量。
然后,我遍历 services 并将它们从数据库中删除。我的下一个函数是添加所有新值并更新到数据库。然后我们将尝试保存函数。如果我们遇到错误,我们将捕获并返回到 Index 页面。这个 try catch
函数主要是为了避免重复。如果您重复添加相同的日程数据,我们将被重定向到 Index 页面。您可以使用此 try
方法重定向到另一个页面并警告他们有关重复条目的信息。
public ActionResult Edit([Bind(Include="LocationID,DayID,TimeID,LanguageID,Notes")] Schedule schedule)
{
if (ModelState.IsValid)
{
int OLocationID = Convert.ToInt32(Request["LocationID"]);
int OTimeID = Convert.ToInt32(Request["TimeID"]);
int ODayID = Convert.ToInt32(Request["DayID"]);
string OLanguageID = Request["LanguageID"].ToString();
var services = db.Schedule.Where(a => a.LocationID == OLocationID)
.Where(a => a.TimeID == OTimeID)
.Where(a => a.DayID == ODayID)
.Where(a => a.LanguageID == OLanguageID);
foreach (var s in services)
{
db.Schedule.Remove(s);
}
db.Schedule.Add(schedule);
try
{
db.SaveChanges();
}
catch
{
return RedirectToAction("Index");
}
db.Entry(schedule).State = EntityState.Modified;
return RedirectToAction("Index");
}
ViewBag.DayID = new SelectList(db.Days, "DayID", "Day", schedule.DayID);
ViewBag.LanguageID = new SelectList(db.Languages, "LanguageID", "Language", schedule.LanguageID);
ViewBag.LocationID = new SelectList(db.Locations, "LocationID", "Location", schedule.LocationID);
ViewBag.TimeID = new SelectList(db.Times, "TimeID", "Time", schedule.TimeID);
return View(schedule);
}
现在运行项目,您会发现您可以更改此复合键表的所有主键值。
结论
此解决方案是一种在不添加代理键的情况下处理复合键的变通方法。
尽情享受……