【问题标题】:How to deserialize only part of a large xml file to c# classes?如何仅将大型 xml 文件的一部分反序列化为 c# 类?
【发布时间】:2013-01-26 20:16:08
【问题描述】:

我已经阅读了一些关于如何反序列化 xml 的帖子和文章,但仍然没有弄清楚我应该编写代码以满足我的需求的方式,所以.. 我为另一个关于反序列化 xml 的问题道歉) )

我有一个需要反序列化的大 (50 MB) xml 文件。我使用 xsd.exe 获取文档的 xsd 架构,然后自动生成我放入项目中的 c# 类文件。我想从这个 xml 文件中获取一些(不是全部)数据并将其放入我的 sql 数据库中。

这里是文件的层次结构(简化了,xsd很大):

public class yml_catalog 
{
    public yml_catalogShop[] shop { /*realization*/ }
}

public class yml_catalogShop
{
    public yml_catalogShopOffersOffer[][] offers { /*realization*/ }
}

public class yml_catalogShopOffersOffer
{
    // here goes all the data (properties) I want to obtain ))
}

这是我的代码:

第一种方法:

yml_catalogShopOffersOffer catalog;
var serializer = new XmlSerializer(typeof(yml_catalogShopOffersOffer));
var reader = new StreamReader(@"C:\div_kid.xml");
catalog = (yml_catalogShopOffersOffer) serializer.Deserialize(reader);//exception occures
reader.Close();

我收到 InvalidOperationException:XML(3,2) 文档中存在错误

第二种方法:

XmlSerializer ser = new XmlSerializer(typeof(yml_catalogShopOffersOffer));
yml_catalogShopOffersOffer result;
using (XmlReader reader = XmlReader.Create(@"C:\div_kid.xml"))          
{
    result = (yml_catalogShopOffersOffer)ser.Deserialize(reader); // exception occures
}

InvalidOperationException:XML(0,0) 文档中存在错误

第三个:我试图反序列化整个文件:

 XmlSerializer ser = new XmlSerializer(typeof(yml_catalog)); // exception occures
 yml_catalog result;
 using (XmlReader reader = XmlReader.Create(@"C:\div_kid.xml"))          
 {
     result = (yml_catalog)ser.Deserialize(reader);
 }

我得到以下信息:

error CS0030: The convertion of type "yml_catalogShopOffersOffer[]" into "yml_catalogShopOffersOffer" is not possible.

error CS0029: The implicit convertion of type "yml_catalogShopOffersOffer" into "yml_catalogShopOffersOffer[]" is not possible.

那么,如何修复(或覆盖)代码以免出现异常?

编辑:当我写的时候:

XDocument doc = XDocument.Parse(@"C:\div_kid.xml");

发生 XmlException:根级别的未经许可的数据,字符串 1,位置 1。

这里是xml文件的第一个字符串:

<?xml version="1.0" encoding="windows-1251"?>

编辑 2: xml文件简短示例:

<?xml version="1.0" encoding="windows-1251"?>
<!DOCTYPE yml_catalog SYSTEM "shops.dtd">
<yml_catalog date="2012-11-01 23:29">
<shop>
   <name>OZON.ru</name>
   <company>?????? "???????????????? ??????????????"</company>
   <url>http://www.ozon.ru/</url>
   <currencies>
     <currency id="RUR" rate="1" />
   </currencies>
   <categories>
      <category id=""1126233>base category</category>
      <category id="1127479" parentId="1126233">bla bla bla</category>
      // here goes all the categories
   </categories>
   <offers>
      <offer>
         <price></price>
         <picture></picture>
      </offer>
      // other offers
   </offers>
</shop>
</yml_catalog>

附言 我已经接受了答案(很完美)。但现在我需要使用 categoryId 为每个 Offer 找到“基本类别”。数据是分层的,基本类别是没有“parentId”属性的类别。所以,我写了一个递归方法来找到“基本类别”,但它永远不会完成。似乎算法不是很快))
这是我的代码:(在 main() 方法中)

var doc = XDocument.Load(@"C:\div_kid.xml");
var offers = doc.Descendants("shop").Elements("offers").Elements("offer");
foreach (var offer in offers.Take(2))
        {
            var category = GetCategory(categoryId, doc);
            // here goes other code
        }

辅助方法:

