项目中需要将系统从SQLServer数据库迁移到Oracle上。由于原大部分数据访问操作都是通过包装了Entity Framework的统一访问入口实现的,所以需要研究Entity Framework从SQLServer转移到Oracle的实现方式。
自从EF4.X起,Oracle就为EF提供了驱动支持,但是很可惜的是不支持CodeFirst模式。庆幸的是从ODP.NET 11.2.0.3.0开始,Oracle官方提供了支持CodeFirst的纯托管代码的EF驱动。但是有以下几点是需要知道的;
- ODP.NET for .NET Framework 4.0支持Entity Framework和LINQ to Entities,但ODP.NET for .NET Framework 2.0并不支持。
- Code First的特性只能在Entity Framework 6 以上的版本才能使用。
- ODP.NET和Entity Framework支持标量参数绑定。Entity Framework支持通过名称的参数绑定,不支持通过位置的参数绑定。
- 只支持访问Oracle 10g release2及以后版本
- 使用可升级和分布式事务需要Oracle Service for Microsoft Transaction Server 12.1。ODP.NET在分布式事务中只支持读级别的隔离。
如何使用CodeFirst
接下来才是我们真正需要关心的,如何在项目中使用CodeFirst(本篇默认你已经了解CodeFirst的概念和使用方法)。
有两种方式能找到我们需要的dll:通过Oracle官网下载(在\odp.net4\odp.net\managed\common中),通过NuGet安装。这里我推荐使用NuGet安装。在程序包管理控制台输入:
install-package Oracle.ManagedDataAccess.EntityFramework
修改app.config配置如下:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<section name="entityFramework"
type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
requirePermission="false"/>
</configSections>
<connectionStrings>
<add name="SampleDataSource" providerName="Oracle.ManagedDataAccess.Client"
connectionString="Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=**SERVICE_NAME**)));Persist Security Info=True;User ID=**User ID**;Password=**Password**"/>
</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>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<publisherPolicy apply="no"/>
<assemblyIdentity name="Oracle.ManagedDataAccess" publicKeyToken="89b483f429c47342" culture="neutral"/>
<bindingRedirect oldVersion="4.121.0.0 - 4.65535.65535.65535" newVersion="4.121.2.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
定义一个实体对象:
public class SomeInfo
{
public Guid Id { get; set; }
public DateTime DateTimeField { get; set; }
public bool BooleanField { get; set; }
public decimal DecimalField { get; set; }
public int IntField { get; set; }
public string StringField { get; set; }
}
定义数据上下文:
public class TestDbContext : DbContext
{
public TestDbContext()
: base("name=SampleDataSource")
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<TestDbContext>());
} public DbSet<SomeInfo> SomeInfos { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//这里需要指定schema,默认是sqlserver的dbo
modelBuilder.HasDefaultSchema("YOURSCHEMA");
base.OnModelCreating(modelBuilder);
}
}
在确保连接字符串是正确的情况下就可以写一段测试代码来验证了。验证的代码这里就不介绍了。
.NET类型到Oracle类型的映射
由于Oracle和SQLServer的数据类型有些不同所以这里需要介绍下.NET的数据类型和Oracle数据类型的映射。
.NET数据类型 | Oracle数据类型 | 映射方法 |
---|---|---|
Boolean | number(1, 0) | 使用EDM映射,注意:需要使用EDM映射配置,参考附加信息文档EDM映射节 。 |
Byte | number(3, 0) | 使用EDM映射,注意:需要使用EDM映射配置,参考附加信息文档EDM映射节 。 |
Byte[] | blob | 默认 |
Int16 | number(5, 0) | 默认,注意:需要使用EDM映射配置,参考附加信息文档EDM映射节 。 |
Int32 | number(10, 0) | 默认,注意:需要使用EDM映射配置,参考附加信息文档EDM映射节 。 |
Int64 | number(19, 0) | 默认,注意:需要使用EDM映射配置,参考附加信息文档EDM映射节 。 |
Decimal | number(18, 2) | 默认 |
Single | binary_float | 默认 |
Double | binary_double | 默认 |
Guid | raw(16) | 默认 |
DateTime | date | 默认 |
DateTimeOffset | timestamp withtime zone | 默认 |
String | nclob | 默认 |
String | clob | 使用IsUnicode() fluent API设置Unicode为false |
String | nvarchar2 | 使用HasMaxLength() fluent API或MaxLength标记设置MaxLength不大于2000 |
String | varchar2 | 使用HasMaxLength() fluent API或MaxLength标记设置MaxLength不大于4000,使用IsUnicode() fluent API设置Unicode为false |
String | nchar | 使用HasMaxLength() fluent API或MaxLength标记设置MaxLength不大于1000,使用HasColumnType() fluent API或Column标记设置Column Type为NCHAR |
String | char | 使用HasMaxLength() fluent API或MaxLength标记设置MaxLength不大于2000,使用HasColumnType() fluent API或Column标记设置Column Type为NCHAR |
String | Long | 使用HasColumnType() fluent API或Column标记设置Column Type为LONG,注意: 不推荐使用Long数据类型。 |
String | rowid | 使用HasColumnType() fluent API或Column标记设置Column Type为ROWID |
String | urowid | 使用HasColumnType() fluent API或Column标记设置Column Type为UROWID |
注意:
基于字符的列,即:CHAR,NCHAR,VARCHAR2,NVARCHAR2可以存储指定(MaxLength)的字符。但是受限于Oracle的设计,这些列只能存储最多4000 byte。存储的数据和数据库字符集的设定会使有些字符可能需要多个byte,所以尽管这些类型的列被配置为4000(MaxLength)但是可能存储不了这么多字符。如果待存储的数据超过4000byte可以使用CLOB或NCLOB类型的列。
Oracle数据类型的特性配置
接下来的表格里将列出Oracle所支持的Data Annotation和Fluent API:
Data Annotation | Fluent API | 目的 | 应用于 |
---|---|---|---|
Key | HasKey | 设置主键. | All Scalar Types |
Required | IsRequired | 设置列为NOT NULL. | All |
MaxLength | HasMaxLength | 设置列最大长度 | String |
NotMapped | Ignore | 无需映射该属性 | All |
ConcurrencyCheck | IsConcurrencyToken | 该列需要被用作为乐观并发检查
注意:不要使用无长度上限的string属性,因为这会被映射为LOB类型。使用LOB类型的列最为并发检查会导致ORA-00932: inconsistent datatypes error.错误。 |
All |
TimeStamp | IsRowVersion | 并发控制字段 | Not Supported |
Column | HasColumnType | 指定对应数据库数据列类型。
注意:必须是合法的兼容类型。例如一个Date属性是不能映射到number列的。 |
All |
N/A | IsUnicode | 表示映射到一个N-type类型(nvarchar2或nclob),默认为true。 | String |
N/A | HasPrecision | 表示设置decimal的精度。 | Decimal |
Code First数据迁移
数据迁移的方式和使用SQLServer是一致的,但是需要注意以下两点:
- 唯一能自定义的是更改表的user schema
- Code First自动迁移只能使用dbo schema。所以需要显示的使用Add-Migration命令进行基于代码的数据迁移。
Code First数据库初始化
ODP.NET支持一下几种数据库初始化方式:
- CreateDatabaseIfNotExists (默认)
- DropCreateDatabaseAlways
- DropCreateDatabaseIfModelChanges
- NullDatabaseInitializer
- MigrateDatabaseToLatestVersion
由于Oracle和SQL Server对数据库(database)的定义不同,数据库初始化动作作用于模型中所有Oracle对象。Oracle数据库没有新建或删除,而构成该模型的对象被认为是对数据库的一种操作。
Oracle数据库对象创建
为了支持客户端应用程序,ODP.NET创建和维护所需的数据库对象。下面列举了provider提供的可创建和维护的数据库对象:
- Table
- Table Column
- Primary Key
- Foreign Key
- Index
- Sequence
- Trigger
注意:
Sequence和Trigger在Oracle 11g R2之后的版本中创建,早期版本的数据库支持标识列。
直接和客户端相关的对象,即:一个表对应一个类,一个列对应一个属性,这些对象的命名都是由客户端提供的,对象的命名必须满足Oracle数据库中对对象标识长度的限制。如果类名的长度超长了,那么在创建这个对象时就会报ORA-00972: identifier is too long的异常。
对于剩余的对象,如果提供的命名长度大于数据库标识长度显示,那么ODP.NET回利用命名生成算法。如果提供的命名未超长那就直接使用。在所有情况下,对象名称会被创建为带引号的标识符一方面是为了保持大小写,另一方面任何特殊字符都可以作为标识符的一部分。
命名算法会按照下面两种方式工作:
- 从头开始截取原始名称中的一段
- 从原名称计算出一个数字后缀
下面例子演示一个类对象名称是如何被截取的:
public class LongSamplePocoTestClassName
{
[Key]
public int Id { get; set; } [MaxLength()]
public string Name { get; set; }
}
默认生成的主键名称为:
PK_LongSamplePocoTestClassNames
这个名称包含31(最多支持30个字符)个字符,超出了限制。将对其做截取操作,最后生成:
PK_LongSamplePocoTes_730795129
算法被设计为尽可能的保留更多的原始名称。
原创文章,转载请注明: 转载自xdlysk的博客