2

I intend to load an xml file using XDocument.Load(), update values of some elements, then save it using XDocument.Save(). Reading works fine, saving just won't work.

My code:

    class XmlDocHandler
{
    private string filePath;
    private XDocument xmlDoc;
    private IList<XElement> updatedElements;

    public IEnumerable<XElement> Elements => xmlDoc.Descendants();
    public IEnumerable<XElement> UpdatedElements => updatedElements;

    public XmlDocHandler(string filePath)
    {
        this.filePath = filePath;
        ReloadFromFile();
        updatedElements = new List<XElement>();
    }

    public void UpdateElements(IEnumerable<XElement> newElements)
    {
        updatedElements = new List<XElement>();
        foreach (XElement newElement in newElements)
        {
            XElement element = xmlDoc.Descendants()
                                     .FirstOrDefault(x => x.Name.LocalName == newElement.Name.LocalName);
            if (element != null)
            {
                if (element.Value != newElement.Value)
                {
                    element.Value = newElement.Value;
                    updatedElements.Add(element);
                }
            }
        }
    }

    public void ReloadFromFile()
    {
        bool success = false;

        if (File.Exists(filePath))
        {
            try
            {
                xmlDoc = XDocument.Load(filePath);

                success = true;
            }
            catch
            {

            }
        }

        if (!success)
        {
            xmlDoc = new XDocument();
        }
    }

    public void WriteToFile()
    {
        xmlDoc.Save(filePath);
    }
}

As far as I can tell, its a serialized set of operations, nothing parallel or other fancy stuff, that could block my file. I've found no indication that XDocument.Load("c:\file.xml") would create a lock on the file.

I've tried to replace the straight forward operations

xmlDoc = XDocument.Load(filePath);

and

xmlDoc.Save(filePath);

with the stream based approaches found here: XDocument.Save() unable to access file and here c# xml.Load() locking file on disk causing errors so that they look like this:

Loading..

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
    xmlDoc = XDocument.Load(fs);
}

or

using (var sr = new StreamReader(filePath))
{
    xmlDoc = XDocument.Load(sr);
}

and writing..

using (var fs = new FileStream(filePath, FileMode.Open, FileAccess.Write, FileShare.Write))
{
    xmlDoc.Save(fs);
}

No matter what I do, closing the streams properly and making sure the file isn't opened in any editor or otherwise used, the stupid file is always "used by another process".

What exactly am I not seeing here? The file in question resides in my Debug output folder of VS2017 Pro next to the .exe file. I'm not aware that I have limited write access in that folder.

noobie
  • 41
  • 6
  • where is the file. Is it in the root folder? Try to load and save the file to a different folder like `C:\Temp` – Jaques Oct 24 '18 at 13:38
  • I just tried writing the file to a new file (same location). This worked, after I set the FileMode flag to .OpenOrCreate. I mean I could somehow work around this replacing the new seperate file with the original one, but is this how it's supposed to work? – noobie Oct 24 '18 at 14:09
  • What drives me crazy: the way I see this, I'm loading the xml structured file contents into memory by creating an XDocument object, thereby releasing any ties to the file on disk. Then I perform some data manipulation in-memory on the object and lastly writing the new xml contents back to the file. These three operations are completely seperate from eachother and in sequence. In between these the file is left untouched, or not? – noobie Oct 24 '18 at 14:20
  • Now this is highly uncanny! I've replaced `XDocument.Load()` with `XDocument.Parse(File.ReadAllText(filePath));` and `xmlDoc.Save()` with `File.WriteAllText(filePath, xmlDoc.ToString());` and I still get the same error on saving!! I mean what the heck is going on??? I know that the read and write operations on `File` (static methods) close the stream instantly after returning or even before returning! Can someone explain this? – noobie Oct 24 '18 at 15:04
  • 1
    Try using one of [these techniques](https://superuser.com/questions/117902/find-out-which-process-is-locking-a-file-or-folder-in-windows) to see if you can identify what process is locking the file, from your code & test I cant see it being yours. – Alex K. Oct 24 '18 at 15:41
  • @AlexK., it seems to be the app I'm developing itself, that's locking the file. Once I close the app, the handle disappears. Also, the file can then be deleted. I've read [here](https://learn.microsoft.com/en-us/dotnet/api/system.xml.linq.xdocument.load?view=netframework-4.7.2#System_Xml_Linq_XDocument_Load_System_String_) that `XDocument.Load(string path)` uses `XmlReader` class. Maybe I'll find something there. I've scattered some `Thread.Sleep(1000)` after loading and before saving, but that seems to only halt the thread that already locks the file. – noobie Oct 25 '18 at 08:00
  • A collegue at work figured it might be the debugger locking the file. After rebooting and running the app directly (not through VS) I can also rule this out. While researching I came across an [article](https://stackoverflow.com/questions/2115875/openfiledialog-locks-the-folder) where `OpenFileDialog` locks an entire folder. I'll investigate on that too since I'm using OFD. – noobie Oct 25 '18 at 08:23
  • I know you solved to problem, just another thing. The catch - all in your ReloadFromFile method can be dangerous. It hides all kind of errors! – slfan Oct 25 '18 at 20:51
  • @slfan, I totally agree. That was some old ugly code I wrote back then. I've been refactoring my code keeping these things in mind. Kinda nice to see what you've learned :-) – noobie Nov 15 '18 at 15:57

2 Answers2

2

I found the error causing this fiasco!

The reason for this issue has nothing to do with XDocument.Load() or .Save(). While being relieved to have solved my problem, I'm also embarrassed to admit that one of my own implementaions I've made years ago caused this.

I've once made a wrapper class for System.Windows.Forms.OpenFileDialog() that would ease my frequently used configuration of the OFD class. In it I've used something like

var ofd = new OpenFileDialog();
try
{
    if((var stream = ofd.OpenFile()) != null)
    {
        return ofd.FileName;
    }
    else
    {
        return "file already opened";
    }
}
catch
{
    return "error";
}

while keeping the ofd and the stream references as instance variables. Looking at this code now makes me shiver (or laugh) but back then I didn't know any better. Of course this has now bitten me in my a.., because I did not close that stream! A crime I've committed years ago now cost me almost 2 days of work.

My remedy: I've rewritten my OFD wrapper class to use the Microsoft.Win32.OpenFileDialog class, because that does not seem to have any stream based stuff that would lock files unintentionally.

Lesson(s) learned (and what I warmly recommend to others): never trust yourself when using streams or other constructs that can be left open and cause memory leaks. Read up on the using block statement that makes use of the dispose pattern by calling the IDisposable.Dispose() method which usually takes care of such cleanup work. And lastly, use the neat profiling features in VS that weren't available back when I made my OFD wrapper, they help discover such issues.

Thanks to all who helped and to SO to make their help available.

noobie
  • 41
  • 6
  • Again: do not catch exceptions and return strings. That's not what structured exception handling was invented for. – slfan Oct 25 '18 at 20:54
0

try

fs.Flush();

and maybe

fs.Close();

after writing. AFAIK there is some sort of caching going on in the IO Streams. It might help.

Regards.