数据集级联和内存消耗

7
我有一个应用程序,使用DataSet.WriteXML导出数据和DataSet.ReadXML导入数据。在导入过程中,根据应用程序逻辑需要更改某些主键。
当记录数超过500K时,成功写入和读取XML。一旦更改了主键,它会等待一段时间并抛出OutOfMemory异常。我认为原因是必须进行大量的级联更新。我尝试在更改主键期间使用BeginEdit和EndEdit,但在这种情况下仍然失败于EndEdit。
据我所知,DataSets还将一些以前的数据保留在内存中。是否有办法优化DataSet更新操作,以使其消耗最少的内存?

这可能需要更多的工作,但您是否考虑过将所有数据编写到临时数据库表中,并在临时数据库级别上进行重新编号?500k是很多行需要保存在内存中。您的数据集中有多少个不同的表? - tgolisch
@tgolisch:我有大约70个不同的表。是否可以创建一个临时数据库?我不知道临时数据库。你能给我指一个例子吗?谢谢。 - ABCD
我认为你的应用程序存在一些设计问题。我的意思是“500K条记录,…”)) 也许你可以发布更多信息 - 代码 - 来描述你的问题? - MikroDel
显然,这种方式不适用于大型记录,并且存在设计问题。我正在尝试找到一种增强方法,在更新 DataSet 中的任何主键时,以减少级联更新中的内存消耗。 - ABCD
4个回答

1
如果您需要更多的控制权,那么您需要删除数据集提供的某些功能。一种减少级联引起的内存的方法是简单地不进行级联操作,使用表模式手动更新表ID。
这样,您就可以控制要更新哪些行,在任何时候接受更改,强制在更新过程中进行垃圾回收或任何其他您想要控制的事情。
我创建了一个简单的测试场景,展示了我的意思。

enter image description here

模式:

<?xml version="1.0"?>
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
  <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:UseCurrentLocale="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
        <xs:element name="Planet">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Continent">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="PlanetID" type="xs:int" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Country">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="ContinentID" type="xs:int" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="County">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="CountryID" type="xs:int" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="City">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="CountyID" type="xs:int" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Street">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="CityID" type="xs:int" minOccurs="0" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="People">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="StreetID" type="xs:int" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Job">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="PeopleID" type="xs:int" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Pets">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="ID" type="xs:int" />
              <xs:element name="PeopleID" type="xs:int" minOccurs="0" />
              <xs:element name="Name" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:choice>
    </xs:complexType>
    <xs:unique name="Constraint1">
      <xs:selector xpath=".//Planet" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="Continent_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//Continent" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="Country_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//Country" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="County_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//County" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="City_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//City" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="Street_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//Street" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="People_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//People" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="Job_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//Job" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:unique name="Pets_Constraint1" msdata:ConstraintName="Constraint1">
      <xs:selector xpath=".//Pets" />
      <xs:field xpath="ID" />
    </xs:unique>
    <xs:keyref name="Relation8" refer="People_Constraint1">
      <xs:selector xpath=".//Pets" />
      <xs:field xpath="PeopleID" />
    </xs:keyref>
    <xs:keyref name="Relation7" refer="People_Constraint1">
      <xs:selector xpath=".//Job" />
      <xs:field xpath="PeopleID" />
    </xs:keyref>
    <xs:keyref name="Relation6" refer="Street_Constraint1">
      <xs:selector xpath=".//People" />
      <xs:field xpath="StreetID" />
    </xs:keyref>
    <xs:keyref name="Relation5" refer="City_Constraint1">
      <xs:selector xpath=".//Street" />
      <xs:field xpath="CityID" />
    </xs:keyref>
    <xs:keyref name="Relation4" refer="County_Constraint1">
      <xs:selector xpath=".//City" />
      <xs:field xpath="CountyID" />
    </xs:keyref>
    <xs:keyref name="Relation3" refer="Country_Constraint1">
      <xs:selector xpath=".//County" />
      <xs:field xpath="CountryID" />
    </xs:keyref>
    <xs:keyref name="Relation2" refer="Continent_Constraint1">
      <xs:selector xpath=".//Country" />
      <xs:field xpath="ContinentID" />
    </xs:keyref>
    <xs:keyref name="Relation1" refer="Constraint1">
      <xs:selector xpath=".//Continent" />
      <xs:field xpath="PlanetID" />
    </xs:keyref>
  </xs:element>
