XslCompiledTransform和自定义XmlUrlResolver:“具有相同键的条目已存在”
有没有办法调试由自定义XmlUrlResolver从数据库加载的XSLT文档,或者有人知道,下面的错误消息是什么?
我有一个XSLT样式表导入一个常见的xslt文档:
Scheme由一个自定义的XmlResolver
处理,它从数据库加载XSLT文档,但是我收到一个错误:
已存在具有相同密钥的条目。
xsl:import
引用的公共XSLT文档包含一些常见的XSLT模板,每个模板都有一个唯一的名称。
将XSLT文档从本地文件系统移动到数据库后,此错误开始发生。 使用指向本地文件的默认导入方案时以及从本地文件系统加载XSLT文档时,不会发生错误。
我还尝试在创建XslCompiledTransform
的实例时打开调试,但不知何故不可能“进入”基于数据库的XSLT。
_xslHtmlOutput = new XslCompiledTransform(XSLT_DEBUG);
更新:以下基本上是请求的解析器代码,但我的代码中没有发生exception; 因此,我想在下面的代码中没有明显的原因。 (这个相同的代码实际上用于加载包含导入的XSLT样式表,当注释掉导入时,一切都按预期工作。)
public class XmlDBResolver : XmlUrlResolver { private IDictionary GetUriComponents(String uri) { bool useXmlPre = false; uri = uri.Replace("db://", ""); useXmlPre = uri.StartsWith("xml/"); uri = uri.Replace("xml/", ""); IDictionary dict = new Dictionary(); 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://.hist.org * db://..hist.org * db://...hist.org * * */ Tracing.TraceHelper.WriteLine(String.Format("GetEntity {0}", absoluteUri)); XmlReader reader = null; switch (absoluteUri.Scheme) { case "db": string origString = absoluteUri.OriginalString; IDictionary 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服务器上的样式表。
更新:exception的堆栈跟踪:
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()
简短回答:
您的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! }
如果指定此参数,一切都可能正常工作。 请参阅本答案的最后一部分,看看为什么我建议这样做。
答案很长,简介:可能的错误来源:
让我们首先简要地思考哪些组件可能导致错误:
“将XSLT文档从本地文件系统移动到数据库后,就开始出现此错误。使用指向本地文件的默认导入方案时,以及从本地文件系统加载XSLT文档时,不会发生错误。”
将样式表放在数据库中意味着你必须拥有......
- 更改了样式表中的导入路径(介绍了
db://…
paths) - 实现并连接自定义
XmlDbResolver
以处理db://
import方案 - 以
XmlDbResolver
的forms实现数据库访问代码,后者支持XmlDbResolver
如果样式表除了导入路径之外没有变化,那么错误可能在XmlResolver
类和/或IDatabaseService
实现中。 由于您没有显示后者的代码,因此我们无法在没有猜测的情况下调试您的代码。
我使用您的XmlDbResolver
创建了一个模拟项目(完整描述如下)。 我无法重现错误,因此我怀疑您的 IDatabaseService
实现会导致错误。
更新:我已经能够重现错误。 请参阅OP的评论和本答案的最后一部分。
我尝试重现您的错误:
我在Visual Studio 2010中创建了一个控制台应用程序项目(您可以通过使用Git克隆此Gist来检索( git clone https://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git
),然后检查第二次提交, git checkout d00629
)。 我将在下面更详细地描述每个解决方案的项目。
(请注意, SqlServerDatabase.mdf
, TestInput.xml
和.xslt
项目项的“ 复制到输出目录”属性应设置为“ 始终” 。)
SqlServerDatabase.mdf:
这是一个基于服务的数据库,我将附加到SQL Server Express 2008的本地实例。(这是通过App.config
的连接字符串完成的;请参阅下文。)
我在这个数据库中设置了以下项目:
该表包含两列,定义如下:
这些表最初是空的。 测试数据将在运行时添加到数据库中(请参阅下面的Program.cs
和CommonHistOrg.xslt
)。
App.config中:
此文件包含上述数据库的连接字符串条目。
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
类相同:
-
公共构造函数接受
IDatabaseService
对象。 这用于代替DatabaseServiceFactory.DatabaseService
。 -
我不得不删除对
Tracing.TraceHelper.WriteLine
的调用。
CommonHistOrg.xslt:
这是db://common.hist.org
样式表,它将在运行时放入数据库中(参见下面的Program.cs
):
TestStylesheet.xml:
这是一个参考上面的db://common.hist.org
样式表的样式表:
TestInput.xml:
这是我们将使用上面的TestStylesheet.xslt
转换的XML测试输入文档:
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文档,这是db://common.hist.org
样式表从测试中输出匹配的
元素的内容输入。
更新:错误再现和修复:
-
在
Main
方法中插入以下语句:databaseService.StoreApplicationDocument( applicationName: "test", document: XmlReader.Create("TestStylesheet.xslt"));
-
代替
xslt.Load(@"TestStylesheet.xslt", …);
做
xslt.Load(@"db://test.hist.org", …);
这会触发OP报告的错误。
经过一些调试后,我发现以下内容不会导致此问题。
-
数据库表中的
Document
列具有XML
类型的事实。 它也失败了NTEXT
。 -
事实上,从DB返回的文档中缺少
标头。 即使在
SqlServerDatabaseService
将控制权返回给框架之前手动添加XML标头,错误仍然存在。
实际上,错误是在.NET Framework代码中的某处触发的。 这就是我决定下载并安装.NET Framework参考源的原因 。 (我将解决方案更改为使用框架版本3.5进行调试。)安装此项并重新启动VS然后允许您在调试会话期间查看并逐步执行框架代码。
从我们的Main
方法中调用xslt.Load(…;)
,我进入框架代码并最终找到了LoadStylesheet
的方法XsltLoader.cs
。 有一个名为documentUrisInUse
的HybridDictionary
,它显然存储已经加载的样式表的基URI。 因此,如果我们加载多个带有空URI或缺少基URI的样式表,则此方法将尝试将该字典添加两次null
; 这就是导致错误的原因。
因此,一旦为IDatabaseService返回的每个样式表分配了唯一的基本URI,一切都应该可以正常工作。 您可以通过将baseUri
传递给XmlReader
构造函数来完成此操作。 在我的答案的最开始看一个代码示例。 您还可以通过下载或克隆此Gist来检索更新的,有效的解决方案( git clone https://gist.github.com/fbbd5e7319bd6c281c50b4ebb1cee1f9.git
)。