[实录]解决Migrator.Net 小bug

好久没写了,平时比较忙,只能趁周末的时候,写一点小东西,自己也记录一下。

平时我们做项目的时候,都会有自己的数据访问层,为了能方便以后的升级,我们一般会抽象出数据访问层,利用某些方式(比如工厂模式),达到数据库类型的切换,这大大提高了我们的开发效率,只需要修改建立一个新数据库,再配置的时候修改一下就能使用了。但每次我们必须要建立这个新的数据库,有时候这个工作量也非常的大,如果不熟悉的人,还可能建立的数据库与原先的数据访问不兼容,那怎么解决呢?有什么办法使我们建立数据库能够统一呢?答案是肯定的,我们今天讲的Migrator.Net就是这样一个方便的数据迁移工具,看它的名字就能猜到,可能是从Java项目转过来的,呵呵,这个不重要,重要的时,真的很有用,方便了我们。至于如何用,大家有兴趣的话,可以研究下,或者下次就写Migrator.Net的简单使用吧。

今天我说的是,这次项目中遇到的某个问题,还有解决方案。

问题场景

在项目中,我用Migrator.Net建立了一个User表,创建没有问题,但在回滚版本的时候,却发生了不能删除的问题。

问题研究

首先,我换了User表的名字,比如Users,Migrator正常运行,能够回滚,ok,大概的原因知道了。

因为我用的是Sql Server 2005,在MsSql中,User是一个关键字,也就是说我们建立和创建的时候,必须加[],比如:

Create Table [User]

Drop Table [User]

查看我的Migrator代码,已经加入了中括号,那是什么原因呢?因为运行中也没有抛出任何错误,看来只能看他的源代码了。好在我们可以通过Migrator.Net的Svn下载其源代码。代码结构如下:

可以看出,是用了Provider模式,而且已经写好了几个Provider,我用的是Sql Server,已经有了支持,至于如何使用,我会在后面说说吧,如果大家都感兴趣的话,呵呵。

先来看看,Migrator的简单应用吧,增加一个表,删除一个表:

[Migration(1)]
public class _001_AddTable_User : Migration
{
    public override void Up()
    {
        Database.AddTable("[User]",
            new Column("UserID", System.Data.DbType.Int32, ColumnProperty.PrimaryKeyWithIdentity),
            new Column("UserName", System.Data.DbType.String, 50, ColumnProperty.NotNull | ColumnProperty.Unique),
            new Column("Password", System.Data.DbType.String, 64, ColumnProperty.NotNull),
            new Column("Email", System.Data.DbType.String, 128, ColumnProperty.NotNull | ColumnProperty.Unique),
            new Column("AddTime", System.Data.DbType.DateTime, ColumnProperty.NotNull, "getDate()"));
    }

    public override void Down()
    {
        Database.RemoveTable("[User]");
    }
}

正如先前所说的,我们加了一个User表,我们的创建表的类,必须继承Migration抽象类,实现Up()和Down()方法,Up是升级,Down是回滚操作。代码可以正确执行,但是当你回滚的时候,虽然提示正确,但是,我们User表始终没有删除,就是上面所说的bug,那我们要看的,就是Database.RemoveTable(tableName)这个方法,通过查看,发现Database是一个抽象对象:TransformationProvider,看名字就知道了,呵呵。看看RemoveTable(tableName)方法吧:

public virtual void RemoveTable(string name)
{
    if (TableExists(name))
        ExecuteNonQuery(String.Format("DROP TABLE {0}", name));
}

发现了没有,他执行了一个Sql脚本,Drop Table tableName,那这句应该没错,可以排除,但是在运行前有一个TableExists(tableName)的判断,我们再看看:

public virtual bool TableExists(string table)
{
    try
    {
        ExecuteNonQuery("SELECT COUNT(*) FROM " + table);
        return true;
    }
    catch (Exception)
    {
        return false;
    }
}

运行的Sql脚本是:Select count(*) from tableName

恩?没错嘛,如果是这样运行的话,应该可以查到数据的,返回true才对,慢着,这个方法是virtual,也就是说我们子类Provider可以完全重写。

基本上可以确定是Provider的RemoveTable和TableExists这2个方法中有一个出错了,好的,我们再来看看。

因为我用的是Sql Server,当然只需要看SqlServerTransformationProvider了,看看这2个方法:

public override bool TableExists(string table)
{
    using (IDataReader reader =
        ExecuteQuery(String.Format("SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='{0}'", table)))
    {
        return reader.Read();
    }
}

呵呵,只找到一个TableExists,那说明RemoveTable还是正确的,不过这个TableExists方法有点奇怪,因为运行了:

SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='[User]'

这是什么呢?

MSDN:为当前用户具有权限的当前数据库中的每个表返回一行

我试着把这个语句放到我的Sql Managerment中运行一下,发现没有找到,不过当我去掉中括号后,返回一行记录。

至此,原因找明白了,一个非常非常小的问题:

运行Sql脚本的时候,我们遇到关键字表或者字段的话,MSSQL必须用中括号包围,当然这是一种好的习惯,应该每个字段每个表名都这样做,但是,MSSQL的这个INFORMATION_SCHEMA却在查询的时候,是按照字符串来查询的,不需要这个中括号,这样就产生了冲突。

好了,问题找到了,我们可以自己动手修改一下,但为了简单起见,我就修改他的源代码了,不写一个Provider了。

修改很简单,注释掉SqlServerTransformationProvider的TableExists(table)方法就ok了,编译,然后再console中运行一下卸载的bat(下篇再讲),哈,成功删除User表,问题至此也得到了完美解决。效果图:

