tags:

views:

244

answers:

2

I need to open an XML file exclusively, make a modification, and save it.

I can open it and make the modification pretty easily like so:

DataSet ds = new DataSet();
ds.ReadXml(filename);

DataTable table = ds.Tables[0];
DataRow[] rows = table.Select("Inventory== 1");
DataRow row = rows[0];
row["Inventory"] = "2";
ds.WriteXml(filename);

That all works great, but it doesn't lock the file. I absolutely need the file locked.

So I tried it with a stream:

FileStream stream = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.None);

DataSet ds = new DataSet();
ds.ReadXml(stream);

DataTable table = ds.Tables[0];
DataRow[] rows = table.Select("Inventory== 1");
DataRow row = rows[0];
row["Inventory"] = "2";
ds.WriteXml(stream);
stream.Close();

That opens the file exclusively, but when it saves, it appends the XML to the end of the time, it doesn't overwrite it, so I end up with something like:

<Widgets>
  <Widget Code="5" Number="10" Inventory="1" />
  <Widget Code="6" Number="11" Inventory="15" />
  <Widget Code="7" Number="12" Inventory="22" />
</Widgets>
<Widgets>
   <Widget Code="5" Number="10" Inventory="2" />
  <Widget Code="6" Number="11" Inventory="15" />
  <Widget Code="7" Number="12" Inventory="22" />
</Widgets>

What I want is:

<Widgets>
  <Widget Code="5" Number="10" Inventory="2" />
  <Widget Code="6" Number="11" Inventory="15" />
  <Widget Code="7" Number="12" Inventory="22" />
</Widgets>

I know I could open the file and use the File methods to parse it line by line and make my change... but I was hoping for something more elegant. The first method - loading the file using ReadXml - does the file modification just fine, but it doesn't appear to have any options for opening the file exclusively. Am I missing something?

+6  A: 

You can do it with a stream, but you need to reset your stream before writing:

using(FileStream stream = File.Open(filename, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
{
    DataSet ds = new DataSet();
    ds.ReadXml(stream);

    DataTable table = ds.Tables[0];
    DataRow[] rows = table.Select("Inventory== 1");
    DataRow row = rows[0];
    row["Inventory"] = "2";

    // Reset the stream here to the beginning of the file!
    stream.Seek(0, SeekOrigin.Begin);
    ds.WriteXml(stream);
    // Reset the length of the stream prior to closing it, in case it's shorter than it used to be...
    stream.SetLength(stream.Position);
}
Reed Copsey
What happens if the new xml is shorter than the old xml here?
Walt W
Wont seeking to the beginning of the file just make sure that the data is written from the start of the file. i.e. it will overwrite the previous data assuming the new data is more or equal to the previous data?
Ganesh R.
@Walt W: +1. I forgot to put in the SetLength(...) call. It will work now, after my edit. You need to set the length to 0 prior to writing, or set the length to the position after writing.
Reed Copsey
+5  A: 

In your stream, you need to reset the stream's head to the beginning of the file AND erase existing content to overwrite existing data in case your replacement text is shorter than the original content. Try placing this before ds.writeXml:

stream.Seek(0, SeekOrigin.Begin);
stream.SetLength(0);

The first line moves the head, the second truncates the file so if what you are replacing the text with is shorter than the original text, you will not have extraneous characters at the end.

Note that, as Reed Copsey points out, it might be better to do the write first, then set the length to the stream's Position member, as this will have no effect in the event that the replacing text is the same length or longer than the text replaced, which might be marginally more efficient.

Walt W
Great! I knew it had to be something simple that I was missing!
BDW
@Walt W: You can also call setlength AFTER writing the new data, which is potentially more efficient (since it won't have an effect if it's longer, and if it's shorter, you only reset hte length once, instead of as you write...)
Reed Copsey
@Reed Copsey: This is true.
Walt W