MVC5 Entity Framework学习之实现基本的CRUD功能

在上一篇文章中,我们使用Entity Framework 和SQL Server LocalDB创建了一个MVC应用程序,并使用它来存储和显示数据。在这篇文章中,你将对由 MVC框架自动创建的CRUD(create, read, update, delete)代码进行修改。

注意:通常我们在控制器和数据访问层之间创建一个抽象层来实现仓储模式,为了将注意力聚焦在如何使用实体框架上,这里暂没有使用仓储模式。

在本篇文章中,要创建的web页面:




1.创建一个Details页面

由框架代码生成的Students Index页面暂没有考虑Enrollments属性,因为该属性是一个集合。在Details页面中,我们将在HTML表格中显示集合中的内容。

打开 Controllers\StudentController.cs,可以看到对应Details视图的Details方法使用Find方法来检索单个学生实体:

public ActionResult Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

Details方法的id参数来自Index页面中Details链接,称为路由数据(route data)。

路由数据是指在路由表中指定,通过URL传递,由模型绑定器接收的数据。如下所示,默认路由指定了controller, action和 id

 routes.MapRoute(
    name: "Default",
    url: "{controller}/{action}/{id}",
    defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
在下面的 URL中,默认路由将Instructor 映射为controller, Index映射为action, 1 映射为 id

http://localhost:1230/Instructor/Index/1?courseID=2021
"?courseID=2021" 是查询字符串, 如果你将id作为查询字符串,模型绑定器也能正常解析

http://localhost:1230/Instructor/Index?id=1&CourseID=2021
在Razor视图中,由ActionLink语句来创建URL,如下面的代码中id参数匹配默认路由,所以id被作为进路由数据
 @Html.ActionLink("Select", "Index", new { id = item.PersonID  })
下面的代码中courseID参数 不匹配默认路由,所以courseID被作为查询字符串
@Html.ActionLink("Select", "Index", new { courseID = item.CourseID }) 


打开Views\Student\Details.cshtml,每个字段都使用DisplayFor帮助器来显示数据,如下面的代码所示:

<dt>
    @Html.DisplayNameFor(model => model.LastName)
</dt>
<dd>
    @Html.DisplayFor(model => model.LastName)
</dd>

在EnrollmentData字段之后,</dl>标签之前,添加下面的代码

        <dt>
            @Html.DisplayNameFor(model => model.EnrollmentDate)
        </dt>

        <dd>
            @Html.DisplayFor(model => model.EnrollmentDate)
        </dd>
        <dt>
            @Html.DisplayNameFor(model => model.Enrollments)
        </dt>
        <dd>
            <table class="table">
                <tr>
                    <th>Course Title</th>
                    <th>Grade</th>
                </tr>
                @foreach (var item in Model.Enrollments)
                {
                    <tr>
                        <td>
                            @Html.DisplayFor(modelItem => item.Course.Title)
                        </td>
                        <td>
                            @Html.DisplayFor(modelItem => item.Grade)
                        </td>
                    </tr>
                }
            </table>
        </dd>
    </dl>
</div>
<p>
    @Html.ActionLink("Edit", "Edit", new { id = Model.ID }) |
    @Html.ActionLink("Back to List", "Index")
</p>

如果代码缩进不正确,可以使用Ctrl-K-D快捷键来纠正它。 

上面的代码遍历Enrollments导航属性中的实体,对于每一个Enrollment实体,显示出Course Title 和Grade。Course Title是从Enrollments实体中的Course导航属性中的Course实体中获取的,所有这些数据豆是在需要时自动从数据库检索的。(换句话说,这里使用的是延迟加载。你没有为Courses导航属性指定预先加载,所以在同一次查询中,只检索了Students数据而没有检索enrollments数据。相反,在第一次试图访问Enrollments导航属性时,会创建一个新的查询并发送到数据库。

运行项目,选择Students 选项卡并点击名为Alexander Carson的Details 链接。(如果你按Ctrl+F5,直接打开Details.cshtml,会得到HTTP 400错误,因为Visual Studio会直接打开Details页面却没有指定任何一个studen,路由匹配错误导致程序出错。在这种情况下,你只需要从URL中删除Student/Details然后重试)

可以看到所选学生的courses 和grades



2.更新Create 页面

打开Controllers\StudentController.cs,使用下面的代码替换HttpPost Create方法

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "LastName,FirstMidName,EnrollmentDate")] Student student)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    db.Students.Add(student);
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
            }
            catch (DataException)
            {
                //Log the error (uncomment dex variable name and add a line here to write a log.
                ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
            }
            return View(student);
        }