看到Drop Table [User] 了吧,这就是运行的Sql语句,再看看你的数据库,嘿嘿,已经删除了。

总结

这次呢,因为项目中正好遇到了这个问题,顺便看了下,还发现了基础小bug,比如对NVarchar(Max)的支持不好,少了MSSQL的Timestamp类型等。不过总体来说,Migrator.Net是一个优秀的数据库迁移框架,有了它,我们大大提高了项目的扩展性,而且对于以后的重构,升级都有很好的帮助,大家也可以去它的讨论区进行讨论。目前这个bug还没有提交给作者,等有时间了,提交下吧。

点击下载修正过的Migrator.Providers.dll

时间: 2024-08-02 05:49:55

[实录]解决Migrator.Net 小bug的相关文章

Win10 UWP系列:关于错误 0x80073CF9及一个小bug的解决

原文:Win10 UWP系列:关于错误 0x80073CF9及一个小bug的解决 最近一直在开发XX的uwp版本,也是边摸索边做,最近遇到几个比较奇怪的问题,记录于此. 1.项目可用部署到PC,但无法部署到手机,提示以下错误: 错误 : DEP0001 : 意外错误: Install failed. Please contact your software vendor. (Exception from HRESULT: 0x80073CF9 为了方便开发,我将常用的类库引用好.默认的几个页面做

c++-pta:大炮打蚊子,有点小bug,求大神帮忙解决一下

问题描述 pta:大炮打蚊子,有点小bug,求大神帮忙解决一下 现在,我们用大炮来打蚊子:蚊子分布在一个M×N格的二维平面上,每只蚊子占据一格.向该平面的任意位置发射炮弹,炮弹的杀伤范围如下示意: O OXO O 其中,X为炮弹落点中心,O为紧靠中心的四个有杀伤力的格子范围.若蚊子被炮弹命中(位于X格),一击毙命,若仅被杀伤(位于O格),则损失一半的生命力.也就是说,一次命中或者两次杀伤均可消灭蚊子.现在给出蚊子的分布情况以及连续k发炮弹的落点,给出每炮消灭的蚊子数. 输入格式: 第一行为两个不

asp.net 2.0中tablecontrol搭配masterpage的小bug

asp.net|erp 在asp.net 2.0中,如果在一个masterpage页面中,使用服务端的table控件的话,如下所示,会在设置视图时,没了其中的contentplaceholder,     <asp:Table ID="tbl" runat="server">            <asp:TableRow>                <asp:TableCell>                    Th

c++-二叉树搜索的问题,以及一个小bug

问题描述 二叉树搜索的问题,以及一个小bug #include<iostream> #include<string> using namespace std; class node{ public: string name; string keyword; node* left; node* right; node(string a = "0", string b = "0", node* c = 0, node* d = 0) : name

menu-关于popupwindow学习中出现的一个小bug

问题描述 关于popupwindow学习中出现的一个小bug 我设置了一个buttonde点击事件 点击后能弹出popuowindow的框 ,我也设置了menu键,点击menu键 也可以弹出这个popupwindow的框 ,但是奇怪的地方在于,如果我不先点击button,我点menu键,它不会再弹出这个popupwindow的框了 ,意思就是只有先点button弹出那个popupwindow框先,点击menu才会有popupwindow这个框.我一直找不到原因,特地找各位大神帮帮忙!然后还有一个

Chrome出了个小bug:论如何在Chrome下劫持原生只读对象

Chrome出了个小bug:论如何在Chrome下劫持原生只读对象       概述 众所周知,虽然JavaScript是个很灵活的语言,浏览器里很多原生的方法都可以随意覆盖或者重写,比如alert.但是为了保证网页的安全性和网页制作者的一定控制权,有些浏览器对象是无法更改的,比如"window.location"对象,或者对它们的更改是无效的,比如"window.navigator"对象.然而,最近我发现Chrome出现了一个小"bug",在C

李开复:创业开始别想太高先解决用户的小问题

10月30日下午消息,据多家台湾媒体报道,创新工场董事长兼首席执行官李开复今天在台湾参加<远见>第12届华人企业领袖远见高峰会,他指出,台湾创业环境没转好,建议台湾创业团队没必要一开始想太高.太大,应先解决用户的小问题.李开复去年9月公布罹癌消息后,在首度公开演讲时谈及台湾的创新创业环境,他说:台湾产业跟我一样病了,充满了困难与危机,如果想恢复过去的ICT(资通讯)时代的辉煌成绩,就要下猛药.时隔一年,李开复再度参加论坛,并直言:今年台湾病情没转好.他表示,台湾创业环境面临的问题是不够国际化.

linux的一点小bug

那天,我们"操作系统"课程安排上机实验,主题是让我们熟悉 Linux 下的系统调用,内容是尝试用 fork() 创建一个子进程. 老师告诉我们:fork() 执行后,父进程和子进程共享代码段.我当时还不清楚子进程运行的时候,到底是从头开始执行还是从 fork() 开始执行.fork 这个单词的字面意思是分叉,按这个逻辑进程应该是从 fork() 这个分叉口继续执行.于是,写了如下代码检验我的推测. /** * Linux 的小 Bug * Code 1 * Author: redrai

解决dotproject的两个小bug的方法_其它CMS

dotProject Version: 2.1.2 [问题一] 除了管理员,其他用户无法在讨论区(forums)发言,用户在讨论区开始新话题或者回复时,看不到"提交"(submit)按钮. [解决] 在论坛找到有人遇到类似问题,原因是权限分配的bug造成的,修改方法如下: 打开modules/forums/post_message.php,找到以下代码(在文件源代码末尾处): if ( $canEdit && ( $AppUI->user_id == $row['