如何使用 C# 安全地将数据保存到现有文件?
如何安全地将数据保存到 C# 中已存在的文件中?我有一些数据被序列化到文件中,我很确定直接安全到文件不是一个好主意,因为如果出现任何问题,文件将被损坏,以前的版本将丢失。
这就是我到目前为止一直在做的事情:
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 个随机数字的字符串。
我不明白这怎么会弄乱原始文件,除非操作系统变得疯狂。这与汉斯的答案非常接近,只是我首先将文件保存到临时文件中,这样,如果序列化时出现问题,我不需要将文件重命名回原来的名称,这也可能会出错。请!如果您发现任何缺陷,请告诉我。
How do you safely save data to a file that already exists in C#? I have some data that is serialized to a file and I'm pretty sure is not a good idea to safe directly to the file because if anything goes wrong the file will get corrupted and the previous version will get lost.
So this is what I've been doing so far:
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);
The problem is that when I tried to save to a file that was in my Dropbox, sometimes I got an exception telling me that it cannot save to a file that already exists. Apparently the first File.Delete(fileName);
didn't delete the file right away but after a little bit. So I got an exception in the File.Move(tempFile, fileName);
because the file existed and then the file got erased and my file got lost.
I've used other applications with files in my Dropbox and somehow they manage to not mess it up. When I'm trying to save to a file in my Dropbox folder, sometimes I get a message telling me that the file is being used or stuff like that but I never had a problem with a file being erased.
So what would it be the standard / best practice here?
OK this is what I came up with after reading all answers:
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
is just a little function I made that creates an string with n random digits.
I don't see how could this mess up the original file unless the OS is going wacko. It's pretty close to Hans's answer except that I first save the file to a temporary file so that, if something goes wrong when serializing, I don't need to rename the file back to it's original name, which can also go wrong. Please! let me know if you find any flaw.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
我不确定这有多安全,但假设你的操作系统没有崩溃,你猜怎么着?有一个应用程序:File.Replace
我想你<在紧急情况下真正需要的是事务;只有这样你才能保证数据不丢失。查看本文了解 .NET 解决方案,但请注意,它可能是一个比简单的文件替换解决方案更难使用。
I'm not sure how secure this is, but assuming your OS isn't crashing, guess what? There's an app for that: File.Replace
I think what you really need in a critical situation is transactions; only then can you guarantee against the loss of data. Take a look at this article for a .NET solution, but be aware that it might be a bit harder to use than a simple file replacement solution.
这是我通常这样做的方式:
hello.dat
),并附加自动增量序列号或附加时间(只需确保它是唯一的,所以使用刻度或微秒等)——说hello.dat.012345
。如果该文件存在,请生成另一个号码并重试。hello.dat
->hello.dat.bak.023456
hello.dat
如果#3 和#8 之间有任何故障,则发送警告消息。您永远不会丢失任何数据,但临时文件可能会累积。偶尔清理一下你的目录。
This is the way I usually do it:
hello.dat
) with an auto-increment serial number attached, or the time attached (just make sure that it is unique, so use ticks or microseconds etc.) -- sayhello.dat.012345
. If that file exists, generate another number and try again.hello.dat
->hello.dat.bak.023456
hello.dat
If there is any failure anywhere between #3 and #8, send a warning message. You're never losing any data, but temp files may build up. Clean your directory once in a while.