0

I have a windows forms application that should save data to a file. For that I call:

public void SaveVocabulary()
{
    string line;

    try
    {
        //create backup of file
        File.Copy(ConfigData.Filename, ConfigData.Filename.Replace(".txt", "_backup.txt"), true);

        // delete all content
        File.Create(ConfigData.Filename).Close();

        foreach (VocabularyData vocable in vocList)
        {
            line = vocable.VocGerman.Replace('|', '/') + "|";
            line += vocable.VocEnglish.Replace('|', '/') + "|";

            File.AppendAllText(ConfigData.Filename, line + Environment.NewLine);
        }

        // delete backup
        File.Delete(ConfigData.Filename.Replace(".txt", "_backup.txt"));
    }
    catch (Exception ex)
    {
        throw new Exception("Error saving Vocabulary: " + ex.Message, ex);
    }
}

But every 2nd time I pass the line File.Create(ConfigData.Filename).Close(); the code throws an exception telling me, that I can not access the file because it is used by another process.

Der Prozess kann nicht auf die Datei "C:\Users\some-path\Vocabulary.txt" zugreifen, da sie von einem anderen Prozess verwendet wird.

By documentation the file is closed by File.AppendAllText. I also tried to do it with StreamWriter and close it explicitly. This also threw the same exception. Also, no one else is using the file. (If you know a way to prevent someone from opening the file for writing while my program is running, please tell me how I can do so.)

Please tell me why this occurs? How can I make sure the file is "free" after saving? So I can save it later a second time.

EDIT: Here is how I load the file:

public List<VocabularyData> LoadVocabulary()
{
    try
    {
        vocList = new List<VocabularyData>();

        string[] lines = File.ReadAllLines(GetFileName());
        string[] voc;
        VocabularyData vocable;

        foreach (string line in lines)
        {
            voc = line.Split('|');
            vocable = new VocabularyData();
            vocable.VocGerman = voc[0];
            vocable.VocEnglish = voc[1];
            vocable.CreationDate = DateTime.Parse(voc[2]);
            vocable.AssignedDate = DateTime.Parse(voc[3]);
            vocable.SuccessQueue = voc[4];
            vocable.TimeQueue = voc[5];

            vocList.Add(vocable);
        }
    }
    catch (Exception ex)
    {
        throw new Exception("Error loading  Vocabulary: " + ex.Message, ex);
    }

    return vocList;
}
CSDev
  • 3,177
  • 6
  • 19
  • 37
Ephedra
  • 831
  • 10
  • 24
  • 2
    [This exception](https://stackoverflow.com/q/26741191/1997232) is most of time your fault. `File.Create().Close()` followed up by `File.AppendAllText()` is bad idea. What you should rather do: create temp file, write into it everything, then delete old file and rename temp. – Sinatr Aug 01 '19 at 08:52
  • 1
    Get rid of the line you use to delete the file contents and change the append line to `File.WriteAllText`. That will overwrite the contents instead of append to them. – Reinstate Monica Cellio Aug 01 '19 at 08:52
  • I changed it, like Dimity Bychenko said. See code down below. But I still get the Error – Ephedra Aug 01 '19 at 09:57

2 Answers2

3

Let's get rid of explicit Streams (File.Create(ConfigData.Filename).Close();) and let .Net do the work for you:

using System.Linq;

...

// backup - same directory as ConfigData.Filename
//          same filename as ConfigData.Filename with _backup.txt suffix
string backUpName = Path.Combine(
  Path.GetDirectoryName(ConfigData.Filename),
  Path.GetFileNameWithoutExtension(ConfigData.Filename) + "_backup.txt");

File.Copy(ConfigData.Filename, backUpName, true);

// lines we want to save (see comments below)
var lines = vocList
  .Select(vocable => string.Join("|", // do not hardcode, but Join into line
     vocable.VocGerman.Replace('|','/'),
     vocable.VocEnglish.Replace('|', '/'),
     vocable.CreationDate.ToString("dd.MM.yyyy"),
     vocable.AssignedDate.ToString("dd.MM.yyyy"),
     vocable.SuccessQueue,
     vocable.TimeQueue,
     ""
   ));

File.WriteAllLines(ConfigData.Filename, lines);

File.Delete(backUpName);

Edit: File reading routine can be simplified into

public List<VocabularyData> LoadVocabulary() {
  try {
    return File
      .ReadLines(GetFileName())
      .Where(line => !string.IsNullOrWhiteSpace(line)) // to be on the safe side
      .Select(line => line.Split('|'))
      .Select(voc => new VocabularyData() {
         VocGerman    = voc[0],
         VocEnglish   = voc[1],
         CreationDate = DateTime.Parse(voc[2]), 
         AssignedDate = DateTime.Parse(voc[3]),
         SuccessQueue = voc[4],
         TimeQueue    = voc[5]
       })
      .ToList();
  }
  catch (IOException ex) {
    //TODO: do not throw general Exception but derived 
    throw new InvalidOperationException($"Error loading Vocabulary: {ex.Message}", ex);
  }
}
Dmitry Bychenko
  • 180,369
  • 20
  • 160
  • 215
  • thank you but how do I delete the current content of the file now? that means make sure that I dont add things twice? – Ephedra Aug 01 '19 at 09:25
  • 1
    @Ephedra: Unlike `File.AppendAllLines`, `File.WriteAllLines` *overrides* the file – Dmitry Bychenko Aug 01 '19 at 09:26
  • my debugger has a Problem with this line: var lines = vocList.Select(vocable => $"{vocable.VocGerman.Replace('|', '/')}|{vocable.VocEnglish.Replace('|', '/')}|"); It says there is a ')' expected. I also dont know what this is doing ... where can I read about that? – Ephedra Aug 01 '19 at 09:29
  • also I just posted a shortcut because I did not expect this to be important... the whole code ist like this: line = vocable.VocGerman.Replace('|','/') + "|"; line += vocable.VocEnglish.Replace('|', '/') + "|"; line += vocable.CreationDate.ToString("dd.MM.yyyy") + "|"; line += vocable.AssignedDate.ToString("dd.MM.yyyy") + "|"; line += vocable.SuccessQueue + "|"; line += vocable.TimeQueue + "|"; – Ephedra Aug 01 '19 at 09:30
  • 1
    @Ephedra: I see; let's `Join` the desired fragments into the line with a help of `string.Join`. See my edit, please. – Dmitry Bychenko Aug 01 '19 at 09:34
  • thanks very much. The code works....hm... but I still get the same error sometimes. – Ephedra Aug 01 '19 at 09:45
  • I alway get the (cant access file because it is used) after adding some Vocabular. When I only change some Vocabular it works. – Ephedra Aug 01 '19 at 09:47
  • 1
    @Ephedra: I seems you want to inspect the fragment when you *read* the file – Dmitry Bychenko Aug 01 '19 at 09:49
  • 1
    Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/197338/discussion-between-dmitry-bychenko-and-ephedra). – Dmitry Bychenko Aug 01 '19 at 10:06
0

OMG

I found out what my error was!

I use the file as an attachment in a mail. And I guess the mail is not yet sent, when I again save it.

thanks for all the help. I finally find it out with Process explorer und Exploring at wicht time my file becomes locked.

Ephedra
  • 831
  • 10
  • 24