修改EFOracleProvider——让EFOracleProvider支持自增长类型
我们知道Oracle不像SqlServer那样,支持原生的自动增长型,而是通过sequence来实现类似于自增长类型的效果。
对于以项目为主的公司而言,往往需要做到数据库之间的切换。而当初引入Entity Framework,一个重要的目标就是不同数据库之间的快速切换。
现在面临的一个问题就是:自增长类型,如果做到不用修改代码,就能支持无缝切换
说句题外话,关于主键类型的选择
1. 自增长类型
优:简单、查找方便、空间占用少
缺:不同类型数据库的支持不同、数据同步
2. Guid 的string
优:数据同步较方便
缺:占用空间大——一般用32个字符,查找不便——没有先后次序规律
3. 自定义的生成规则
嗯,不管你们用什么类型的主键,反正我是需要改造一下EFOracleProvider来支持如下的功能
//去掉设置主键值
//entity.ID = 20;
entity.Column1 = something;
//dbContext.Add(entity);
在动手前,我们需要先约定以下规则:
1. 所有表的主键名称统一叫ID
2. 为每个表建立一个序列(sequence),规则是 SEQ_表名(序列的名字长度不能超过30个字符,所以表名不要超过26个字符)
完成该功能,需要修改以下3个文件(共4处地方)
1. Resources\EFOracleStoreSchemaDefinition.ssdl
2. SqlGen\DmlSqlGenerator.cs
3. SqlGen\SqlGenerator.cs
1. 修改 EFOracleStoreSchemaDefinition.ssdl
定位到第36行,将
, 0 "IsIdentity"
修改为:
, case c.COLUMN_NAME when 'ID' then 1 else 0 end "IsIdentity"
目的在于,告诉生成工具(edmgen2,网上搜) ,如果是ID字段,在edmx文件中,加入
<Property Name="ID" Type="number" Nullable="false" Precision="10" />
变成
<Property Name="ID" Type="number" Nullable="false" Precision="10" StoreGeneratedPattern="Identity" />
2. 修改insert语句的生成规则,在DmlSqlGenerator.cs文件中,找到GenerateInsertSql方法,然后用如下代码替换
{
StringBuilder commandText = new StringBuilder(s_commandTextBuilderInitialCapacity);
ExpressionTranslator translator = new ExpressionTranslator(commandText, tree,
null != tree.Returning, sqlVersion);
// insert [schemaName].[tableName]
commandText.Append("insert into ");
tree.Target.Expression.Accept(translator);
if (0 < tree.SetClauses.Count)
{
// (c1, c2, c3, ...)
commandText.Append("(");
bool first = true;
var dbNewInstanceExpression = tree.Returning as DbNewInstanceExpression;//自增长ID,zhb added
bool hasAutoIdentity = dbNewInstanceExpression != null && dbNewInstanceExpression.Arguments.Count > 0;
if (hasAutoIdentity)
{
first = false;
dbNewInstanceExpression.Arguments[0].Accept(translator);
}
foreach (DbSetClause setClause in tree.SetClauses)
{
if (first) { first = false; }
else { commandText.Append(", "); }
setClause.Property.Accept(translator);
}
commandText.AppendLine(")");
// values c1, c2, ...
first = true;
commandText.Append("values (");
if (hasAutoIdentity)
{
translator.Sequence(tree.Target.Expression as DbScanExpression);
first = false;
}
foreach (DbSetClause setClause in tree.SetClauses)
{
if (first) { first = false; }
else { commandText.Append(", "); }
setClause.Value.Accept(translator);
translator.RegisterMemberValue(setClause.Property, setClause.Value);
}
commandText.AppendLine(")");
}
else
{
// default values
commandText.AppendLine().AppendLine("default values");
}
// generate returning sql
GenerateReturningSql(commandText, tree, translator, tree.Returning, providerManifest, sqlVersion);
parameters = translator.Parameters;
return commandText.ToString();
这段代码的作用在于,将原本
insert into t(column1) values('value1')
变成
insert into t(id, column1) values(seq_t.nextval, 'value1')
3. 由于我们在第2步时,调用了
translator.Sequence(tree.Target.Expression as DbScanExpression);
所以我们需要修改一下ExpressionTranslator 类(在DmlSqlGenerator.cs文件中),为其添加一个Sequece的方法,如下
{
_commandText.Append(SqlGenerator.GetSequenceSql(scanExpression.Target));
}
4. 由于第3步,sequence的生成,又转到了SqlGenerator类中,于是我们在SqlGenerator.cs中,添加GetSequenceSql方法
{
// ##ORACLE
// CONSIDER caching generated SQL here
string definingQuery = MetadataHelpers.GetMetadataProperty<string>(entitySetBase,
MetadataHelpers.DefiningQueryMetadata);
if (true)
{
// construct escaped T-SQL referencing entity set
StringBuilder builder = new StringBuilder(50);
string table = MetadataHelpers.GetMetadataProperty<string>(entitySetBase,
MetadataHelpers.TableMetadata);
if (!string.IsNullOrEmpty(table))
{
builder.Append("seq_" + table);
}
else
{
builder.Append("seq_" + entitySetBase.Name);
}
builder.Append(".nextval");
return builder.ToString();
}
至此,修改代码工作算是完成了,编译重新发布吧