上面的代码将ASP.NET MVC模型绑定器创建的Student实体添加到Students 实体集并保存到数据库中。(模型绑定器可以让你更容易的提交表单数据,可以将提交的表单值转换为CLR值并将它们作为参数传递给Controller中的方法。在本项目中,模型绑定器使用了表单集合中的属性值实例化了一个Student 实体)

这里删除了Bind 属性中的ID参数,因为ID是primary key,SQL Server在插入数据时会自动设置该值。

安全注意:ValidateAntiForgeryToken属性有助于防止跨站请求伪造(cross-site request forgery)攻击,但是需要在视图中设置相应的Html.AntiForgeryToken()语句。

Bind属性可以防止过份提交(over-posting)。举例来说,假设Student实体中包含一个Secret 字段,你不希望在Web页面中更新它

 public class Student
   {
      public int ID { get; set; }
      public string LastName { get; set; }
      public string FirstMidName { get; set; }
      public DateTime EnrollmentDate { get; set; }
      public string Secret { get; set; }

      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }

即使在Web页面中没有Secret字段,黑客也可以通过工具例如Fiddler或者JavaScript 将表单数据包括Secret值提交到服务器。如果不使用Bind属性来限制模型绑器需要的字段,模型绑定器会将接收到的Secret值更新至数据库中,下面的截图是通过Fiddler工具来提交表单数据




OverPost值将会被成功的更新至数据库,这是你不希望看到的。

为了安全起见,最好使用Bind属性的Include参数,也可以使用Exclude参数排除那些你不想要更新的属性。但是这里推荐使用Include,因为如果你在实体中添加了一个新的属性,Exclude并不会将这个新添加的属性排除在外。

另一种替代方法是在模型绑定时使用视图模型,视图模型中只包含你想要绑定的属性。

除了Bind属性,上面的代码中只需要加入try-catch块,如果在保存更改时引发DataException异常,就会在页面中显示相应的错误信息。DataException异常有时是由外部事件引发而不是因为程序错误,所以建议用户重试。记住在生产环境下,所有的应用程序错误都应该被记录下来。

Views\Student\Create.cshtml中的代码和Details.cshtml中的很相似,除了DisplayFor被EditorFor和ValidationMessageFor帮助器替代

<div class="form-group">
    @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
    <div class="col-md-10">
        @Html.EditorFor(model => model.LastName)
        @Html.ValidationMessageFor(model => model.LastName)
    </div>
</div>

Create.cshtml也包含了@Html.AntiForgeryToken()方法以防止跨站请求伪造攻击。

运行项目,选择Students选项卡,并点击Create New

输入姓名和无效的日期,然后单击Create查看错误消息


默认情况下使用的是服务器端验证,以后会教大家通过添加属性来生成客户端验证,下面的代码展示了Create 方法中的模型验证检查

if (ModelState.IsValid)
{
    db.Students.Add(student);
    db.SaveChanges();
    return RedirectToAction("Index");
}

修改日期为一个有效的值,点击Create,可以看到新添加的Student信息


3.更新Edit HttpPost页面

在Controllers\StudentController.cs中,HttpGet Edit方法(没有使用HttpPost属性的那一个)和Details方法一样使用Find方法来检索所选择的Student实体。

