XslCompiledTransform和定制XmlUrlResolver:“具有相同键的项已存在”

2019-09-21 00:29发布

有没有一种方法来调试从数据库中通过自定义XmlUrlResolver加载或有没有人知道XSLT文件,有什么下面的errormessage的是什么?

我有一个进口一个共同的XSLT文档XSLT样式表:

<xsl:import href="db://common.hist.org"/>

该计划由一个自定义处理XmlResolver加载从DB的XSLT文件,但我得到一个错误:

具有相同键的条目已经存在。

常见的XSLT文件提到了xsl:import包含一些常见的XSLT模板,每一个独特的名字。

这个错误已经开始从本地文件系统到移动的数据库XSLT文档后出现。 当使用指向本地文件的默认导入计划,并加载从本地文件系统的XSLT文档时,不会发生错误。

我也尝试创建的实例时打开调试XslCompiledTransform ,但不知何故,这是不可能的“步入”基于数据库的XSLT。

_xslHtmlOutput = new XslCompiledTransform(XSLT_DEBUG);

更新:下面是基本的解析代码的要求,但例外是不是我的代码内部发生; 因此,我想在下面这段代码没有明显的理由。 (此相同的代码实际上是用来加载包含进口,并注释掉进口一切正常时,XSLT样式表。)

public class XmlDBResolver : XmlUrlResolver
{
    private IDictionary<string,string> GetUriComponents(String uri)
    {
        bool useXmlPre = false;
        uri = uri.Replace("db://", "");
        useXmlPre = uri.StartsWith("xml/");
        uri = uri.Replace("xml/", "");
        IDictionary<string, string> dict = new Dictionary<string, string>();
        string app = null, area = null, subArea = null;

        if (!String.IsNullOrWhiteSpace(uri))
        {
            string[] components = uri.Split('.');

            if (components == null)
                throw new Exception("Invalid Xslt URI");

            switch (components.Count())
            {
                case 3:
                    app = components[0];
                    break;
                case 4:
                    area = components[0];
                    app = components[1];
                    break;
                case 5:
                    subArea = components[0];
                    area = components[1];
                    app = components[2];
                    break;
                default:
                    throw new Exception("Invalid Xslt URI");
            }

            dict.Add("application", app);
            dict.Add("area", area);
            dict.Add("subArea", subArea);
            dict.Add("xmlPreTransform", String.Format("{0}", useXmlPre));
        }

        return dict;
    }

    public override System.Net.ICredentials Credentials
    {
        set { /* TODO: check if we need credentials */ }
    }

