Entity Framework 4.3.1 Code First 连接 PostgreSQL 9.2.3 小结
在CentOS6.3上源码编译装好PostgreSQL9.2.3,下一步尝试将数据库从Microsoft SQL Server 2000迁移到PostgreSQL,并且尝试使用Entity Framework 4.3.1 Code First 连接 PostgreSQL 9.2.3,使它们协同工作。
微软的EF Code First Database Migration原生支持SQL Server 2008以后版本,这个没办法,PostgreSQL上建数据表索引什么的,都要自己写SQL语句。采用开源的Npgsql库作为Data Provider,下载地址:http://npgsql.projects.pgfoundry.org,目前版本2.0.12。
首先在项目中引用Npgsql.dll,然后在web.config或app.config中添加下面的代码:
<configuration> <configSections> <!-- 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=4.3.1.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections> <system.data> <DbProviderFactories> <remove invariant="Npgsql"></remove> <add name="Npgsql Data Provider" invariant="Npgsql" description=".Net Framework Data Provider for Postgresql Server" type="Npgsql.NpgsqlFactory, Npgsql, Version=2.0.12.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" /> </DbProviderFactories> </system.data> <connectionStrings> <add name="DB" connectionString="Server=192.168.211.100;Port=5432;Database=kit;User Id=postgres;Password=123456;CommandTimeout=20;" providerName="Npgsql" /> </connectionStrings> </configuration>
在实例化DBContext时,使用下面的代码阻止EF去初始化数据库:
public class BestDbContext : DbContext { public BestDbContext(string databaseName, bool isDoInitialize = false) : base(databaseName) { if (!isDoInitialize) { Database.SetInitializer<BestDbContext>(null); } } } ....... //实例化DbConetxt的代码,DB对应config文件中的connectionStrings设置 using (var db = new BestDbContext("name=DB")) { Product product = new Product() { ... };
db.Products.Add(product);
db.SaveChanges();
....... }
微软的EF会把dbo作为默认的数据库schema,为了省事,在PostgreSQL中建立完数据库,再创建个名字为dbo的schema,所有的数据表和其他对象都建立在这个schema下。
否则就要在定义类时使用下面的代码,声明类所对应的数据表和schema:
[Table("Products", Schema="public")] public class Product { ...... }
注意,PostgreSQL要求数据库对象名称如果不是全小写,就必须用双引号括起来,这在自行编写SQL语句时要格外注意,请参考后面的示例代码。
对于EF中随处都用到的ID字段,也就是自增字段,在PostgreSQL建表时要指定sequence的OWNER为对应的字段,参考下面的代码最后一行:
CREATE SEQUENCE dbo."Products_Id_seq" INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1; ALTER TABLE dbo."Products_Id_seq" OWNER TO postgres; CREATE TABLE dbo."Products" ( "Id" integer NOT NULL DEFAULT nextval('dbo."Products_Id_seq"'::regclass), "Name" character varying(50) NOT NULL, "Remark" character varying(500), "Sequence" integer NOT NULL, "IsAvailable" boolean NOT NULL, "CreatedBy" integer NOT NULL, "CreatedTime" timestamp without time zone NOT NULL, "UpdatedBy" integer NOT NULL, "UpdatedTime" timestamp without time zone NOT NULL, CONSTRAINT "Products_pkey" PRIMARY KEY ("Id"), CONSTRAINT "Products_Name_key" UNIQUE ("Name") ) WITH ( OIDS=FALSE ); ALTER TABLE dbo."Products" OWNER TO postgres; ALTER SEQUENCE dbo."Products_Id_seq" OWNED BY dbo."Products"."Id";
否则,EF会无法获得新建对象的ID,报如下错误:"A null store-generated value was returned for a non-nullable member...",参考这个网页:http://pgfoundry.org/forum/message.php?msg_id=1006377&group_id=1000140
EF能自动处理好前面提到的数据库对象名必须用双引号括起来的问题,这一点很欣慰。
关于数据类型,下面列出一部分SQL Server类型和PostgreSQL类型的对应关系:
int --> integer
nvarchar(50) --> character varying(50)
nvarchar(max) 或 ntext --> text
datatime --> timestamp without time zone
对于boolean类型,PostgreSQL不支持用0或1隐式转换,直接在SQL语句中使用true和false即可。
接下来解决Timestamp的问题,参考了这里:http://stackoverflow.com/questions/1035980/postgresql-update-timestamp-when-row-is-updated 的文章。
为了防止并发访问时出问题,对本项目中的Stocks库存表,设置了Timestamp并发控制列,在使用PostgreSQL数据库时,做以下调整:
首先,在实体类定义中定义RowVersion列,设为DateTime类型:
public class Stock { public int Id { get; set; } [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))] public Location Location { get; set; } [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))] public Part Part { get; set; } public Batch Batch { get; set; } [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))] public int Quantity { get; set; } [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))] public int UpdatedBy { get; set; } [Required(ErrorMessageResourceName = "Generic_Required", ErrorMessageResourceType = typeof(ValidationMessage))] public DateTime UpdatedTime { get; set; } public DateTime RowVersion { get; set; } }
在DbContext的构造器中加入下面的代码:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Stock>().Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); modelBuilder.Entity<Stock>().Property(p => p.RowVersion).IsConcurrencyToken().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); }
对应的Stocks表的创建脚本:
CREATE SEQUENCE dbo."Stocks_Id_seq" INCREMENT 1 MINVALUE 1 MAXVALUE 9223372036854775807 START 1 CACHE 1; ALTER TABLE dbo."Stocks_Id_seq" OWNER TO postgres; CREATE TABLE dbo."Stocks" ( "Id" integer NOT NULL DEFAULT nextval('dbo."Stocks_Id_seq"'::regclass), "Batch_BatchNo" character varying(50), "Quantity" integer NOT NULL, "UpdatedBy" integer NOT NULL, "UpdatedTime" timestamp without time zone NOT NULL, "Location_Id" integer NOT NULL, "Part_PartNo" character varying(50) NOT NULL, "RowVersion" timestamp without time zone NOT NULL, CONSTRAINT "Stocks_pkey" PRIMARY KEY ("Id"), CONSTRAINT "Stocks_Batch_BatchNo_fkey" FOREIGN KEY ("Batch_BatchNo") REFERENCES dbo."Batches" ("BatchNo") MATCH SIMPLE ON UPDATE RESTRICT ON DELETE RESTRICT, CONSTRAINT "Stocks_Location_Id_fkey" FOREIGN KEY ("Location_Id") REFERENCES dbo."Locations" ("Id") MATCH SIMPLE ON UPDATE RESTRICT ON DELETE RESTRICT, CONSTRAINT "Stocks_Part_PartNo_fkey" FOREIGN KEY ("Part_PartNo") REFERENCES dbo."Parts" ("PartNo") MATCH SIMPLE ON UPDATE RESTRICT ON DELETE RESTRICT ) WITH ( OIDS=FALSE ); ALTER TABLE dbo."Stocks" OWNER TO postgres; ALTER SEQUENCE dbo."Stocks_Id_seq" OWNED BY dbo."Stocks"."Id"; CREATE UNIQUE INDEX "Stocks_Batch_BatchNo_Location_Id_Part_PartNo_idx" ON dbo."Stocks" USING btree ("Batch_BatchNo" COLLATE pg_catalog."default", "Location_Id", "Part_PartNo" COLLATE pg_catalog."default");
然后,在PostgreSQL中执行下面的脚本,建立触发器和对应的函数:
CREATE OR REPLACE FUNCTION dbo.update_rowversion_column() RETURNS trigger AS $BODY$ BEGIN NEW."RowVersion" = now(); RETURN NEW; END; $BODY$ LANGUAGE plpgsql VOLATILE COST 100; ALTER FUNCTION dbo.update_rowversion_column() OWNER TO postgres; CREATE TRIGGER update_stocks_rowversion BEFORE UPDATE ON dbo."Stocks" FOR EACH ROW EXECUTE PROCEDURE dbo.update_rowversion_column();
至此,已知问题都解决了,下一步将关注复杂条件的查询效率。