</xs:schema>

并且一些生成测试用例的代码

    private void CreateRows(Int32 MaxBaseRows, Int32 MaxChildRows)
    {
        dataSet1.Clear();
        Int32 RowCount = 0;
        Random R = new Random();
        foreach (DataTable DT in dataSet1.Tables)
        {
            Int32 NewCount = R.Next(1, MaxBaseRows);
            foreach (var FK in DT.Constraints.OfType<ForeignKeyConstraint>())
            {
                NewCount = NewCount * R.Next(1, MaxChildRows);
            }
            for (int i = 0; i < NewCount; i++)
            {
                DataRow DR = DT.NewRow();
                foreach (DataColumn DC in DT.Columns)
                {
                    if (DC.ColumnName == "ID")
                    {
                        DR[DC] = DT.Rows.Count;
                    }
                    else if (DC.DataType == typeof(Int32))
                    {
                        Boolean ValueSet = false;
                        foreach (var FK in DT.Constraints.OfType<ForeignKeyConstraint>())
                        {
                            if (FK.Columns.Contains(DC))
                            {
                                DR[DC] = R.Next(0, FK.RelatedTable.Rows.Count);
                                ValueSet = true;
                            }
                        }
                        if (!ValueSet)
                        {
                            DR[DC] = R.Next(0, 10000);
                        }
                    }
                    else if (DC.DataType == typeof(String))
                    {
                        DR[DC] = String.Format("{0}{1}", DT.TableName, DT.Rows.Count);
                    }
                }
                DT.Rows.Add(DR);
                RowCount++;
            }
        }
        label19.Text = RowCount.ToString();
        dataSet1.AcceptChanges();
    }


    private void UpdateUsingCascade()
    {
        EnableRelations();
        GC.Collect();
        long Mem = System.GC.GetTotalMemory(false);
        if (dataSet1.Tables["Planet"].Rows.Count > 0)
        {
            dataSet1.Tables["Planet"].Rows[0]["ID"] = new Random().Next(BaseRowCount, BaseRowCount + 10);
        }
        Mem = System.GC.GetTotalMemory(false) - Mem;
        DataSet ds = dataSet1.GetChanges();
        Int32 Changes = ds.Tables.OfType<DataTable>().Sum(DT => DT.Rows.Count);
        label19.Text = Changes.ToString();
        label21.Text = Mem.ToString();
        dataSet1.AcceptChanges();
    }

    private void UpdateManually()
    {
        DisableRelations();
        GC.Collect();
        long Mem = System.GC.GetTotalMemory(false);

        DataTable DT = dataSet1.Tables["Planet"];
        Int32 ChangeCount = 0;
        if (DT.Rows.Count > 0)
        {
            DataColumn DC = DT.Columns["ID"];
            Int32 oldValue = Convert.ToInt32(DT.Rows[0][DC]);
            DT.Rows[0][DC] = new Random().Next(BaseRowCount + 20,BaseRowCount + 30);
            Int32 newValue = Convert.ToInt32(DT.Rows[0][DC]);
            foreach (DataRelation Relation in DT.ChildRelations)
            {
                if (Relation.ParentColumns.Contains(DC))
                {
                    foreach (DataColumn CC in Relation.ChildColumns)
                    {
                        foreach (DataRow DR in Relation.ChildTable.Rows)
                        {
                            if (Convert.ToInt32(DR[CC]) == oldValue)
                            {
                                DR[CC] = newValue;
                                ChangeCount++;
                                dataSet1.AcceptChanges();
                                GC.Collect();
                            }
                        }
                    }
                }
            }
        }
        Mem = System.GC.GetTotalMemory(false) - Mem;
        label20.Text = ChangeCount.ToString();
        label22.Text = Mem.ToString();
        dataSet1.AcceptChanges();
    }

    private void EnableRelations()
    {
        dataSet1.EnforceConstraints = true;
        foreach (DataRelation Relation in dataSet1.Relations)
        {
            Relation.ChildKeyConstraint.UpdateRule = Rule.Cascade;
        }
    }

    private void DisableRelations()
    {
        dataSet1.EnforceConstraints = false;
        foreach (DataRelation Relation in dataSet1.Relations)
        {
            Relation.ChildKeyConstraint.UpdateRule = Rule.None;
        }
    }

