在 Entity Framework 中读取 sql_variant






4.89/5 (10投票s)
处理 Entity Framework 排除 sql_variant 的方法
引言
尽管 Entity Framework 不支持内置的 SQL Server sql_variant 数据类型 [1][2],但可以通过变通方法来处理。本文介绍了一种方法,可让您使用 Entity Framework EDM 读取 sql_variant
类型的列。
背景
我们需要一个只读访问数据库的方式,该数据库在一个使用 Entity Framework 的应用程序中相对大量地使用了 sql_variant
。不幸的是,我们很快发现它不支持此数据类型的列:所有 sql_variant
列都被排除在向导生成的 EDM 之外,并带有烦人的警告。幸运的是,Entity Framework 提供了两个可以结合使用的功能来读取 sql_variant
:复杂类型和DefiningQuery。此实现将 sql_variant
转换为 varbinary
,并将二进制表示转换为对象,这需要一套冗长但直观的规则。
将 sql_variant 添加到您的模型
以下步骤假设您正在使用 Visual Studio,并且您的数据库表已存在,并且您已导入了一个 EDM 文件,其中仅缺少 sql_variant
列。这些步骤还假定您对 Entity Framework 的概念和工具有基本的了解。
- 在 模式浏览器 中,添加一个名为
SqlVariant
的新复杂类型,它有两个属性:一个名为BaseType
的private string
属性,以及一个名为Representation
的private Binary
属性。 - 在 EDM 编辑器中,为 EDM 向导跳过的每个
sql_variant
字段添加一个类型为SqlVariant
的只读复杂属性。根据您采用的命名约定命名这些属性,以匹配它们在数据库中对应列的名称。 - 在 XML 编辑器中打开您的 EDM 文件,并为包含
sql_variant
列的每个表上的实体集添加一个DefiningQuery
标签。除了列出您表的所有“常规”列之外,该查询还需要为每个sql_variant
列包含一对表达式cast([mytable].[mycolumn] as varbinary) as [RepMyColumn]
和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
映射到BaseTypeMyColumn
;Representation
映射到RepMyColumn
。 - 下载本文附带的源文件,并将其命名空间更改为与您的项目匹配。这将为
SqlVariant
类型添加一个名为 'Converted
' 的object
类型属性。使用此列来访问您的sql_variant
的值。
实现
该实现的概要描述可以浓缩为一句话:它是一个基于 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 string
转换为 .NET 字符串的代码很好地说明了各个转换器的工作原理。
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);
}
Using the Code
您现在可以通过访问您在步骤 2 中添加的复杂属性的 Converted
属性来读取 SqlVariant
列中的数据。
foreach (var x in myEdm.myTableWithSqlVariantDatas) {
var obj = x.MySqlVariantColumn.Converted;
...
}
限制
- 该代码缺少对某些 SQL Server 基本类型的支持。但是,您可以遵循实现的一般模式来添加您感兴趣的类型。
- 由于此实现仅提供对
sql_variant
列的只读访问,因此您既不能写入这些列,也不能对它们的值运行数据库查询。 - 该实现“理解”的二进制格式高度依赖于 SQL Server 2008,因此在未来版本的数据库中可能无法正常工作。但是,我希望 Entity Framework 团队能在 SQL Server 团队发布二进制不兼容版本之前为
sql_variant
添加原生支持 :-) - 毋庸置疑,本文提供的代码可能存在 bug。它被提供为实现起点,而非即插即用库。