Как безопасно сохранять данные в существующий файл с помощью С#?

Как вы безопасно сохраняете данные в файл, который уже существует на С#? У меня есть некоторые данные, которые сериализованы в файл, и я уверен, что это не очень хорошая идея для безопасного обращения к файлу, потому что, если что-то пойдет не так, файл будет поврежден, а предыдущая версия будет потеряна.

Так вот что я делаю до сих пор:

string tempFile = Path.GetTempFileName();

using (Stream tempFileStream = File.Open(tempFile, FileMode.Truncate))
{
    SafeXmlSerializer xmlFormatter = new SafeXmlSerializer(typeof(Project));
    xmlFormatter.Serialize(tempFileStream, Project);
}

if (File.Exists(fileName)) File.Delete(fileName);
File.Move(tempFile, fileName);
if (File.Exists(tempFile)) File.Delete(tempFile);

Проблема заключается в том, что когда я пытался сохранить файл, который был в моем Dropbox, иногда я получал исключение, говорящее, что он не может сэкономить файл, который уже существует. По-видимому, первый File.Delete(fileName); не удалил файл сразу, а после немного. Поэтому я получил исключение в File.Move(tempFile, fileName);, потому что файл существовал, а затем файл удалился, и мой файл потерялся.

Я использовал другие приложения с файлами в своем Dropbox, и каким-то образом им не удалось их испортить. Когда я пытаюсь сохранить файл в папке Dropbox, иногда я получаю сообщение о том, что файл используется или что-то вроде этого, но у меня никогда не было проблемы с удаляемым файлом.

Итак, какова была бы эталонная/лучшая практика здесь?

Хорошо, вот что я придумал после прочтения всех ответов:

private string GetTempFileName(string dir)
{
    string name = null;
    int attempts = 0;
    do
    {
        name = "temp_" + Player.Math.RandomDigits(10) + ".hsp";
        attempts++;
        if (attempts > 10) throw new Exception("Could not create temporary file.");
    }
    while (File.Exists(Path.Combine(dir, name)));

    return name;
}

private void SaveProject(string fileName)
{
    bool originalRenamed = false;
    string tempNewFile = null;
    string oldFileTempName = null;

    try
    {
        tempNewFile = GetTempFileName(Path.GetDirectoryName(fileName));

        using (Stream tempNewFileStream = File.Open(tempNewFile, FileMode.CreateNew))
        {
            SafeXmlSerializer xmlFormatter = new SafeXmlSerializer(typeof(Project));
            xmlFormatter.Serialize(tempNewFileStream, Project);
        }

        if (File.Exists(fileName))
        {
            oldFileTempName = GetTempFileName(Path.GetDirectoryName(fileName));
            File.Move(fileName, oldFileTempName);
            originalRenamed = true;
        }

        File.Move(tempNewFile, fileName);
        originalRenamed = false;

        CurrentProjectPath = fileName;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
    finally
    {
        if(tempNewFile != null) File.Delete(tempNewFile);

        if (originalRenamed) MessageBox.Show("'" + fileName + "'" +
            " have been corrupted or deleted in this operation.\n" +
            "A backup copy have been created at '" + oldFileTempName + "'");
        else if (oldFileTempName != null) File.Delete(oldFileTempName);
    }
}

Player.Math.RandomDigits - это лишь небольшая функция, которую я создал, которая создает строку с n случайными цифрами.

Я не вижу, как это может испортить исходный файл, если ОС не собирается wacko. Это довольно близко к ответу Ханса, за исключением того, что я сначала сохраняю файл во временном файле, чтобы, если что-то пошло не так, когда сериализуется, мне не нужно переименовывать файл обратно к нему с оригинальным именем, что также может пойти не так. Пожалуйста! сообщите мне, если вы обнаружите недостатки.

Ответы

Ответ 1

Я не уверен, насколько это безопасно, но если ваша ОС не сбой, угадайте, что? Там есть приложение для этого: File.Replace

File.Replace(tempFile, fileName, backupFileName);

Я думаю, что вам действительно нужно в критической ситуации транзакции; только тогда вы можете гарантировать от потери данных. Взгляните на эту статью для решения .NET, но имейте в виду, что это может быть немного сложнее в использовании, чем простое решение для замены файлов.

Ответ 2

Так я обычно это делаю:

  • Всегда пишите в новый файл (скажем hello.dat) с прилагаемым серийным номером автоматического приращения или приложенным временем (просто убедитесь, что он уникален, поэтому используйте тики или микросекунды и т.д.) - скажите hello.dat.012345. Если этот файл существует, сгенерируйте еще один номер и повторите попытку.
  • После сохранения, забудьте об этом. Делайте другие вещи.
  • У вас есть фоновый процесс, который продолжает работать через эти новые файлы. Если существует, выполните следующие действия:
  • Переименуйте исходный файл в резервную копию с серийным номером или меткой времени hello.dathello.dat.bak.023456
  • Переименуйте последний файл в исходное имя hello.dat
  • Удалить файл резервной копии
  • Удалите все промежуточные файлы между последним файлом и исходным файлом (все они все равно будут перезаписаны последним файлом).
  • Возвратитесь к # 3

Если какой-либо сбой произошел где-то между # 3 и # 8, отправьте предупреждающее сообщение. Вы никогда не теряете никаких данных, но временные файлы могут накапливаться. Периодически очищайте свой каталог.