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

时间:2012-08-08 12:29:32

标签: c# sql-server xml xslt xslcompiledtransform

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

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

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

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

  

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

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

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

我还尝试在创建XslCompiledTransform的实例时启用调试,但不知何故不能进入&#34;进入&#34;基于数据库的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片段,而不是存储在Web服务器上的样式表。

更新:例外的堆栈跟踪:

  

System.Xml.Xsl.XslLoadException:XSLT-Kompilierungsfehler。 Fehler bei(9,1616)。 ---&GT; System.ArgumentException:具有相同键的条目已存在.. bei System.Collections.Specialized.ListDictionary.Add(Object key,Object value)bei System.Collections.Specialized.HybridDictionary.Add(Object key,Object value)bei 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读者)bei System.Xml.Xsl.Xslt.XsltLoader.Load(编译器编译器,对象样式表,XmlResolver xmlResolver)bei System.Xml.Xsl.Xslt.Compiler.Compile(对象样式表,XmlResolver xmlResolver,QilExpression&amp; qil)bei System。 Xml.Xsl.XslCompiledTransform.LoadInternal(对象样式表,XsltSettings设置,XmlResolver 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 namespace)和类).get_DisplayMarkup()

1 个答案:

答案 0 :(得分:8)

简答:

您的IDatabaseService接口方法返回XmlReader个对象。构造它们时,请确保将baseUri传递给构造函数; e.g:

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!
}

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


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

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

  

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

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

  1. 更改了样式表中的导入路径(介绍了db://…路径)
  2. 实施并连接了自定义XmlDbResolver以处理db://导入方案
  3. IDatabaseService的形式实施了数据库访问代码,支持XmlDbResolver
  4. 如果样式表除了导入路径之外没有变化,那么错误可能出现在XmlResolver类和/或IDatabaseService实现中。由于您还没有显示后者的代码,因此我们无法在没有猜测的情况下调试您的代码。

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

      

    更新:我已经能够重现错误。请参阅OP的评论&amp;这个答案的最后一部分。


    我尝试重现您的错误:

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

    Project items

    (请注意,git checkout d00629SqlServerDatabase.mdfTestInput.xml项目项的复制到输出目录属性应设置为始终。)


    SqlServerDatabase.mdf:

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

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

    SqlServerDatabase structure

    此表包含两列,其定义如下:

    ApplicationDocuments column definitions

    这些表最初是空的。测试数据将在运行时添加到数据库中(请参阅下面的App.configProgram.cs)。


    App.config中:

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

    CommonHistOrg.xslt

    IDatabaseService.cs:

    此文件包含<?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> 界面的定义,我在此不再重复。


    SqlServerDatabaseService.cs:

    这包含一个实现IDatabaseService的类。它读/写数据到上面的数据库:

    IDatabaseService

    XmlDbResolver.cs:

    这包含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类相同:

    1. 公共构造函数接受XmlDBResolver对象。这用于代替IDatabaseService

    2. 我必须删除对DatabaseServiceFactory.DatabaseService的电话。


    3. CommonHistOrg.xslt:

      这是Tracing.TraceHelper.WriteLine样式表,它将在运行时放入数据库中(参见下面的db://common.hist.org):

      Program.cs

      TestStylesheet.xml:

      这是一个引用上述<?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> 样式表的样式表:

      db://common.hist.org

      TestInput.xml:

      这是我们将使用上述<?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> 转换的XML测试输入文档:

      TestStylesheet.xslt

      的Program.cs:

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

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

      在我的机器上像魅力一样工作:输出是一个带有空根元素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元素的内容测试输入。


      更新:错误再现&amp;修正:

      1. <Foo/>方法中插入以下语句:

        Main
      2. 而不是

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

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

        这会触发OP报告的错误。

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

        • 数据库表中的xslt.Load(@"db://test.hist.org", …); 列的类型为Document。它也失败了XML

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

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

        从我们的SqlServerDatabaseService方法调用xslt.Load(…;)开始,我进入框架代码,最终找到Main内的方法LoadStylesheet。有XsltLoader.cs名为HybridDictionary,显然存储已加载样式表的基本URI。因此,如果我们加载多个带有空URI或缺少基URI的样式表,此方法将尝试将documentUrisInUse添加到该字典两次;这就是导致错误的原因。

        因此,一旦为null返回的每个样式表分配了唯一的基本URI,一切都应该可以正常工作。您可以通过将IDatabaseService传递给baseUri构造函数来完成此操作。在我的答案的最开始看一个代码示例。您还可以downloading检索更新的有效解决方案,或克隆this GistXmlReader)。