使用下面的代码替换HttpPost Edit方法:

        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "ID,LastName,FirstMidName,EnrollmentDate")] Student student)
        {
            try
            {
                if (ModelState.IsValid)
                {
                    db.Entry(student).State = EntityState.Modified;
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
            }
            catch (DataException /* dex */)
            {
                //Log the error (uncomment dex variable name and add a line here to write a log.
                ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
            }
            return View(student);
        }

上面的代码类似于HttpPost Create方法,但不同的是这里在实体中设置了一个标志位来指明它已经被更改,而不是将由模型绑定器创建的实体添加到实体集。当调用SaveChanges方法时,Modified标志会导致 Entity Framework创建SQL语句并更新数据库。数据库中该行的所有列都将被更新,包括那些用户并没有更改的,并忽略并发冲突。

实体状态、附加和SaveChanges方法

数据库上下文会一直跟踪内存中的实体是否与数据库中的行保持同步,并由此决定当调用SaveChanges方法时会发生什么,例如,当你调用Add方法添加实体时,该实体的状态会被设置为Added,然后当调用SaveChanges方法时,数据库上下文会生成一个SQL Insert命令。

一个实体可能处于以下状态之一:

  • Added,数据库并不存在该实体,SaveChanges方法必须生成一个Insert语句。
  • Unchanged,对该实体,SaveChanges方法什么都不需要做,当从数据库中读取一个实体时,该实体就为这一状态。
  • Modified,某些或所有实体的属性值被更改,SaveChanges方法必须生成一个Update语句。
  • Deleted。实体已被标志为删除状态,SaveChanges方法必须生成一个Delete语句。
  • Detached,实体没有被数据库上下文跟踪。

在桌面应用程序中,状态变化通常是自动的,当你读取一个实体并更改它的一些属性值,该实体的状态会自动更改为Modified,然后当你调用SaveChanges方法时,Entity Framework 会生成一个SQL Update来更新数据库。

DbContext 在读取一个实体并将其呈现到页面上后就会被销毁,当HttpPost Edit方法被调用,此时会生成一个新的请求和DbContext 实例,所以你必须手动设置实体状态为Modified,然后当你调用SaveChanges方法时,Entity Framework 会更新数据库行的所有列,因为数据库上下文没有办法知道你到底更改了哪些属性。

如果你希望SQL Update语句只更新那些用户实际更改的字段,你可以先将原来的值以某种方法(比如隐藏字段)保存起来,这样在调用HttpPost Edit方法时就可以使用它们,然后你可以使用原来的值来创建一个Student实体,调用Attach方法,并使用新的值更新该实体,最后调用SaveChanges方法。

Views\Student\Edit.cshtml 中的HTML 和Razor代码与Create.cshtml中的很类似。

运行项目,选择Students选项卡,点击其中一个学生的Edit链接


修改其中的值,点击Save,可以在Index页面中看到已经修改过的数据


4.更新Delete页面

在Controllers\StudentController.cs中,由模板生成的HttpGet Delete方法使用Find方法检索所选的Student实体。然而,当调用SaveChanges方法失败时为了显示自定义的错误信息,你需要向该方法和相对应的视图中添加一些功能。

就像update和create操作,delete操作也需要两个动作方法。用于响应Get请求的方法用来显示一个可以让用户<批准或取消delete操作的视图,如果用确认执行delete操作,此时会产生一条POST请求,并调用HttpPost Delete方法,该方法执行真正的delete操作。

在HttpPost Delete方法中添加try-catch块可以用来捕获数据库更新时可能出现的任何错误,如果出现了错误,则HttpPost Delete方法会调用HttpGet Delete方法,并向其传递一个参数指明发生了错误,然后HttpGet Delete会显示一个错误信息,并给用户一个取消或重试的机会。

使用下面的代码替换HttpGet Delete方法:

public ActionResult Delete(int? id, bool? saveChangesError=false)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }
    if (saveChangesError.GetValueOrDefault())
    {
        ViewBag.ErrorMessage = "Delete failed. Try again, and if the problem persists see your system administrator.";
    }
    Student student = db.Students.Find(id);
    if (student == null)
    {
        return HttpNotFound();
    }
    return View(student);
}

