在SQL Server中,sql_variant数据类型允许存储多种不同的数据类型。然而,Entity Framework并没有内置支持这种数据类型,这导致在处理包含sql_variant列的数据库时会遇到一些限制。幸运的是,可以通过一些技巧来绕过这些限制。本文将介绍一种方法,它允许使用Entity Framework的EDM(实体数据模型)来读取sql_variant类型的列。
在应用程序中,需要对一个相对频繁使用sql_variant数据类型的数据库进行只读访问。不幸的是,很快发现Entity Framework并没有为这种数据类型提供内置支持:所有的sql_variant列都被排除在了向导生成的EDM之外,并且伴随着一个不友好的警告。幸运的是,Entity Framework提供了两个可以结合使用的功能来读取sql_variant:复杂类型(Complex Types)和定义查询(DefiningQuery)。实现的转换将sql_variant转换为varbinary,并将二进制表示转换为对象,使用一系列长但直接的规则。
以下步骤假设正在使用Visual Studio,数据库表已经存在,并且已经导入了一个EDM文件,其中只缺少sql_variant列。这些步骤还假设对Entity Framework的概念和工具有基本的了解。
在模型浏览器中,添加一个名为SqlVariant的新复杂类型,它有两个属性:一个名为BaseType的私有字符串属性,和一个名为Representation的私有二进制属性。
在EDM编辑器中,为EDM向导跳过的每个sql_variant字段添加一个类型为SqlVariant的只读复杂属性。根据采用的命名约定命名属性,以匹配数据库中相应列的名称。
在XML编辑器中打开EDM文件,并为包含sql_variant列的表的每个实体集添加一个DefiningQuery标签。除了列出表的所有“常规”列之外,查询还需要为每个sql_variant列包含一对表达式:
cast([mytable].[mycolumn] as varbinary) as [RepMyColumn]
and
sql_variant_property([mytable].[mycolumn], 'BaseType') as [BaseTypeMyColumn]
替换"mytable" EntitySet的Schema="dbo"属性为store:Name="mytable"。
为每个sql_variant列向每个实体添加两个列。将它们命名为RepMyColumn和BaseTypeMyColumn,其中MyColumn是sql_variant列的名称。
切换回EDM编辑器,按照以下方式映射SqlVariant属性:
BaseType maps to BaseTypeMyColumn;
Representation maps to RepMyColumn.
实现的高层次描述可以用一句话概括:它是一个基于sql_variant的基础类型的switch语句,后面跟着从二进制表示构造.NET对象。
public object Converted {
get {
if (Representation == null || BaseType == null) {
return null;
}
switch (BaseType) {
case "uniqueidentifier":
return new Guid(Representation);
case "char":
case "varchar":
return GetString(Representation);
case "nvarchar":
case "nchar":
return GetNlString(Representation);
// More cases covering other base types...
}
throw new InvalidOperationException("Unsupported SQL type: '" + BaseType + "'");
}
}
考虑到本文提供的实现支持十五种基础类型,代码中有很多细节编码到各个转换器中并不奇怪。然而,所有的转换器都遵循相同的基本模式:首先,它们检查字节数组是否为预期的长度,然后使用BitConverter构造结果的元素,最后构造结果。转换nvarchar字符串的代码提供了一个很好的示例,说明各个转换器是如何工作的:
private static string GetNlString(byte[] src) {
if (src.Length % 2 != 0) {
throw new InvalidOperationException("NLS format is invalid.");
}
var buf = new char[src.Length / 2];
for (var pos = 0; pos != src.Length; pos += 2) {
buf[pos / 2] = BitConverter.ToChar(src, pos);
}
return new string(buf);
}
现在,可以通过访问步骤2中添加的复杂属性的Converted属性来读取SqlVariant列中的数据:
foreach (var x in myEdm.myTableWithSqlVariantDatas) {
var obj = x.MySqlVariantColumn.Converted;
...
}
代码缺乏对一些SQL Server基础类型的支持。然而,可以通过遵循实现的一般模式来添加感兴趣的类型。
由于这种实现只提供了对sql_variant列的读取访问,既不能写入这些列,也不能针对它们的值运行数据库查询。