c# - 以 XML 形式存储的查询字符串属性

标签 c# sql xml entity-framework linq

我正在使用 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
}

AttributesValueMetadataValueParametersValue 声明为字符串,但作为 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);
    }
}

关于 TestInitModelContext 的快速解释: ModelContext 继承自 DbContext,是由 SqlModelContextOracleModelContext 实现的抽象类(均重写 OnModelCreating)。根据连接字符串,CreateContextWithoutClientId 返回 SqlModelContextOracleModelContext。摘要:工厂模式。

让我们开始讨论正题: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/

相关文章:

c# - Func vs. Action vs. Predicate

sql - PostgreSQL - 左连接错误计数输出

xml - 对派生的复杂类型使用 xml 类型属性

java - 使用 JAXB 解码 xml 字符串但得到空字符串

java - Java 中的 WebDriver C# 扩展方法

c# - 是否可以提取具有主类属性的对象?

c# - Windows Azure 服务总线 - 具有枚举值的 BrokeredMessage.Properties

Sqlite,用斜杠替换反斜杠

mysql - SQL 内连接表

php - 如何使用 php 导出 mysql 表