上面的代码接受一个可选择参数,指明该方法在保存更改出现错误后是否被调用。当HttpGet Delete方法不是由于出现错误而被调用的话,该参数值为false,当HttpPost Delete出现了错误而调用HttpGet Delete方法时该参数为true并在相应的视图上显示错误信息。

使用下面的代码替换HttpPost Delete方法(名称为DeleteConfirmed的那个),此方法用来执行真正的delete操作并捕获任何数据库更新错误

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Delete(int id)
{
    try
    {
        Student student = db.Students.Find(id);
        db.Students.Remove(student);
        db.SaveChanges();
    }
    catch (DataException/* dex */)
    {
        //Log the error (uncomment dex variable name and add a line here to write a log.
        return RedirectToAction("Delete", new { id = id, saveChangesError = true });
    }
    return RedirectToAction("Index");
}

上面的代码从数据库中检索要删除的实体,然后调用Remove方法将实体的状态设置为Deleted,最后调用SaveChanges方法并生成一条SQL Delete命令。另外你也可以将方法名DeleteConfirmed改为Delete。框架代码将HttpPost Delete方法命名为DeleteConfirmed是为了为其设置一个独一无二的名称(CLR重载方法需要有不同的参数)。现在遵守MVC的约定,HttpPost和HttpGet delete方法使用了相同的名字,并为它们设置不同的参数。

如果你想提高高访问量应用程序的性能,你要避免使用不必要的SQL查询。使用下面的代码替换Find和Remove方法

Student studentToDelete = new Student() { ID = id };
db.Entry(studentToDelete).State = EntityState.Deleted;

上面的代码使用唯一的主键值实例化了一个学生实体并设置实体状态为Deleted,这便是Entity Framework为了删除一个实体所需要做的动作。

如前所述HttpGet Delete方法并不会执行数据删除操作,在一个Get请求响应中执行delete操作(执行任何edit操作、create操作或者其它对数据进行更改的操作)将带来安全风险。

在Views\Student\Delete.cshtml中添加错误信息

<h2>Delete</h2>
<p class="error">@ViewBag.ErrorMessage</p>
<h3>Are you sure you want to delete this?</h3>

运行项目,点击Students选项卡,点击其中一个学生的Delete链接:


点击Delete,你会看到在Index页面中该学生已经被删除。

5.确保数据库连接适时关闭

要确保数据库连接被正确的关闭并释放所占用的资源,在你使用完数据库上下文时,必须要将其销毁,这就是为什么框架代码在StudentController.cs的最后部分提供了一个Dispose方法

protected override void Dispose(bool disposing)
{
    db.Dispose();
    base.Dispose(disposing);
}

Controller类实现了IDisposeable接口,所以上面的代码通过重写Dispose(bool)方法来显式的销毁数据库上下文实例。

6.处理事务

默认情况下,Entity Framework隐式的实现事务处理。当你对多行或者多个表进行更改后调用SaveChanges方法,Entity Framework会自动确保所有更改要么全部成功要么全部失败。如果已经做完一些更改后发生了一个错误,那么所有的更改包括已做完的都将自动回滚。


欢迎转载,请注明文章出处:http://blog.csdn.net/johnsonblog/article/details/38711659

博客搬家啦,我的小站:MVC5 Entity Framework学习(2):实现基本的CRUD功能

还大家一个健康的网络环境,从你我做起

项目源码:https://github.com/johnsonz/MvcContosoUniversity

