基于Oracle的EntityFramework的WEBAPI2的实现(一)——准备工作
目前在.net的范围内,好的而且方便的ORM的真的不是很多,与VS集成方便的也就当属EntityFramework(以下简称EF,不知道为什么,总EF这个缩写好不专业)。但是,好多公司使用的又是ORACLE,导致使用EF的时候会出现各种不如意的情况,包括搭建环境的时候,都很蛋疼。最近终于有一个项目上了这种方式,而且很成功,所以,在这里跟大家分享一下,也希望有问题的地方大家指出来,一起改进。
环境配置由以下几个重要点:
- Oracle的ODP安装(包含Oracle调用控件和Oracle的vs工具)
- Webapi项目的建立
- 微软EntityFramework6的安装
- 将Oracle的托管引用改为非托管引用(可以脱离目标环境的位数限制)
ODP.NET的安装
因为安装ODP.NET后,我们需要重启VS,所以,我们先来安装ODP.NET再来新建项目。
下载oracle的dbac(x86)下载地址(不能下载请留言),关掉所有vs,然后安装下载下来的压缩包里面的setup.exe。安装设置的两个目录,一定不要有特殊字符,可以用同一个,@,()等千万别有。
Webapi项目的建立
打开vs的新建项目界面,选C#-WEB-ASP.NET MVC,然后填写项目名称,选择项目所在位置,点确定。
继而弹出新的窗口,按图中选择操作:
然后点击确定,就会打开一个项目,其它的配置如同其它项目一样。
引入EntityFramework
打开工具,选择Nuget包工具管理,如果图打开Nuget包管理器:
选择联机,找到entity framework,点安装,安装到刚刚我们的WebApi项目中。
引用Oracle的ODP.NET
右击项目的引用·添加新的引用·在扩展里面找到Oracle.ManagedDataAccess以及Oracle.ManagedDataAccess.EntityFramework【注意1】。然后确定。此时,项目中就成功引用了这两个东西。
将如下web.config内容加入到web.config中,注意如果有相同的,则将原有的替换掉。
<configSections> <section name="oracle.manageddataaccess.client" type="OracleInternal.Common.ODPMSectionHandler, Oracle.ManagedDataAccess, Version=4.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342" /> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --> <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> <connectionStrings> </connectionStrings> <entityFramework> <defaultConnectionFactory type="Oracle.ManagedDataAccess.EntityFramework.OracleConnectionFactory, Oracle.ManagedDataAccess.EntityFramework, Version=6.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342" /> <providers> <provider invariantName="Oracle.ManagedDataAccess.Client" type="Oracle.ManagedDataAccess.EntityFramework.EFOracleProviderServices, Oracle.ManagedDataAccess.EntityFramework, Version=6.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342" /> </providers> </entityFramework> <system.data> <DbProviderFactories> <remove invariant="Oracle.ManagedDataAccess.Client" /> <add name="ODP.NET, Managed Driver" invariant="Oracle.ManagedDataAccess.Client" description="Oracle Data Provider for .NET, Managed Driver" type="Oracle.ManagedDataAccess.Client.OracleClientFactory, Oracle.ManagedDataAccess, Version=4.121.2.0, Culture=neutral, PublicKeyToken=89b483f429c47342" /> </DbProviderFactories> </system.data> <oracle.manageddataaccess.client> <version number="*"> <settings> <setting name="TNS_ADMIN" value="C:TNS_ADMIN" /> <setting name="TraceOption" value="1" /> <setting name="PerformanceCounters" value="0" /> </settings> </version> </oracle.manageddataaccess.client>
需要注意的是,[TNS_ADMIN]这个东西后面的value指向的是放TNS文件的目录,如果你不是用TNS文件来管理数据库的IP地址,而是在连接字符串中直接使用IP连接,则可以不写这个SETTING项。
至此,准备工作全部完成。
【注意1:如果引用的时候找不到Oracle.ManagedDataAccess以及Oracle.ManagedDataAccess.EntityFramework,可以在下面的ODP安装包中,找到这两个文件,解压出来放到项目里面,然后直接对这两个文件进行引用即可。】
基于Oracle的EntityFramework的WEBAPI2的实现(二)——使用DbFirst
之所以使用DbFirst而没有使用CodeFirst是因为考虑到现实的情况中,我们之所以会选择oracle而不是SQL SERVER,一方面是因为之前公司已经在使用Oracle,而且有好多我们需要用到的数据表已经存在了。所以,一般可以确认的是,部分表已经事先存在的这情况是有的,而CodeFirst虽然也有办法解决,但是,我们的Leader,同事似乎也会习惯手动改表而非告诉你让你写Code然后改表。所以DbFirst是一个不错的选择,其实也算是代替了代码生成品的一部分功能。
右击我们的项目,点添加·新建项·C#·数据·ADO.NET实体数据模型,取个名字,比如“OrclModel”点确定。
弹出如下的界面
选择第一个【来自数据库的EF设计器】,下一步。
按图中的箭头依次操作,注意如下:
- 【将web.config中的连接设置另外为】的地主一定要写一个好看的名字,因为后面使用linq的时候,都要用到它,建议使用Db开头Con结尾,中间是数据库库名的缩写或者名称。
- 【测试连接】一定要试,否则后面出问题就不好办了。
- 【是否将连接字符串加密】一般我是选不加的,毕竟是自己的服务器上面,人家都进了服务器了,而且是.NET,你在应用程序中设置此数据根本没用,人家ILSPY直接可以看到你的源码,根本没用,所以,为了方便,也不废那么多事儿了,就直接选择明文。
- 【连接类型】如果选TNS,那么在前一篇中web.config里面的setting部分,TNS_ADMIN是一定要加上的,否则会找不到TNS文件。
完成后,点击确定。后面就是熟悉的entityframework操作部分,与微软官网的走法一模一样,此处也不必再涉及过多,只是【选表】作映射的时候,尽量只选我们用到的表,毕竟,之前数据库已经存在了,而且里面有大量的我们用不到的表,多则无益。生成了edmx文件后,点一下保存,然后重新生成项目(原因是如果不重新生成项目 ,虽然映射文件有了,但是T4代码并没有生成cs文件,所以我们与表映射的类暂时还没有,我们可以使用重新生成来触发T4的编译工作)。
基于Oracle的EntityFramework的WEBAPI2的实现(三)—— 建立APIController及设置返回类型JSON、XML等
建立普通的ApiControler
右击项目中的controller文件夹·添加·控制器·包含操作的webapi2控制器(使用entity framework),写个名字,如果:Test。然后选择类,就是我们刚刚从数据库映射过来的类,然后选择我们之前设置的连接字符串名称(我前面提到最好以Db开头,Con结尾的那个东西),点确定,然后会发现controller文件夹中多出了一个类TestContoller。
打开TestController.cs,看见里面帮助我们生成了GET,PUT,POST等方法。运行项目,发现已经可以请求到这个项目的GET方法了(我们可以在数据库里面手动加几条数据,我们就会看见返回数据了,正常 情况下,返回的是200我就认为是正确的请求)。
使用Area
前面我们是在默认的Controller中建立的ApiController,但是,我们的项目一般是复杂类型的,不可将所有的东西放在一个文件夹中,所以,我们要使用Area(区域)。右击项目中的Area文件夹,添加·区域,起个名字,然后确定。就会发现,在Area目录中多了那个我们的文件夹(假如名字叫TArea)。目录结构如图:
其实相当于我们又建立了一个子项目。但是有地方需要我们进行修改,就是路由,原因有如下几点:
- 路由可能会请求不到我们自定义的区域。
- 我区域中的控制器的命名空间和类名可能会和外部的控制器重复,导致路由找到两个。
那么我们就要做如下几点:
- 修改WEBAPI主路由设置类,将WebApiConfig.cs中的路由加上Area这个项。
- 修改Area下我们的区域内的AdminAreaRegistration.cs这个文件的内容。
个性WEBAPI主路由设置
代码如下:
public static void Register(HttpConfiguration config) { // Web API 配置和服务 config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/plain")); config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html")); config.Formatters.JsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); // Web API 路由 config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute( name: "DefaultAreaApi", routeTemplate: "Api/{area}/{controller}/{id}", defaults: new { id = RouteParameter.Optional } ); config.Filters.Add(new WebApi.Filters.ExceptionHandlingAttribute()); }
config.Formatters的三条语句,其实就是在这一步顺便加上的,因为文章没必须再开一个区域写这段代码。config.Formatters是用来设置转换器的,一旦加上了text/plain和application/json等(默认xml已经有了),就可以根据用户请求时的content-type来返回与其一样的格式的内容。如,用户请求时content-type写的是application/json,数据也是application/json格式的,那么得到的返回的值也会是application/json格式。xml和text/html相同的原理。当然,如果用户在Accept头中强写了application/json等指定类型,那么不管用户请求的数据是什么格式以及content-type是如何设置的,asp.net都会根据Accept中的格式进行序列化返回结果给用户。我们加上上面那三句,纯是为了增加对json以及纯文件的支持。
routeTemplate是路由模板的设置,其中Api这个单词可以由我们自己定义,那么在以后,我们请求api的时候,就需要在域名后加上这个自定义的字符,来告诉ASP.NET我们要请求的是API,而不是正常的页面 或者 mvc的普通controller。我们会发现,这里和模板和mvc的模板不一样,因为这里少了{Action}。mvc一般会在{controller}后面跟{action},而这里没有,原因是我们的Action是Get、Post、Put、Delete等Http的Method方法,ASP.NET是直接根据HTTP请求的METHOD内容进行判断是要调用哪个Action的,因此这里根本没有{action}(也不能有啊,大哥)。{id}就不会说了,因为我们使用了EF,所以Id就代码我们的主键,在Put(修改)和Get(根据ID)取数据时是有用的。Defaults中的设置意思是id是可选的,也就是说,可以不填写,即Url中可以不存在这个id。
修改Area下我们的区域内的AdminAreaRegistration.cs
现在,我们需要让我们的Area向主模块中注册,其它这个注册操作是通过AdminAreaRegistration.cs来进行的,而vs本身也为我们自动生成了,但是,还是有地方是需要修改的,不然,会出现一些问题。修改后的代码如下:
public class AdminAreaRegistration : AreaRegistration { public override string AreaName { get { return "Admin"; } } public override void RegisterArea(AreaRegistrationContext context) { string[] str = new string[] { "WebApi.Areas.Admin.Controllers" }; context.MapRoute( "Admin_default", "Api/Admin/{controller}/{action}/{id}", new { action = "Index", id = UrlParameter.Optional }, str ); } }
我们继承了AreaRegistration这个父类,然后重写了AreaName这个方法 ,这个方法是主路由用来取我们这个区域的{Area}的(不知道{Area}干什么的请看上一小节的路由部分)。一旦总路由找到了对应的Area是我们的这个,就会把请求转到我们这个Area里面,由我们的Area路由进行处理。如果匹配到了对应的Controller和Action,那么就会调用这个Controller和Action进行处理。但是,需要注意的是,虽然我们区域中的ApiController在我们的区域文件夹中,但是,一旦编译成功,在DLL才不会分文件夹呢,所以,在其它的地方也有可能存在被找到的ApiController,这个会导致异常(如果找到两个的话)以及危险(没找到自己的反而找到别的地方的了)。这时,我们需要用命名空间来限定,这就是那个str数组变量的作用,它的内容是几个(一般是一个)命名空间,然后放到context.MapRoute中去。下次,这个区域的路由就到只在这几个命名空间内寻找。其实还有一个作用,就是你的ApiController在另一外项目或者DLL中,使用这个方法,完全可以将那些在外部的ApiController引用到这里被你的区域调用,是不是很爽?
调试一下,看一下能否请求到这个Area中的Controller吧,祝你成功。
基于Oracle的EntityFramework的WEBAPI2的实现(四)——自动生成在线帮助文档
打开我们项目中的Area文件夹,正常情况下,我们会发现已经有了一个名字叫做【HelpPage】的区域(Area),这个区域是vs帮助我们自动建立的,它是一个mvc(不是webapi),它有普通的Conroller和Action,以及View,Model等。我们可以在调试的时候输入地址:http://localhost:8080/Help,就可以看见一个页面,有没有看见自己熟悉的TestController(名字中去掉了Controller,只剩下Test了)。如果没有,那么,一定是我们的配置有问题,那么我们下面的检查一下。
首先,要达到自动生成在线帮助的页面,我们必须提示说明信息。其实我们的说明信息就是我们对我们的Controller类的注释以及每一个方法的注释。当然不是//这样的注释,而是如下的注释:
///<summary>
///这才是真的注释
///</summary>
这个注释放在我们的类和我们的Controller以及Controller下的各种Action上,然后把文字写清楚了。右击我们的项目·属性,然后设置如下:
在【生成】标签页里面设置好【XML文档文件】,在里面写好地址,应该和我们的项目的dll在一个位置(通常是主项目下的bin目录),然后全部保存。
这个时候我们所有对类和成员的注释在生成的时候都会被写入进这个xml文件中,但是,我们的在线帮助页要如何取得这些数据呢,当然,这就需要我们告诉它我们的这个xml文件在什么位置。先来看一下目录图。
我们在App_Start文件夹里面找到【HelpPageConfig.cs】文件,这个文件是整个 HelpPage的配置所在。将此文件打开,找到如下的代码,解开注释,并修改为如下内容:
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/bin/WebApi.xml")));
此时,我们按照一开始时调试 一下,应该就可以看到在每个ApiController上的注释了。但是,我们发现,我们的Model类后面的注释都是空的,原因是我们的Model类是从数据库生成的,虽然数据库里面有注释,EF并没有帮我们把注释从数据库中取进来,有一些其它的第三方工具,可以实现此功能,但是事实上,数据库的注释是给我们看的,而我们的Model类的注释是给调用者看的,这是不一样的,比如我们的Model类型需要写明这个属性是否可以为空,而数据库中的注释有时候会泄露我们系统业务的内部秘密。所以,这个时候,我们打开我们的Model设计器(双击那个edmx文件)选择一个类的一个属性,右击,属性,就可以看见属性里面有一项叫文档,点开,在摘要里面写上我们的注释,即可。当然,详细内容也可以写(但是对HelpPage不做修改的情况下,是看不见详细内容的)。
这还不够,因为edmx只是生成映射文件,是数据库表与我们的类的映射,当然它还包含了一些我们自定义的信息(如刚刚的注释),但是,真正担任代码生成工作的是我们的.tt文件(T4代码模板?T4代码生成代码?随便你怎么叫)。我们点开edmx文件前面的三角号展开它,看见有一个与edmx文件同名但是扩展名为.tt的文件,双击打开它(等等,不要慌,这不是乱码,这是T4的代码,如果有高亮的话,你会觉得它和ASPX或者CSHTML一样让人喜爱,只是现在这个样子确实有点丑,丑得让人看不懂,不过没关系,大家通过无下划线的上下文搜索是可以搜索到相关位置的,然后加入有下划线的代码即可)。如下:
WriteHeader(codeStringGenerator, fileManager); string summary=string.Empty; foreach (var entity in typeMapper.GetItemsToGenerate<EntityType>(itemCollection)) { fileManager.StartNewFile(entity.Name + ".cs"); BeginNamespace(code); if(entity.Documentation != null && entity.Documentation.Summary != null) { summary=entity.Documentation.Summary; }else{ summary=entity.Name; } #> <#=codeStringGenerator.UsingDirectives(inHeader: false)#> ///<summary> ///<#=summary#> ///</summary> <#=codeStringGenerator.EntityClassOpening(entity)#> {
var simpleProperties = typeMapper.GetSimpleProperties(entity); if (simpleProperties.Any()) { foreach (var edmProperty in simpleProperties) { if(edmProperty.Documentation !=null && edmProperty.Documentation.Summary != null) { summary=edmProperty.Documentation.Summary; }else { summary=""; } #> ///<summary> ///<#=summary#> ///</summary> <#=codeStringGenerator.Property(edmProperty)#>
当我们部分注释是源于其它项目时,该怎么办,比如Model是单独的项目
那么问题来了,我们刚刚只讲到让HelpPage只取一个WebApi.xml,但是,如果我们的Model在WebApi.Model这个项目中怎么办。我可以告诉你,如果在WebApi.Model中,那么你写一万行的注释,HelpPage也不会让你看见的,因为你的WebApi.Model.Xml并没有给它,它取不到里面的信息。
那么我们右击WebApi.Model这个项目,点属性,然后在【生成】中将【XML文档生成】的位置设置到我们的WebApi项目的bin目录下,与WebApi.xml在 一起。
ok,还有下面一步,就是在HelpPageConfing.cs中,告诉系统我们xml的位置。这个时候,你会发现,【XmlDocumentationProvider】这个类很贱的没有两个参数的重载~~我操!!
那我们现在有两个xml文件,该怎么办?我们在上面的那个目录结构图中,发现,这个【XmlDocumentationProvider】类的源码就在那里,是不是很兴奋,有想改这个类的冲动?别吧,留着它,万一以后用得着呢?我们照着它新建一个继承IDocumentationProvider, IModelDocumentationProvider的类,名字叫【MultiXmlDocumentationProvider】(这个类是我很久之前从网上找的,不知道作者是谁了,在此表示深深的感谢),它的实现如下:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Web; using System.Web.Http.Controllers; using System.Web.Http.Description; using WebApi.Areas.HelpPage.ModelDescriptions; namespace WebApi.Areas.HelpPage { /// <summary>A custom <see cref="IDocumentationProvider"/> that reads the API documentation from a collection of XML documentation files.</summary> public class MultiXmlDocumentationProvider : IDocumentationProvider, IModelDocumentationProvider { /********* ** Properties *********/ /// <summary>The internal documentation providers for specific files.</summary> private readonly XmlDocumentationProvider[] Providers; /********* ** Public methods *********/ /// <summary>Construct an instance.</summary> /// <param name="paths">The physical paths to the XML documents.</param> public MultiXmlDocumentationProvider(params string[] paths) { this.Providers = paths.Select(p => new XmlDocumentationProvider(p)).ToArray(); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(MemberInfo subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(Type subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(HttpControllerDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(HttpActionDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetDocumentation(HttpParameterDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /// <summary>Gets the documentation for a subject.</summary> /// <param name="subject">The subject to document.</param> public string GetResponseDocumentation(HttpActionDescriptor subject) { return this.GetFirstMatch(p => p.GetDocumentation(subject)); } /********* ** Private methods *********/ /// <summary>Get the first valid result from the collection of XML documentation providers.</summary> /// <param name="expr">The method to invoke.</param> private string GetFirstMatch(Func<XmlDocumentationProvider, string> expr) { return this.Providers .Select(expr) .FirstOrDefault(p => !String.IsNullOrWhiteSpace(p)); } } }
这个时候,我们再回到HelpPageConfig.cs类中,找到刚刚那句:
config.SetDocumentationProvider(new XmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/bin/WebApi.xml")));
将它替换为:
config.SetDocumentationProvider(new MultiXmlDocumentationProvider(HttpContext.Current.Server.MapPath("~/bin/WebApi.xml"), HttpContext.Current.Server.MapPath("~/bin/WebApi.Model.XML")));
这时候,我们再调试一下,倍儿爽!!~~~~
EF6:编写你自己的code first 数据迁移操作(睡前来一篇,翻译的)
原英文版由EF团队成员 Rowan Miller 在2013年发表,此处只作翻译备忘。
数据迁移提供了一套强类型API,用于执行通用的操作,比如CreateIndex("dbo.Blogs","Url")
。同时,也提供了在一些特殊的情况下用户需要执行特殊SQL的接口,比如Sql("Grant Select On dbo.Blogs to guest);
。当然,这个SQL的接口也有一些缺点——那就是一旦你写了SQL那么就意谓着你的程序不再数据库无关了(比如ORACLE的语法和SQL SERVER有时候并不一样,这造成了不兼容)。
你可以把你需要的强类型API提交给ICECLOW。
创建我们自己的操作
我们打算添加一个允许我们将一张表的权限赋给一个用户的操作。正常情况下,我们希望写一个类似GrantPermission("dbo.Blogs","guest",Permission.Select);
的方法。
首先,我们创建一个MigrationOperation的子类,即一个自定义操作。除了实现IsDestructiveChange这个属性别的不需要任何操作。
using System.Data.Entity.Migrations.Model;
namespace ExtendingMigrations.Migrations
{
public enum Permission
{
Select,
Update,
Delete
}
public class GrantPermissionOperation : MigrationOperation
{
public GrantPermissionOperation(string table, string user, Permission permission)
: base(null)
{
Table = table;
User = user;
Permission = permission;
}
public string Table { get; private set; }
public string User { get; private set; }
public Permission Permission { get; private set; }
//是否为破坏性的修改
public override bool IsDestructiveChange
{
get { return false; }
}
}
}
下面,我们来写一个扩展方法以实现我们想要的功能。我们使用IDbMigration接口,这个接口让我们可以有权限操作DbMigration中不可见的API。(比如下面migration,本身没有AddOperation操作【fuck,这是怎么实现的!】,但是,强制转换为IDbMigration的时候就有了)
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;
namespace ExtendingMigrations.Migrations
{
public static class Extensions
{
public static void GrantPermission(this DbMigration migration, string table, string user, Permission permission)
{
((IDbMigration)migration)
.AddOperation(new GrantPermissionOperation(table, user, permission));
}
}
}
你可以把上面的扩展方法作为普通的方法写在GrantPermissionOperation
类中,但是,假如像我这样使用了扩展方法,那么在类中我们就有更漂亮的写法。如下:
namespace ExtendingMigrations.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class GrantGuestPermissions : DbMigration
{
public override void Up()
{
this.GrantPermission("dbo.Blogs", "guest", Permission.Select);
}
public override void Down()
{
}
}
}
为我们的操作创建SQL
如果我们现在就调试运行我们的新的数据迁移类,那么肯定会出现异常。因为默认的SQL生成器不知道我们的操作流程。不过,我们可以从已经存在的migrator
(迁移器?可以这样叫么?)中继承然后将新的SQL生成逻辑添加到我们的操作中。
using System.Data.Entity.Migrations.Model;
using System.Data.Entity.Migrations.Sql;
namespace ExtendingMigrations.Migrations
{
public class MySqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
{
protected override void Generate(MigrationOperation migrationOperation)
{
var operation = migrationOperation as GrantPermissionOperation;
if (operation != null)
{
using (var writer = Writer())
{
writer.WriteLine(
"GRANT {0} ON {1} TO {2}",
operation.Permission.ToString().ToUpper(),
operation.Table,
operation.User);
Statement(writer);
}
}
}
}
}
注意上面的代码中,Generate
方法被我们重写了,但是,我们却没有执行base.Generate(..);
方法,因为父类的方法不知道如何处理这个逻辑,所以即使调用也会报异常。而且,也只有在我们默认的SQL生成器不知道如何处理我们自定义SQL的时候才会调用我们的Generate(MigrationOperation)
方法。
最后,我们需要在数据迁移配置(migrations configuration)中注册我们新的SQL生成器:
namespace ExtendingMigrations.Migrations
{
using System.Data.Entity.Migrations;
internal sealed class Configuration : DbMigrationsConfiguration<ExtendingMigrations.BloggingContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
// Register our custom generator
SetSqlGenerator("System.Data.SqlClient", new MySqlServerMigrationSqlGenerator());
}
}
}
测试
在我们VS的包管理控制台
中输入Update-Database -Script
,会输出更新数据库(对数据库进行数据迁移)时要执行的SQL脚本,其如下:
GRANT SELECT ON dbo.Blogs TO guest
INSERT [dbo].[__MigrationHistory]([MigrationId], [ContextKey], [Model], [ProductVersion])
VALUES ('201302272012532_GrantGuestPermissions', 'ExtendingMigrations.Migrations.Configuration', 0x1F8B0800000..., '6.0.0-alpha3-20222')
vs2015安装ORACLE的DbFirst
不说DbFirst好在哪里,它和ModelFirst,CodeFirst都各有各的好,由于对于已经存在的一个大型的业务库,使用EntityFramework的更倾向于DbFirst,因为好多同事已经习惯了直接修改数据库。
下载oracle的dbac(x86)下载地址(不能下载请留言),关掉vs,然后安装下载下来的压缩包里面的setup.exe。安装设置的两个目录,一定不要有特殊字符,可以用同一个,@,()等千万别有。
高兴的打开vs,然后新建一个edmx,新建连接,完了之后,看到了好多表,选中,保存敏感信息,不另存为,然后点完成。ok了,看见了新生成的edmx,但是,里面却没有数据。没有任何一个表被映射过来。在错误列表中发现,是由于在oracle中设置了联合主键,然后联合主键的Enable被设置为False,又将联合主键中的字段设为可空,导致生成实体的时候报“联合主键部分属性不能为null”的错误。
重新处理,编译一次系统,然后再添加基于实体的WEBAPI控制器,一切ok。