XslCompiledTransform和自定义XmlUrlResolver:"具有相同键的条目已存在"

Ole*_*rat 5 c# xml sql-server xslt xslcompiledtransform

有没有办法调试由自定义XmlUrlResolver从数据库加载的XSLT文档,或者有人知道,下面的错误消息是什么?

我有一个XSLT样式表,可以导入一个常见的xslt文档:

<xsl:import href="db://common.hist.org"/>
Run Code Online (Sandbox Code Playgroud)

该方案由一个XmlResolver从数据库加载XSLT文档的自定义处理,但是我收到一个错误:

已存在具有相同密钥的条目.

它引用的通用XSLT文档xsl:import包含一些常见的XSLT模板,每个模板都有一个唯一的名称.

将XSLT文档从本地文件系统移动到数据库后,此错误开始发生.使用指向本地文件的默认导入方案时以及从本地文件系统加载XSLT文档时,不会发生错误.

我还尝试在创建实例时启用调试XslCompiledTransform,但不知何故,不可能"进入"基于数据库的XSLT.

_xslHtmlOutput = new XslCompiledTransform(XSLT_DEBUG);
Run Code Online (Sandbox Code Playgroud)

更新:以下基本上是请求的解析器​​代码,但我的代码中没有发生异常; 因此我想在下面的代码中没有明显的原因.(这个相同的代码实际上用于加载包含导入的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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

为了完整性,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);
}
Run Code Online (Sandbox Code Playgroud)

更新:我试图通过从Web服务器临时加载样式表来隔离问题,这是有效的.我了解到SQL Server显然只存储没有XML声明的XML片段,而不是存储在Web服务器上的样式表.

更新:异常的堆栈跟踪:

System.Xml.Xsl.XslLoadException:XSLT-Kompilierungsfehler.Fehler bei(9,1616).---> System.ArgumentException:具有相同键的条目已存在.bei System.Collections.Specialized.ListDictionary.Add(Object key,Object value)bei System.Collections.Specialized.HybridDictionary.Add(Object key,Object值)System System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(XmlReader reader,Boolean include)bei System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(Uri uri,Boolean include)bei System.Xml.Xsl.Xslt.XsltLoader .LoadStylesheet(XmlReader reader,Boolean include)--- Ende der inneren Ablaufverfolgung des Ausnahmestacks --- bei System.Xml.Xsl.Xslt.XsltLoader.LoadStylesheet(XmlReader reader,Boolean include)bei System.Xml.Xsl.Xslt.XsltLoader .Load(XmlReader reader)bei System.Xml.Xsl.Xslt.XsltLoader.Load(Compiler compiler,Object stylesheet,XmlResolver xmlResolver)bei System.Xml.Xsl.Xslt.Compiler.Compile(Object stylesheet,XmlResolver xmlResolver,QilExpression&qil) bei System.Xml.Xsl.XslCompiledTransform.LoadInternal(对象样式表,XsltSettings设置,XmlResolve r stylesheetResolver)bei System.Xml.Xsl.XslCompiledTransform.Load(String stylesheetUri,XsltSettings settings,XmlResolver stylesheetResolver)bei(my namespace and class).GetXslTransform(Boolean preTransform)bei(my namespace and class).get_XslHtmlOutput()bei(my命名空间和类).get_DisplayMarkup()

sta*_*ica 8

简短回答:

您的IDatabaseService接口方法返回XmlReader对象.构造它们时,请确保将a传递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!
}
Run Code Online (Sandbox Code Playgroud)

如果指定此参数,一切都可能正常工作.请参阅本答案的最后一部分,看看为什么我建议这样做.


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

让我们首先简要地思考哪些组件可能导致错误:

"将XSLT文档从本地文件系统移动到数据库后,就开始出现此错误.使用指向本地文件的默认导入方案时,以及从本地文件系统加载XSLT文档时,不会发生错误."

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

  1. 更改了样式表中的导入路径(引入的db://…路径)
  2. 实现并连接了一个XmlDbResolver用于处理db://导入方案的自定义
  3. 实现了数据库访问代码的形式IDatabaseService,其背后XmlDbResolver

如果样式表除导入路径外没有变化,那么错误似乎可能在您的XmlResolver类和/或IDatabaseService实现中.由于您没有显示后者的代码,因此我们无法在没有猜测的情况下调试您的代码.

我已经使用您创建了一个模拟项目XmlDbResolver(完整描述如下).我无法重现错误,因此我怀疑您的IDatabaseService实现会导致错误.

更新:我已经能够重现错误.请参阅OP的评论和本答案的最后一部分.


我尝试重现您的错误:

我在Visual Studio 2010中创建了一个控制台应用程序项目(可以通过使用Git()克隆此Gistgit clone https://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git然后检出第二次提交来检索git checkout d00629).我将在下面更详细地描述每个解决方案的项目.

项目项目

(请注意,复制到输出目录的属性SqlServerDatabase.mdf,TestInput.xml以及两个.xslt工程项目应设置为始终.)


SqlServerDatabase.mdf:

这是一个基于服务的数据库,我将附加到SQL Server Express 2008的本地实例.(这是通过连接字符串来完成的App.config;见下文.)

我在这个数据库中设置了以下项目:

SqlServerDatabase结构

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

ApplicationDocuments列定义

这些表最初是空的.测试数据将被添加到在运行数据库(见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>
Run Code Online (Sandbox Code Playgroud)

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)
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

TestInput.xml:

这是我们将使用上述内容进行转换的XML测试输入文档TestStylesheet.xslt:

<?xml version="1.0" encoding="utf-8" ?>
<Foo/>
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

在我的机器上像魅力一样工作:输出是一个带有空根元素的XML文档<Bar/>,这是db://common.hist.org样式表<Foo/>从测试输入中输出匹配元素的样式.


更新:错误再现和修复:

  1. Main方法中插入以下语句:

    databaseService.StoreApplicationDocument(
        applicationName: "test",
        document:        XmlReader.Create("TestStylesheet.xslt"));
    
    Run Code Online (Sandbox Code Playgroud)
  2. 代替

    xslt.Load(@"TestStylesheet.xslt", …);
    
    Run Code Online (Sandbox Code Playgroud)

    xslt.Load(@"db://test.hist.org", …);
    
    Run Code Online (Sandbox Code Playgroud)

    这会触发OP报告的错误.

经过一些调试后,我发现以下内容不会导致此问题.

  • Document数据库表中的列具有类型的事实XML.它也失败NTEXT了.

  • <?xml … ?>从DB返回的文档中缺少标题的事实.即使在SqlServerDatabaseService将控制返回到框架之前手动添加XML标头,错误仍然存​​在.

实际上,错误是在.NET Framework代码中的某处触发的.这就是我决定下载并安装.NET Framework参考源的原因.(我将解决方案更改为使用框架版本3.5进行调试.)安装此项并重新启动VS然后允许您在调试会话期间查看并逐步执行框架代码.

在呼叫开始xslt.Load(…;)在我们的Main方法中,我走进了框架代码,并最终来到了一个方法LoadStylesheet里面XsltLoader.cs.有一个HybridDictionary被调用的documentUrisInUse,它显然存储已经加载的样式表的基URI.因此,如果我们加载多个带有空URI或缺少基URI的样式表,此方法将尝试null两次添加到该字典; 这就是导致错误的原因.

因此,一旦为您返回的每个样式表分配了唯一的基本URI IDatabaseService,一切都应该可以正常工作.您可以通过传递baseUriXmlReader构造函数来完成此操作.在我的答案的最开始看一个代码示例.您还可以通过下载或克隆此Gist(git clone https://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git)来检索更新的,有效的解决方案.