最近鼓捣一个SaaS项目,后台用的是entity framework 6 code first +SQL server,需要把数据库迁移到MySQL,在这里记录一下遇到的问题。时间比较久了记得不是很准确,供参考。
1, 以前残留的Migration .cs文件(在项目的Migration目录里,包括Configuration.cs)要统统删除重新生成一遍,因为由SQL server connector生成的.cs文件与MySQL connector生成的.cs并不兼容。在nuget manager console里依次使用enable-migrations和add-migration <name>重新生成。
2,创建数据库的时候报异常The ADO.NET provider with invariant name 'MySql.Data.MySqlClient' is either not registered in the machine or application config file, or could not be loaded. See the inner exception for details."(或者是一个别的异常?记得不是很清楚),一番度娘之后发现要在DbContext的子类上添加以下:
[DbConfigurationType(typeof(MySqlEFConfiguration))]
3,再次创建数据库,又有异常Specified key was too long; max key length is 767 bytes,再一番度娘之后发现要在Configuration.cs文件里增加如下代码:
SetSqlGenerator("MySql.Data.MySqlClient", new MySql.Data.Entity.MySqlMigrationSqlGenerator());
4,前进到建表的SQL语句,发现Procedure是MySQL的SQL语句的关键字,需要用`作为escape字符,而在SQL server里用的是[].
_helper.ExecuteCommand("INSERT INTO DBUpdateProcedure SET `Procedure` = @p0");
5,开始迁移stored procedure。SQL server创建stored procedure是在migration cs文件里手动调用this.CreateStoredProcedure()实现的,但是MySQL创建stored procedure有一个很特殊的语法,需要先将DELIMETER设为别的东西(比如“//”),再结束的时候再设回来。这个东西通过Migration cs文件没法实现,即使通过this.SQL()方法也不行,EF貌似不能正确的理解DELIMETER语法。所以只能在别的地方创建一个MySQLScript对象,通过设定它的Delimeter属性来达到同样的效果。
sqlConn.Open();MySqlScript script_add_sp = new MySqlScript(sqlConn);StringBuilder sql = new StringBuilder();sql.Append(@"...");script_add_sp.Query = sql.ToString();script_add_sp.Delimiter = "//";script_add_sp.Execute();script_add_sp.Delimiter = ";";
6,stored procedure迁移好了,在调用的时候又报异常Only MySqlParameter objects may be stored。原因是有如下代码:
var tenantIdParam = new SqlParameter("TenantId", tenantId);var officeIdParam = new SqlParameter("OfficeId", officeId);var searchTextParam = new SqlParameter("SearchText", searchText ?? "");var pageIndexParam = new SqlParameter("PageIndex", pageIndex);var pageSizeParam = new SqlParameter("PageSize", pageSize);var orderFieldParam = new SqlParameter("OrderField", orderField);var patients = DataContext.Database.SqlQuery<PatientPageResult>("csp_SearchPatient(@TenantId, @OfficeId, @SearchText, @PageIndex, @PageSize, @OrderField)",tenantIdParam, officeIdParam, searchTextParam, pageIndexParam, pageSizeParam, orderFieldParam).ToList();
这里我们必须构建MySqlParameter对象,而不是SQLParameter对象。并且MySql调用stored procedure要显示的加CALL关键字,所以改正之后的代码为:
var tenantIdParam = new MySqlParameter("TenantId", tenantId);var officeIdParam = new MySqlParameter("OfficeId", officeId);var searchTextParam = new MySqlParameter("SearchText", searchText ?? "");var pageIndexParam = new MySqlParameter("PageIndex", pageIndex);var pageSizeParam = new MySqlParameter("PageSize", pageSize);var orderFieldParam = new MySqlParameter("OrderField", orderField);var patients = DataContext.Database.SqlQuery<PatientPageResult>("CALL csp_SearchPatient(@TenantId, @OfficeId, @SearchText, @PageIndex, @PageSize, @OrderField)",tenantIdParam, officeIdParam, searchTextParam, pageIndexParam, pageSizeParam, orderFieldParam).ToList();
7,在Linq语句区数据转化为对象的时候,报异常:System.FormatException occurred, Message=String was not recognized as a valid Boolean.貌似因为实体类上有boolean属性,MySQL connector生成的默认migration cs文件把这个属性存在类型为tinyint的列里,但读出来的时候又不知道怎么把0/1转换为true/false,所以抛了这个异常。这个问题作为MySQL connector的一个bug报在了这里。就在快要绝望的时候,发现了stack overflow上牛人提的解决方案(原文),方法就是在modelbuilder里指定将boolean类型用bit列存储,没太明白,先记录下来。
modelBuilder.Properties().Where(x => x.PropertyType == typeof(bool)).Configure(x => x.HasColumnType("bit"));
这只是个开始,后面的坑再开新的随笔记录。