非常好,实际上我已经通过将EnforceConstraints设置为false来解决了这个问题。然后手动更新其他记录。与您提出的方案非常相似。感谢您的回答! - ABCD

0

DataSets是智能的生物。它们不仅可以读取/写入/保存/过滤数据,还可以进行更改跟踪,因此后续的更新/写入/删除操作会更快(当与数据库一起使用时,而不仅仅是XML文件)。

可能发生的情况是,您的DataSet已经开启了更改跟踪,这将强制它始终记住当前数据以及数据之前的样子以及新数据与旧数据的关系。如果您只将DataSet保留为当前工作负载的“容器”,则不需要缓存/更改跟踪-只需关闭它。我的意思是,如果可能的话-我目前不记得是否以及如何完成。但是,我非常确定您可以通过调用.AcceptChanges()或通过处理旧DS并为每个要加载的新数据批次创建新DS来清除更改。后者当然无法帮助在当前批次的顺序更新期间抛出OOM时。如果在第一个PK更新点处抛出OOM,则AcceptChanges无法帮助。您只能在一个完整操作结束后“接受”更改,即使如此,在您发出它之前也不会有“同时”。但是,如果在几个更改PK之后抛出OOM,则在每个更改后或每隔几个更改后调用AcceptChanges可能会有所帮助。

请注意,我只是猜测。您的DS未连接到DB,因此更改跟踪可能默认关闭。但我怀疑这一点,我记得即使对于XML文件,您也可以要求DS将数据与更改日志一起转储.. 我认为它默认开启。

感谢您的帖子。我实际上已经知道这个问题。我的问题是当我更新主键时,会出现OutOfMemory异常,这会导致一些级联更新。我正在寻找的是在级联更新过程中减少内存消耗的方法。 - ABCD

0

SHCJ - 你应该使用BufferedStream

DataSet dataSet = new DataSet();
FileStream fileStream = File.OpenRead(pathToYourFile);
BufferedStream bufferedStream = new BufferedStream(fileStream);
dataSet.ReadXml(bufferedStream);

更新

请尝试使用此操作进行写入:

using (XmlWriter xmlWriter = XmlWriter.Create(_pathToYourFile))
{
   /* write oprations */
}

BufferedStream消耗了大量的内存块,并且在ReadXml过程中抛出了OutOfMemory异常。 - ABCD
这是在我读取XML后更新它时发生的。当然,我已经在写和读两个方面都使用了'using'。 - ABCD
@SHCJ - 我认为你的应用程序存在一些设计问题。我的意思是“500K条记录,...”)) 也许你可以发布更多信息 - 代码 - 来描述你的问题? - MikroDel
显然,这种方式不适用于大型记录,并且存在设计问题。我正在尝试找到一种增强方法,在更新数据集中的任何主键时应用,以减少级联更新中的内存消耗。 - ABCD

0

试试这个:

try
{

    //Logic to load your file

    var xelmOriginal = new XElement("Root");

    for (int i = 0; i < 500000; i++)
    {
        var item = new XElement("Item");
        item.SetAttributeValue("id", i);
        xelmOriginal.Add(item);
    }

    // Logic to transform each element

    var xelmRootTransformed = new XElement("Root");

    foreach (var element in xelmOriginal.Elements())
    {
        var transformedItem =
            new XElement("Transformed",
                            element.
                                Attributes()
                                .Single(x => x.Name.LocalName.Equals("id")));


        xelmRootTransformed.Add(transformedItem);
    }

    //Logic to save your transformed file
}catch(Exception e)
{

    Console.WriteLine("Failed");
    return;
}

Console.WriteLine("Success");

这里的关键是分离输入和输出。也就是说,你不会立即转换文件并直接写入它;否则你会弄乱你的枚举。
相反,一次读取一个元素,并逐个将其写入临时输出;理论上,你将只有一个元素处于活动状态。

我不确定您是否真正理解我的问题。在WriteXml和ReadXml方面,我没有任何问题。基本上,我的问题是在DataSet的级联更新过程中出现了内存溢出异常。我想要的是在更新主键时减少内存消耗的方法(根据业务逻辑,我必须更改主键,这是不可避免的)。 - ABCD

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接