public static string GetCategory(int categoryId, XDocument document)
    {
        var tempId = categoryId;
            var categories = document.Descendants("shop").Elements("categories").Elements("category");
            foreach (var category in categories)
            {
                if (category.Attribute("id").ToString() == categoryId.ToString())
                {
                    if (category.Attributes().Count() == 1)
                    {
                        return category.ToString();
                    }
                    tempId = Convert.ToInt32(category.Attribute("parentId"));
                }
            }
        return GetCategory(tempId, document);
    }

我可以在这种情况下使用递归吗?如果没有,我还能如何找到“基本类别”?

【问题讨论】:

  • 您能否通过向我们展示一些示例数据以及您希望您的对象如何从中获取对象来提供一个 XML 模式的小样本? (ps,你需要Load()一个文件,而不是Parse()它)
  • @JeffMercado,我更新了问题

标签: c# xml deserialization xml-deserialization


【解决方案1】:

试试 LINQ to XML。 XElement result = XElement.Load(@"C:\div_kid.xml");

LINQ 中的查询非常棒,但在开始时确实有点奇怪。您可以使用类似 SQL 的语法或使用 lambda 表达式从 Document 中选择节点。然后创建包含您感兴趣的数据的匿名对象(或使用现有类)。

最好是亲眼目睹。

根据您的示例 XML 和代码,这里有一个具体示例:

var element = XElement.Load(@"C:\div_kid.xml");
var shopsQuery =
    from shop in element.Descendants("shop")
    select new
    {
        Name = (string) shop.Descendants("name").FirstOrDefault(),
        Company = (string) shop.Descendants("company").FirstOrDefault(),
        Categories = 
            from category in shop.Descendants("category")
            select new {
                Id = category.Attribute("id").Value,
                Parent = category.Attribute("parentId").Value,
                Name = category.Value
            },
        Offers =
            from offer in shop.Descendants("offer")
            select new { 
                Price = (string) offer.Descendants("price").FirstOrDefault(),
                Picture = (string) offer.Descendants("picture").FirstOrDefault()
            }

    };

foreach (var shop in shopsQuery){
    Console.WriteLine(shop.Name);
    Console.WriteLine(shop.Company);
    foreach (var category in shop.Categories)
    {
        Console.WriteLine(category.Name);
        Console.WriteLine(category.Id);
    }
    foreach (var offer in shop.Offers)
    {
        Console.WriteLine(offer.Price);
        Console.WriteLine(offer.Picture);
    }
}  

另外:以下是从平面 category 元素反序列化类别树的方法。 你需要一个合适的类来容纳它们,因为 Children 列表必须有一个类型:

class Category
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public List<Category> Children { get; set; }
    public IEnumerable<Category> Descendants {
        get
        {
            return (from child in Children
                    select child.Descendants).SelectMany(x => x).
                    Concat(new Category[] { this });
        }
    }
}

创建一个包含文档中所有不同类别的列表:

var categories = (from category in element.Descendants("category")
                    orderby int.Parse( category.Attribute("id").Value )
                    select new Category()
                    {
                        Id = int.Parse(category.Attribute("id").Value),
                        ParentId = category.Attribute("parentId") == null ?
                            null as int? : int.Parse(category.Attribute("parentId").Value),
                        Children = new List<Category>()
                    }).Distinct().ToList();

然后将它们组织成一棵树(大量借用flat list to hierarchy):

var lookup = categories.ToLookup(cat => cat.ParentId);
foreach (var category in categories)
{
    category.Children = lookup[category.Id].ToList();
}
var rootCategories = lookup[null].ToList();

查找包含theCategory的根:

var root = (from cat in rootCategories
            where cat.Descendants.Contains(theCategory)
            select cat).FirstOrDefault();

【讨论】:

  • 加载成功! Console.WriteLine(result.Name);显示“yml_catalog”。但是我怎样才能获得下一个级别的数据?可以提供一些代码吗?
  • 我添加了一些示例的链接。但最简单的答案是它们可以通过 result.Descendants("elementname")
  • 我还建议查看 Linq to Xml C# 代码示例 - 有所有信息)
  • 我根据您提供的 XML 文档添加了一个具体示例。
  • 感谢额外的代码,它工作得很好!您可以查看我在问题帖子末尾所做的编辑吗? (我还需要使用“categoryId”找到“基本类别”,并且我使用永远不会完成执行的递归)。提前致谢!
猜你喜欢
  • 1970-01-01
  • 2010-09-27
  • 1970-01-01
  • 1970-01-01
  • 2011-01-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多