    public override object GetEntity(Uri absoluteUri, string role, Type ofObjectToReturn)
    {
        /*
         *  db://<app>.hist.org
         *  db://<area>.<app>.hist.org
         *  db://<subArea>.<area>.<app>.hist.org
         * 
         * */

        Tracing.TraceHelper.WriteLine(String.Format("GetEntity {0}", absoluteUri));

        XmlReader reader = null;

        switch (absoluteUri.Scheme)
        {
            case "db":
                string origString = absoluteUri.OriginalString;
                IDictionary<string, string> xsltDict = GetUriComponents(origString);

                if(String.IsNullOrWhiteSpace(xsltDict["area"]))
                {
                    reader = DatabaseServiceFactory.DatabaseService.GetApplicationXslt(xsltDict["application"]);
                }
                else if (!String.IsNullOrWhiteSpace(xsltDict["area"]) && String.IsNullOrWhiteSpace(xsltDict["subArea"]) && !Boolean.Parse(xsltDict["xmlPreTransform"]))
                {
                    reader = DatabaseServiceFactory.DatabaseService.GetAreaXslt(xsltDict["application"], xsltDict["area"]);
                }
                else if (!String.IsNullOrWhiteSpace(xsltDict["area"]) && !String.IsNullOrWhiteSpace(xsltDict["subArea"]))
                {
                    if(Boolean.Parse(xsltDict["xmlPreTransform"]))
                        reader = DatabaseServiceFactory.DatabaseService.GetSubareaXmlPreTransformXslt(xsltDict["application"], xsltDict["area"], xsltDict["subArea"]);
                    else
                        reader = DatabaseServiceFactory.DatabaseService.GetSubareaXslt(xsltDict["application"], xsltDict["area"], xsltDict["subArea"]);
                }
                return reader;

            default:
                return base.GetEntity(absoluteUri, role, ofObjectToReturn);
        }
    }

和完整性IDatabaseService接口(相关部分):

public interface IDatabaseService
{
    ...
    XmlReader GetApplicationXslt(String applicationName);
    XmlReader GetAreaXslt(String applicationName, String areaName);
    XmlReader GetSubareaXslt(String applicationName, String areaName, String subAreaName);
    XmlReader GetSubareaXmlPreTransformXslt(String applicationName, String areaName, String subAreaName);
}

更新:我试图从Web服务器来代替,该工程暂时加载样式表来隔离问题。 我了解到,在SQL Server存储显然只XML片段没有XML声明,在对比的是样式表被存储在网络服务器。

更新:异常的堆栈跟踪:

System.Xml.Xsl.XslLoadException:XSLT-Kompilierungsfehler。 Fehler贝(9,1616)。 ---> System.ArgumentException:用相同键的条目已经存在..贝System.Collections.Specialized.ListDictionary.Add(对象键,对象的值)贝System.Collections.Specialized.HybridDictionary.Add(对象键,对象值)贝System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(的XmlReader读取器,布尔包括)贝System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(URI的URI,布尔包括)贝System.Xml.Xsl.Xslt.XsltLoader .LoadStylesheet(XmlReader中的读者,布尔包括)---恩德德inneren Ablaufverfolgung DES Ausnahmestacks ---贝System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(XmlReader中的读者,布尔包括)北System.Xml.Xsl.Xslt.XsltLoader .Load(的XmlReader读取器)贝System.Xml.Xsl.Xslt.XsltLoader.Load(编译器的编译器,对象的样式表,的XmlResolver的XmlResolver)贝System.Xml.Xsl.Xslt.Compiler.Compile(对象的样式表,的XmlResolver的XmlResolver,QilExpression&QIL)贝System.Xml.Xsl.XslCompiledTransform.LoadInternal(对象样式,XsltSettings设置,XmlResolve [R stylesheetResolver)贝System.Xml.Xsl.XslCompiledTransform.Load(字符串stylesheetUri,XsltSettings设置的XmlResolver stylesheetResolver)北(我的命名空间和类).GetXslTransform(预转换布尔)北(我的命名空间和类).get_XslHtmlOutput()北(我命名空间和类).get_DisplayMarkup()

Answer 1:

简短的回答:

IDatabaseService接口方法返回XmlReader对象。 当你构建这些,确保一个传递baseUri来构造; 例如:

public XmlReader GetApplicationXslt(string applicationName)
{
    …
    var baseUri = string.Format("db://{0}.hist.org", applicationName);
    return XmlReader.Create(input: …, 
                            settings: …,
                            baseUri: baseUri);  // <-- this one is important!
}

如果指定此参数,一切都可能只是正常工作。 看到这个答案的最后一节,看看为什么我建议这一点。


长的答案,介绍:可能的错误来源:

让我们首先简要思考一下成分(S)可能会导致错误:

“这个错误已经开始移动的XSLT文件从本地文件系统到数据库后发生。当使用指向本地文件的默认导入计划,并从本地文件系统加载XSLT文件时,不会发生错误。”

在数据库中把样式表意味着你必须有...

  1. 改变了进口路径的样式表(介绍db://…路径)
  2. 实施并迷上了一个自定义XmlDbResolver用于处理db://进口计划
  3. 的形式实现的数据库访问代码IDatabaseService ,它备份XmlDbResolver

如果样式表除了进口路径不变,这似乎可能是错误或者是在你XmlResolver类和/或IDatabaseService实施。 既然你还没有表现出对后者的代码,我们不能没有一些猜测调试代码。

我创建使用一个模拟项目XmlDbResolver (下面的完整描述如下)。 我无法重现的错误,所以我怀疑你IDatabaseService执行导致错误。

更新:我已经能够重现错误。 见OP的评论和这个答案的最后一节。


我试图重现你的错误:

我创建在Visual Studio 2010(你可以通过克隆检索控制台应用程序项目这个要点使用Git( git clone https://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git ),然后检查出的第2犯, git checkout d00629 )。 我将描述在下面详细的每个解决方案的项目。

(请注意, 复制到输出目录属性SqlServerDatabase.mdfTestInput.xml ,和两个.xslt工程项目应始终设置为。)


SqlServerDatabase.mdf:

这是我将附加到SQL Server Express 2008的本地实例基于服务的数据库(这是通过在连接字符串进行App.config ;见下文)。

我已经建立了这个数据库中的下列项目:

该表包含其定义如下两列:

该表最初是空的。 测试数据将在运行时被添加到数据库中(见Program.csCommonHistOrg.xslt下文)。


App.config中:

此文件包含上述数据库连接字符串项。

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="SqlServerDatabase"
         connectionString="Data Source=.\SQLEXPRESS;
                           AttachDbFilename=|DataDirectory|\SqlServerDatabase.mdf;
                           Integrated Security=True;
                           User Instance=True"
         />
  </connectionStrings>
</configuration>

IDatabaseService.cs:

此文件包含您定义IDatabaseService接口,我不在这里重复。


SqlServerDatabaseService.cs:

这包含了实现类IDatabaseService 。 它读取上面的数据库/写入数据:

using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data.SqlClient;
using System.Data.SqlTypes;
using System.IO;
using System.Xml;

class SqlServerDatabaseService : IDatabaseService
{
    // creates a connection based on connection string from App.config: 
    SqlConnection CreateConnection()
    {
        return new SqlConnection(connectionString: ConfigurationManager.ConnectionStrings["SqlServerDatabase"].ConnectionString);
    }

    // stores an XML document into the 'ApplicationDocuments' table: 
    public void StoreApplicationDocument(string applicationName, XmlReader document)
    {
        using (var connection = CreateConnection())
        {
            SqlCommand command = connection.CreateCommand();
            command.CommandText = "INSERT INTO ApplicationDocuments (ApplicationName, Document) VALUES (@applicationName, @document)";
            command.Parameters.Add(new SqlParameter("@applicationName", applicationName));
            command.Parameters.Add(new SqlParameter("@document", new SqlXml(document)));
                                                             //  ^^^^^^^^^^^^^^^^^^^^
            connection.Open();
            int numberOfRowsInserted = command.ExecuteNonQuery();
            connection.Close();
        }
    }

    // reads an XML document from the 'ApplicationDocuments' table:
    public XmlReader GetApplicationXslt(string applicationName)
    {
        using (var connection = CreateConnection())
        {
            SqlCommand command = connection.CreateCommand();
            command.CommandText = "SELECT Document FROM ApplicationDocuments WHERE ApplicationName = @applicationName";
            command.Parameters.Add(new SqlParameter("@applicationName", applicationName));

            connection.Open();
            var plainXml = (string)command.ExecuteScalar();
            connection.Close();

            if (plainXml != null)
            {
                return XmlReader.Create(new StringReader(plainXml));
            }
            else
            {
                throw new KeyNotFoundException(message: string.Format("Database does not contain a application document named '{0}'.", applicationName));
            }
        }
    }

    … // (all other methods throw a NotImplementedException)
}

XmlDbResolver.cs:

这包含XmlDbResolver类,它等同于你的XmlDBResolver除了两个变化类:

  1. 公共构造函数接受一个IDatabaseService对象。 这是用来代替DatabaseServiceFactory.DatabaseService

  2. 我不得不删除调用Tracing.TraceHelper.WriteLine


CommonHistOrg.xslt:

这是db://common.hist.org样式表,这将在运行时被放入数据库中(见Program.cs下面):

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:template match="Foo">
    <Bar/>
  </xsl:template>
</xsl:stylesheet>

TestStylesheet.xml:

这是引用上述一个样式表db://common.hist.org样式表:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  <xsl:import href="db://common.hist.org"/>
</xsl:stylesheet>

TestInput.xml:

这是我们要利用上述转换XML测试输入文档TestStylesheet.xslt

<?xml version="1.0" encoding="utf-8" ?>
<Foo/>

Program.cs中:

这包含测试应用程序的代码:

using System;
using System.Text;
using System.Xml;
using System.Xml.Xsl;

class Program
{
    static void Main(string[] args)
    {
        var databaseService = new SqlServerDatabaseService();

        // put CommonHistOrg.xslt into the 'ApplicationDocuments' database table:
        databaseService.StoreApplicationDocument(
            applicationName: "common",
            document:        XmlReader.Create("CommonHistOrg.xslt"));

        // load the XSLT stylesheet:
        var xslt = new XslCompiledTransform();
        xslt.Load(@"TestStylesheet.xslt", 
            settings: XsltSettings.Default, 
            stylesheetResolver: new XmlDbResolver(databaseService));

        // load the XML test input:
        var input = XmlReader.Create("TestInput.xml");

        // transform the test input and store the result in 'output':
        var output = new StringBuilder();
        xslt.Transform(input, XmlWriter.Create(output));

        // display the transformed output:
        Console.WriteLine(output.ToString());
        Console.ReadLine();
    }
}

就像我的机器上的魅力:输出与空根元素的XML文档<Bar/>这是什么db://common.hist.org样式表输出用于匹配的<Foo/>从测试元件输入。


更新:错误复制与修复:

  1. 插入在下面的语句Main方法:

     databaseService.StoreApplicationDocument( applicationName: "test", document: XmlReader.Create("TestStylesheet.xslt")); 
  2. 代替

     xslt.Load(@"TestStylesheet.xslt", …); 

     xslt.Load(@"db://test.hist.org", …); 

    这触发由OP报告的错误。

一些调试后,我发现了以下不会导致此问题。

  • 该事实Document数据库中的表列的类型为XML 。 它失败, NTEXT了。

  • 事实上,在<?xml … ?>头从被从数据库返回的文件丢失。 错误仍然存在即使当XML头被手动添加回之前SqlServerDatabaseService将控制返回给框架。

事实上,在某处在.NET Framework代码引发的错误。 这就是为什么我决定下载并安装.NET框架参考源 。 (我改变了用于调试的目的框架3.5版本的解决方案。)安装此并重新启动VS然后让你看到和调试会话期间通过框架代码一步。

在调用启动xslt.Load(…;)在我们的Main方法,我走进了框架代码,并最终来到了一个方法LoadStylesheetXsltLoader.cs 。 有一个HybridDictionarydocumentUrisInUse ,这显然已存储装样式表的基准URI。 因此,如果我们加载多个样式与空或缺少基础URI,此方法将尝试添加null到字典两次; 这是什么原因造成的错误。

所以一旦你分配一个唯一的基URI你返回的每个样式IDatabaseService ,一切都应该正常工作。 您可以通过将一个做到这一点baseUriXmlReader构造。 看到我的回答最开始的代码示例。 您还可以检索更新,致力于通过解下载或克隆此吉斯特 ( git clone https://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git )。



文章来源: XslCompiledTransform and custom XmlUrlResolver: “An entry with the same key already exists”