我正在使用 Entity Framework 来查询由模型定义的数据库:在这个模型中,我有几个具有#region动态值
的类:
[DataContract]
public class Job : AbstractEntity, IJob
{
[DataMember]
public virtual Guid Id { get; set; }
...
#region dynamic values
[DataMember]
public virtual string MetadataValue { get; set; }
[DataMember]
public virtual string ParametersValue { get; set; }
[DataMember]
public virtual string AttributesValue { get; set; }
#endregion
#region links
...
#endregion
}
AttributesValue
、MetadataValue
和 ParametersValue
声明为字符串,但作为 XML 文档存储在数据库中。我知道这与模型不一致,应该更改,但由于某些原因,它已经以这种方式管理,并且不允许我修改它。
为了更好地处理问题,我创建了一个单元测试,代码如下:
public class UnitTest1
{
private ModelContext mc;
[TestInitialize]
public void TestInit()
{
IModelContextFactory mfactory = ModelContextFactory.GetFactory();
mc = mfactory.CreateContextWithoutClientId();
}
[TestMethod]
public void TestMethod1()
{
DbSet<Job> jobs = mc.Job;
IQueryable<string> query = jobs
.Where(elem => elem.AttributesValue == "<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>")
.Select(elem => elem.AttributesValue);
List<string> attrs = new List<string>(query);
foreach (string av in attrs)
{
Console.WriteLine(av ?? "null");
}
Assert.AreEqual(1, 1);
}
}
关于 TestInit
和 ModelContext
的快速解释:
ModelContext
继承自 DbContext
,是由 SqlModelContext
和 OracleModelContext
实现的抽象类(均重写 OnModelCreating
)。根据连接字符串,CreateContextWithoutClientId
返回 SqlModelContext
或 OracleModelContext
。摘要:工厂模式。
让我们开始讨论正题:TestMethod1
。
这里的问题出在 Where
方法中,返回的错误正如预期的那样:
SqlException: The data types nvarchar and xml are incompatible in the equal to operator.
(从现在开始我只会考虑 AttributesValue
属性)
我想到了一些可能的解决方案,它们是:
在模型内创建一个新属性(但未映射到数据库)并将其用作“代理”,而不是直接访问
AttributesValue
。然而,只有映射的属性可以在 Linq 中使用,所以我放弃了它。直接对
IQueryable
生成的内部 SQL 查询进行操作,并使用针对 Oracle 和 Sql Server 数据库的自定义CAST
。由于显而易见的原因,我宁愿避免这样做。
有没有办法指定自定义属性 Getter,以便我可以在访问 AttributesValue
之前将其转换为字符串?或者可能是 DbModelBuilder
上的一些配置?
我正在使用标准 Entity Framework 6、代码优先方法。
最佳答案
没有标准的 xml 数据类型或标准规范函数用于将字符串转换为 xml,反之亦然。
幸运的是 EF6 支持所谓的 Entity SQL Language它支持一个名为 CAST 的有用构造。 :
CAST (expression AS data_type)
The cast expression has similar semantics to the Transact-SQL CONVERT expression. The cast expression is used to convert a value of one type into a value of another type.
它可以在 EntityFramework.Functions 的帮助下使用包和Model defined functions .
模型定义的函数允许您将 Entity SQL 表达式与用户定义的函数关联起来。要求是函数参数必须是实体。
Entity SQL 运算符的好处是它们独立于数据库(类似于规范函数),因此最终的 SQL 仍然由数据库提供者生成,因此您不需要为 SqlServer 和 Oracle 编写单独的实现。
通过Nuget安装EntityFramework.Functions包并添加以下类(注意:所有代码都需要 using EntityFramework.Functions;
):
public static class JobFunctions
{
const string Namespace = "EFTest";
[ModelDefinedFunction(nameof(MetadataValueXml), Namespace, "'' + CAST(Job.MetadataValue AS String)")]
public static string MetadataValueXml(this Job job) => job.MetadataValue;
[ModelDefinedFunction(nameof(ParametersValueXml), Namespace, "'' + CAST(Job.ParametersValue AS String)")]
public static string ParametersValueXml(this Job job) => job.ParametersValue;
[ModelDefinedFunction(nameof(AttributesValueXml), Namespace, "'' + CAST(Job.AttributesValue AS String)")]
public static string AttributesValueXml(this Job job) => job.AttributesValue;
}
基本上我们为每个 xml 属性添加简单的扩展方法。这些方法的主体并不执行任何有用的操作 - 这些方法的全部目的不是直接调用,而是在 LINQ to Entities 查询中使用时转换为 SQL。所需的映射是通过 ModelDefinedFunctionAttribute 提供的,并通过包实现的自定义 FunctionConvention
应用。 。 Namespace
常量必须等于 typeof(Job).Namespace
。不幸的是,由于要求属性只能使用常量,我们无法避免硬编码字符串以及 Entity SQL 字符串内的实体类/属性名称。
需要更多解释的一件事是 '' + CAST
的用法。我希望我们可以简单地使用 CAST
,但我的测试表明 SqlServer“太智能”(或有缺陷?),并且在 内部使用时从表达式中删除
。附加空字符串的技巧可以防止这种行为。CAST
哪里
然后,您需要通过将以下行添加到数据库上下文 OnModelCreating
重写中,将这些函数添加到实体模型中:
modelBuilder.AddFunctions(typeof(JobFunctions));
现在您可以在 LINQ to Entities 查询中使用它们:
IQueryable<string> query = jobs
.Where(elem => elem.AttributesValueXml() == "<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>")
.Select(elem => elem.AttributesValue);
在 SqlServer 中翻译成这样:
SELECT
[Extent1].[AttributesValue] AS [AttributesValue]
FROM [dbo].[Jobs] AS [Extent1]
WHERE N'<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>'
= ('' + CAST( [Extent1].[AttributesValue] AS nvarchar(max)))
在 Oracle 中:
SELECT
"Extent1"."AttributesValue" AS "AttributesValue"
FROM "ORATST"."Jobs" "Extent1"
WHERE ('<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>'
= ((('')||(TO_NCLOB("Extent1"."AttributesValue")))))
关于c# - 以 XML 形式存储的查询字符串属性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51098752/