THE END


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: .NET MVC5是一种用于构建Web应用程序的框架,它基于Microsoft .NET平台。其中,Admin部分通常用于管理和维护应用程序的后台功能。 .NET MVC5 Admin可以让开发人员轻松创建一个带有管理员功能的后台管理系统。这个管理员后台系统可以用来管理各种数据,例如用户信息、产品列表、订单信息等等。使用MVC5 Admin,开发人员可以快速构建一个功能强大、安全可靠的后台管理系统。 .NET MVC5 Admin提供了一些重要的功能和特性。首先,它提供了身份验证和授权功能,可以通过身份验证来保护管理员后台。只有经过身份验证的用户才能访问后台系统,确保了系统的安全性。 其次,.NET MVC5 Admin还提供了简单易用的用户界面和操作方式。开发人员可以使用预定义的模板和组件来创建页面和控件,从而大大减少了开发时间和工作量。 此外,.NET MVC5 Admin还支持自定义和个性化的功能。开发人员可以根据具体的需求进行修改和扩展,以满足不同的业务要求。 总而言之,.NET MVC5 Admin是一种强大的工具,可以帮助开发人员快速、高效地构建后台管理员系统。它具有安全性高、易用性好、可扩展性强等特点,适用于各种规模的Web应用程序。无论是小型的个人网站,还是大型的企业门户,.NET MVC5 Admin都可以满足各种需求,提供一个出色的管理员后台系统。 ### 回答2: .NET MVC5 Admin是一个基于.NET MVC5框架的管理员控制面板。 .NET框架是由微软公司开发的一个开发平台,用于构建应用程序。MVC(Model-View-Controller)是一种设计模式,将应用程序分为模型、视图和控制器三个部分,使应用程序更加可维护和可扩展。 Admin是指管理员,通常拥有对系统进行管理和维护的权限。在.NET MVC5 Admin中,管理员可以通过控制面板对系统进行配置和管理。 .NET MVC5 Admin提供了一系列功能,例如用户管理、角色管理、权限管理、日志管理等。管理员可以通过用户管理来管理和控制系统的用户,包括添加用户、删除用户、编辑用户信息等操作。角色管理可以帮助管理员定义不同的角色,并分配相关权限。权限管理可以让管理员对不同的角色设置不同的操作权限,以保证系统安全和合规性。日志管理记录了系统的操作日志,方便管理员进行追踪和审计。 .NET MVC5 Admin还可以集成其他功能模块,例如文件管理、邮件发送等。管理员可以根据具体需求进行扩展和定制。 总之,.NET MVC5 Admin是一个基于.NET MVC5框架的管理员控制面板,提供了用户管理、角色管理、权限管理、日志管理等功能,方便管理员对系统进行配置和管理。它的灵活性和可扩展性使得管理员可以根据具体需求进行定制和拓展。 ### 回答3: .NET MVC5是一种用于构建Web应用程序的框架,而“admin”指的是应用程序的管理员界面或管理功能。在.NET MVC5中,可以使用一些技术来实现管理员界面。以下是一种可能的方法: 首先,可以创建一个用于管理功能的控制器。该控制器将处理所有与管理员相关的操作,例如创建、编辑和删除数据。接下来,可以创建相应的视图,用于显示管理员操作所需的页面和表单。 在视图中,可以使用.NET MVC提供的HTML助手和页面布局来构建用户界面。例如,可以使用HTML表单控件来创建输入字段,使用HTML按钮来提交表单数据。此外,还可以使用CSS和JavaScript来美化和增强管理员界面的外观和交互性。 在控制器中,可以使用.NET MVC提供的模型绑定功能来处理输入数据,并使用Linq或Entity Framework等数据访问技术与数据库进行交互。通过这种方式,可以对管理员功能进行CRUD操作(创建、读取、更新和删除数据)。 此外,可以使用身份验证和授权技术来限制对管理员功能的访问。例如,可以使用ASP.NET身份验证来验证管理员的身份,并使用角色基于访问控制来限制不同角色的管理员所能执行的操作。 总结起来,通过使用.NET MVC5框架和相关技术,可以很容易地构建一个功能强大的管理员界面。该界面可以用来管理数据、执行各种操作并对用户进行身份验证和授权。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值