前面我们扩展了bind方法和ready函数,这次我要讲一下$.fn.extend 和$.extend函数。
其他的不多说,直接切入主题吧!
先来看看这两个函数的区别:
$.fn.extend是为查询的节点对象扩展方法,是基于$的原型扩展的方法
$.extend是扩展常规方法,是$的静态方法。
我们之前写的代码看一下:
(function (win) { var _$ = function (selector, context) { return new _$.prototype.Init(selector, context); } _$.prototype = { Init: function (selector, context) { }, each: function (callback) { } } _$.prototype.Init.prototype = _$.prototype; window.$ = window.JQuery = _$; })(window);
这个是主体的代码,全部的代码上一次的有。
我来先来扩展$.fn.extend方法:
这个方法的初衷是我们扩展之后可以用$("").newMetod()这样访问,实际上就是给$原型加一个extend方法。这中间的fn其实类似于命名空间的作用,没什么实际的意义。为的是和 $.extend作区分。
熟悉原型的其实一看就知道:让$.fn指向$的原型不就行了?
于是我们就有了下面一段代码:
_$.fn = _$.prototype;
接下来我们就来加上extend方法了:
var isObj = function (o) { return Object.prototype.toString.call(o) === "[object Object]"; } _$.fn.extend = function (obj) { if (isObj(obj)) { for (var i in obj) { this[i] = obj[i]; } } }
这段代码中isObj的作用是判断传入的参数是不是object对象, _$.fn.extend 这个方法其实和_$.prototype.extend 一样的,大家看一下,这个代码可能和JQUERY源码不太一样,我是按照自己的意思写的。
下面我们来实现$.extend方法,刚才已经说过了,这个方法其实是为$加一个静态方法,代码如下:
$.extend = function (obj) {
if (isObj(obj)) {
for (var i in obj) {
this[i] = obj[i];
}
}
}
你会发现两个方法是一样的,不过你仔细琢磨一下,是不一样的:
_$.fn.extend里面的this其实是代表$.prototype, $.extend 里面的this代表的是$。
这两个方法我们实现了,奉上全部代码:
使用方法其实就是
$.fn.extend(
{
method:function(){
}
})
$.extend(
{
method:function(){
}
})
代码和Jquery源码不一样,我这是为了简化写的方法,大家主要是要琢磨里面的思想。
创建初始模型和数据库
在开始使用迁移(Migrations)之前,我们需要一个 Project 和一个 Code First Model, 对于本文将使用典型的 Blog 和 Post 模型
public class Blog { public int BlogId { get; set; } public string Name { get; set; } }
public class BlogContext : DbContext { public DbSet<Blog> Blogs { get; set; } }
更改 Program.cs 以调用
static void Main(string[] args) { using (var db = new BlogContext()) { db.Blogs.Add(new Blog { Name = "Another Blog" }); db.SaveChanges(); foreach (var blog in db.Blogs) { Console.WriteLine(blog.Name); } } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); }
运行查看结果
发现如上错误"CREATE DATABSE permission denied in databse 'master'"
我们在 BlogContext 上的无参构造函数上添加诊断代码并设置调试断点
System.Diagnostics.Debug.Write(Database.Connection.ConnectionString);
再次运行
我们注意到 Data Scource 竟然是 .\\SQLEXPRESS 而不是我们想要的 localDB , 这是因为:
知道了原因我们就好解决了:
<connectionStrings> <add name="BlogContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=BlogContext;Integrated Security=SSPI;" providerName="System.Data.SqlClient"/> </connectionStrings>
再次运行就行了,让我们看一下后台生成的数据库
启用迁移
我们对模型 Blog 做一些更改:增加一个 Url 属性
public string Url { get; set; }
我们此时再次运行程序,发现如下错误
'InvalidOperationException' was unhandled. The model backing the 'BlogContext' context has changed since the database was created. Consider using Code First Migrations to update the database ( http://go.microsoft.com/fwlink/?LinkId=238269)
正如错误消息提示的那样,是时候使用 Code First Migrations,第一步是运行如下的命令:
这个命令将在项目下创建文件夹 Migrations
多个实体锁定同一数据库
当使用 EF6 之前的版本时,只会有一个 Code First Model 被用来生成/管理数据库的 Schema, 这将导致每个数据库只会有一张 __MigrationsHistory 表,从而无法辨别实体与模型的对应关系。
从 EF6 开始,Configuration 类将会包含一个 ContextKey 属性,它将作为每一个 Code First Model 的唯一标识符, __MigrationsHistory 表中一个相应地的列允许来自多个模型(multiple models)的实体共享表(entries),默认情况下这个属性被设置成 context 的完全限定名。
生成、运行迁移
Code First Migrations 有两个你需要熟悉的命令:
我们需要脚手架(scaffold 直译)一个迁移,以上面的 Url 属性为例,命令 Add-Migration 允许我们对迁移命名,我们姑且称之为 AddBlogUrl
namespace MigrationsDemo.Migrations { using System; using System.Data.Entity.Migrations; public partial class AddBlogUrl : DbMigration { public override void Up() { AddColumn("dbo.Blogs", "Url", c => c.String()); } public override void Down() { DropColumn("dbo.Blogs", "Url"); } } }
我们现在可以对这个迁移进行编辑或者增加,但似乎看起来还不错,那我们就直接用 Update-Database 来应用到数据库吧
定制化迁移
到目前为止我们生成并运行了一个迁移,但是没有对迁移做任何更改,下面我们将尝试做一些更改:在类 Bolg 上增加一属性 Rating
public int Rating { get; set; }
新建 Post
public class Post { public int PostId { get; set; } [MaxLength(200)] public string Title { get; set; } public string Content { get; set; } public int BlogId { get; set; } public Blog Blog { get; set; } }
在 Blog 中添加 Post 的集合
public virtual ICollection<Post> Posts { get; set; }
在 Package Manager Console 中运行命令 Add-Migration AddPostClass
生成的迁移如下
接下来我们对迁移做些更改:
更改后的代码如下
namespace MigrationsDemo.Migrations { using System; using System.Data.Entity.Migrations; public partial class AddPostClass : DbMigration { public override void Up() { CreateTable( "dbo.Posts", c => new { PostId = c.Int(nullable: false, identity: true), Title = c.String(maxLength: 200), Content = c.String(), BlogId = c.Int(nullable: false), }) .PrimaryKey(t => t.PostId) .ForeignKey("dbo.Blogs", t => t.BlogId, cascadeDelete: true) .Index(t => t.BlogId) .Index(p => p.Title, unique: true); AddColumn("dbo.Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3)); } public override void Down() { DropIndex("dbo.Posts", new[] { "Title" }); DropForeignKey("dbo.Posts", "BlogId", "dbo.Blogs"); DropIndex("dbo.Posts", new[] { "BlogId" }); DropColumn("dbo.Blogs", "Rating"); DropTable("dbo.Posts"); } } }
在 Package Manager Console 中运行命令 Update-Database –Verbose
数据移动 / 定制SQL
迄今为止,迁移都没有更改或移动数据,现在让我们看一下需要移动数据的例子。虽然没有对数据移动的原生支持,但是我们可以随意运行 SQL 脚本。
让我们在 Post 中增加一个属性 Abstract, 稍后我们使用列 Content 的开头来填充此列(数据库已有记录)
public string Abstract { get; set; }
namespace MigrationsDemo.Migrations { using System; using System.Data.Entity.Migrations; public partial class AddPostAbstract : DbMigration { public override void Up() { AddColumn("dbo.Posts", "Abstract", c => c.String()); Sql("UPDATE dbo.Posts SET Abstract = LEFT(Content, 100) WHERE Abstract IS NULL"); } public override void Down() { DropColumn("dbo.Posts", "Abstract"); } } }
在 Package Manager Console 中运行命令 Update-Database –Verbose
迁移至指定版本(包括后退)
迄今为止,我们总是升级至最新迁移,然而某些时候我们需要升级/降级至指定版本,例如我们想迁移数据库至运行 AddBlogUrl 迁移之后的状态,此时我们就可以使用 –TargetMigration 来降级到这个版本
在 Package Manager Console 中运行命令 Update-Database –TargetMigration: AddBlogUrl
这个命令将会运行 AddBlogAbstract and AddPostClass 的 Down 命令
如果你想回滚一切至空数据库,可以使用命令 Update-Database –TargetMigration: $InitialDatabase
得到SQL脚本
如果其它开发人员也希望在他们自己的机器上拥有这些更改,他们只需在我们 check in 代码至 source control 的时候做一次同步即可,一旦他们拥有了这些迁移,只需运行命令 Update-Database 就可以把这些更改应用于本地。但是如果我们想把这些更改推送至测试服务器或生产服务器,我们也许需要一份SQL 脚本提供给 DBA
产生的 SQL 脚本如下
产生幂等脚本(EF6+)
从 EF6 开始,如果你使用 –SourceMigration $InitialDatabase, 产生的脚本将是幂等的,幂等脚本意味着无论数据库当前处于什么版本/状态,都能升级至最新版本或指定版本(指定 –TargetMigration),生成的脚本包括检查表 __MigrationsHistory 的逻辑以及只更新之前从未更新的
在应用程序启动时自动升级(MigrateDatabaseToLatestVersion初始化器)
当你发布部署应用程序的时候,可能希望当程序启动的时候它自动更新数据库(更新应用任何未更新的迁移),你可以通过注册 MigrateDatabaseToLatestVersion 数据库初始化器来实现这一点,数据库初始化器只包含一些逻辑检查用于确保数据库被正确设置,这个逻辑检查将会在AppDomain 的 context 第一次被使用的时候执行。
当我们创建一个初始化器的实例时,需要指定 context type(BlogContext)以及 migrations configuration (Configuration)- 这个迁移配置类是在我们启用迁移时生成的 Migrations 目录下增加的
using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Text; using System.Threading.Tasks; using MigrationsDemo.Migrations; namespace MigrationsDemo { class Program { static void Main(string[] args) { Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>()); using (var db = new BlogContext()) { db.Blogs.Add(new Blog { Name = "Another Blog " }); db.SaveChanges(); foreach (var blog in db.Blogs) { Console.WriteLine(blog.Name); } } Console.WriteLine("Press any key to exit..."); Console.ReadKey(); } } }