用Entities Framework实现代码优先和依赖注入所遇到的问题总结
最近在搞毕业设计,底层到EF来实现与数据库的连接。由于是第一次用EF来做项目,遇到了种种特别奇怪的出错。
现在,总结自己所遇到的问题,方便以后避免。
第一就是要引用两个包:
用Nuget包引入:Moq和Ninject
以上两个包用于实现依赖注入。
第二EFDbContext里的实体要和数据库里的表致,不然就会出现,EFDbContext修改了数据库的数据,而数据库里的数据却没有被修改,总之就是要匹配。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Data.Entity; 6 using System.Data.Objects; 7 using System.Data; 8 9 namespace DAL.Entities 10 { 11 public class EFDbContext : DbContext 12 { 13 public DbSet<Account> Account { get; set; } 14 public DbSet<Address> Address { get; set; } 15 public DbSet<Book> Book { get; set; } 16 public DbSet<BookCart> BookCart { get; set; } 17 public DbSet<Cart> Cart { get; set; } 18 public DbSet<Category> Category { get; set; } 19 public DbSet<City> City { get; set; } 20 public DbSet<Menu> Menu { get; set; } 21 public DbSet<Orders> Orders { get; set; } 22 public DbSet<Province> Province { get; set; } 23 } 24 }
特别是注意一点:就是表名会在其类名上加上字符“s”,我也还没有找出是什么原因。所以加上就不会报错。
对应的数据库如下图:
最后就是配置一点要写对:
<add name="EFDbContext" connectionString="Data Source=.;Initial Catalog=BookStore;Integrated Security=True" providerName="System.Data.SqlClient" />
第五步:更改数据模型
随着我们应用程序开发的推进,我们将继续改进和重构我们的模型。EF 代码优先类库包括一些很好的开发功能,这使我们在开发数据库时更容易协调某些改进。
给Dinner(餐会)模型添加一个新的属性
让我们对我们的Dinner类做一个简单的修改,更具体的说,我们将给Dinner类添加一个新的“Country”属性。
做完改动,让我们在Virtual Studio中按下F5按钮,重新编译并运行应用程序。将会看到下面的这些错误信息:
这些错误信息之所以出现,是因为我们修改了Dinner类的结构定义,我们的模型对象现在已经不再和数据库中自动创建的Dinners表一致了。
当EF自动地为你创建数据库时,默认地会增加一个“EdmMetadata”表,这个表用来记录自动创建数据库时使用的模型对象的形状。
当EF发现你修改过模型对象,并且和之前自动创建的数据库不再同步时,就会出现上面的错误消息。
也有可能就是在系统表里多出一个表格,应该是系统生成的:
所以如果出现这样的问题,就先到数据库看下是不是多了表。
重新同步数据模型类到数据库
我们有很多同步模型对像和数据库的方式:
- 我们可以手动地更新数据库中的结构(Schema)让它们保持一致。
- 我们也可以先删除数据库文件,然后重新运行应用程序,让EF自动重新创建数据库。
- 我们也可以开启EF 代码优先功能,当数据模型发生任何改变时能够自动更新数据库 。
下面,让我们在NerdDinner应用程序中看看如何使用最后一种的自动更新的方式。
当模型对象发生变化时重新创建数据库(RecreateDatabaseIfModelChanges)功能
在EF 代码优先类库的CTP 4版本中包括了一个非常有用的开发时(development-time)功能,它允许你在任意时刻修改数据模型类,自动重建数据库。当你开启这项功能的时候,EF能够识别用来创建数据库的类模型在何时被改动,何时可以重建你的数据库以匹配新的模型类——你不需要做任何手工操作。
这项功能在你刚开发一个应用程序时特别实用,因为它为你快速地重构你的模型代码带来了很大的自由度和灵活性——你根本不用去手动地保持数据库结构的同步。它特别适合SQL CE,因为SQL CE是一个基于文件的数据库而且可以随时在运行时删除和创建。这使得开发流程变得不可思议的流畅。
启用这项功能最简单的方法就是在Global.asax类中的Application_Start()事件处理函数中加上Database.SetInitializer()方法的调用。
这个调用告诉EF当数据模型发生任何变化时,重建NerdDinners.sdf数据库以匹配NerdDinners类。现在当我们重新运行应用程序的时候,再也不会出现提示说类模型和数据库不匹配的错误信息了。反而,EF会自动重建数据库以匹配新的数据模型类,我们的应用程序会正常运行:
防止表名生成带“s”
创建一个名为 DAL 的文件夹,在这个文件夹中创建名为 SchoolContext 的类,使用下面的代码替换原有的代码。
using System;
using System.Collections.Generic;
using System.Data.Entity;
using ContosoUniversity.Models;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace ContosoUniversity.Models
{
public class SchoolContext
: DbContext
{
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
这段代码使用 DbSet 类型的属性定义每一个实体集。在 EF 术语中,一个实体集典型地关联到一个数据库中的表,一个实体关联到表中的一行。
在 OnModelCreating 方法中的代码防止数据库中的表名被多元化。如果没有这样处理的话,生成的表将会被命名为 Students,Courses 和 Enrollments。现在,表名将会被命名为 Student,Course 和 Enrollment。开发者可以决定表名是否被多元化。这个教程使用单数模式,但是重要的是你可以这行代码来选择喜欢的模式。
第三写下依赖注入的实现过程
首先要扩展下控制器
代码如下
using System; using System.Collections.Generic; using System.Linq; using System.Web; using Ninject; using Moq; using System.Web.Routing; using System.Web.Mvc; using DAL.Concrete.Framework; using DAL.Entities; using IBLL.Framework; using BLL.Framework; namespace BookStoreUI.Infrastructure { public class NinjectControllerFactory : DefaultControllerFactory { private IKernel ninjectKernel; public NinjectControllerFactory() { ninjectKernel = new StandardKernel(); AddBindins(); } protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType) { return controllerType == null ? null : (IController)ninjectKernel.Get(controllerType); } //绑定数据 private void AddBindins() { //Mock<IProductRepository> mock = new Mock<IProductRepository>(); //mock.Setup(h => h.Products).Returns( // new List<Product> { // new Product{Name="FootBall",Price=25 }, // new Product{Name="Surf Board",Price=179}, // new Product{Name="Running shoes" ,Price=95} // }.AsQueryable()); //this.ninjectKernel.Bind<IProductRepository>().ToConstant(mock.Object); //采用数据库中的数据来显示,所以要把上面的注释掉,这里就写自己的要实现注入的方法和接口 this.ninjectKernel.Bind<IAccountBLL>().To<AccountBLL>(); } } }
然后要Gobal.asp.cs下面加载这个方法使得程序运行开始就知道控制器被扩展。
不然重写控制器时就出出错。
代码如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using BookStoreUI.Infrastructure; using System.Data.Entity; using DAL.Entities; namespace BookStoreUI { // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); //Database.SetInitializer<EFDbContext>(null); //我们需要注册NinjectControllerFactory到MVC框架,所以我们也需要在Global.asax.cs里给它注册进去 ControllerBuilder.Current.SetControllerFactory(new NinjectControllerFactory()); } } }
最后在控制器里就可以实现你所用的注入方法。
直接上代码:
1 using IBLL.Framework; 2 using DAL.Entities; 3 4 namespace BookStoreUI.Controllers 5 { 6 public class AccountController : Controller 7 { 8 private IAccountBLL iAccountBLL; 9 10 public AccountController(IAccountBLL accountBLL) 11 { 12 iAccountBLL = accountBLL; 13 } 14 15 public ActionResult Index() 16 { 17 return View(); 18 } 19 20 [HttpPost] 21 public ActionResult Index(LoginViewModel model) 22 { 23 //验证码验证 24 if (Session["__VCode"] == null || (Session["__VCode"] != null 25 && model.ValidateCode != Session["__VCode"].ToString())) 26 { 27 ModelState.AddModelError("ValidateCode", "验证码出错"); 28 return View(); 29 } 30 31 if (ModelState.IsValid) 32 { 33 Account account = iAccountBLL.ValidateAccount(model.Account.UserName, model.Account.PassWord); 34 35 //登录是否成功 36 if (account != null) 37 { 38 return RedirectToAction("BackIndex", "BackHome"); 39 } 40 } 41 } 42 }