关于c#:将任何文件保存到数据库中,只需将其转换为字节数组?

Saving any file to in the database, just convert it to a byte array?

将文件转换为字节数组是否是将任何文件格式保存到磁盘或数据库var二进制列的最佳方法?

所以,如果有人想保存.gif或.doc/.docx或.pdf文件,我可以将其转换为bytearray uft8并将其保存为字节流吗?


因为它没有提到您的意思是什么数据库,所以我假设使用的是SQL Server。下面的解决方案适用于2005年和2008年。

必须使用VARBINARY(MAX)作为列之一创建表。在我的示例中,我创建了表Raporty,其中列RaportPlikVARBINARY(MAX)列。

filedrive中放入数据库的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public static void databaseFilePut(string varFilePath) {
    byte[] file;
    using (var stream = new FileStream(varFilePath, FileMode.Open, FileAccess.Read)) {
        using (var reader = new BinaryReader(stream)) {
            file = reader.ReadBytes((int) stream.Length);      
        }          
    }
    using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
    using (var sqlWrite = new SqlCommand("INSERT INTO Raporty (RaportPlik) Values(@File)", varConnection)) {
        sqlWrite.Parameters.Add("@File", SqlDbType.VarBinary, file.Length).Value = file;
        sqlWrite.ExecuteNonQuery();
    }
}

此方法是从数据库中获取file并保存到drive上:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void databaseFileRead(string varID, string varPathToNewLocation) {
    using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
    using (var sqlQuery = new SqlCommand(@"SELECT [RaportPlik] FROM [dbo].[Raporty] WHERE [RaportID] = @varID", varConnection)) {
        sqlQuery.Parameters.AddWithValue("@varID", varID);
        using (var sqlQueryResult = sqlQuery.ExecuteReader())
            if (sqlQueryResult != null) {
                sqlQueryResult.Read();
                var blob = new Byte[(sqlQueryResult.GetBytes(0, 0, null, 0, int.MaxValue))];
                sqlQueryResult.GetBytes(0, 0, blob, 0, blob.Length);
                using (var fs = new FileStream(varPathToNewLocation, FileMode.Create, FileAccess.Write))
                    fs.Write(blob, 0, blob.Length);
            }
    }
}

该方法是从数据库中获取file,取MemoryStream

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static MemoryStream databaseFileRead(string varID) {
    MemoryStream memoryStream = new MemoryStream();
    using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
    using (var sqlQuery = new SqlCommand(@"SELECT [RaportPlik] FROM [dbo].[Raporty] WHERE [RaportID] = @varID", varConnection)) {
        sqlQuery.Parameters.AddWithValue("@varID", varID);
        using (var sqlQueryResult = sqlQuery.ExecuteReader())
            if (sqlQueryResult != null) {
                sqlQueryResult.Read();
                var blob = new Byte[(sqlQueryResult.GetBytes(0, 0, null, 0, int.MaxValue))];
                sqlQueryResult.GetBytes(0, 0, blob, 0, blob.Length);
                //using (var fs = new MemoryStream(memoryStream, FileMode.Create, FileAccess.Write)) {
                memoryStream.Write(blob, 0, blob.Length);
                //}
            }
    }
    return memoryStream;
}

此方法是将MemoryStream放入数据库:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public static int databaseFilePut(MemoryStream fileToPut) {
        int varID = 0;
        byte[] file = fileToPut.ToArray();
        const string preparedCommand = @"
                    INSERT INTO [dbo].[Raporty]
                               ([RaportPlik])
                         VALUES
                               (@File)
                        SELECT [RaportID] FROM [dbo].[Raporty]
            WHERE [RaportID] = SCOPE_IDENTITY()
                   "
;
        using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetails))
        using (var sqlWrite = new SqlCommand(preparedCommand, varConnection)) {
            sqlWrite.Parameters.Add("@File", SqlDbType.VarBinary, file.Length).Value = file;

            using (var sqlWriteQuery = sqlWrite.ExecuteReader())
                while (sqlWriteQuery != null && sqlWriteQuery.Read()) {
                    varID = sqlWriteQuery["RaportID"] is int ? (int) sqlWriteQuery["RaportID"] : 0;
                }
        }
        return varID;
    }

快乐编码:—)


虽然您可以以这种方式存储文件,但它有重大的折衷:

  • 大多数DBS没有针对大量的二进制数据进行优化,查询性能通常会随着表的膨胀而急剧下降,即使有索引也是如此。(具有文件流列类型的SQL Server 2008是规则的例外。)
  • 数据库备份/复制变得非常缓慢。
  • 使用200万个映像处理损坏的驱动器要比使用损坏的DB表容易得多——只需更换RAID上的磁盘即可。
  • 如果您不小心删除了文件系统上的一打图像,您的操作人员可以很容易地从备份中替换它们,而且由于表索引比较小,所以可以快速恢复。如果不小心在一个巨大的数据库表中删除了十几个图像,那么要从备份中恢复数据库,您需要等待很长的时间,同时会使整个系统瘫痪。

这些只是我能想出的一些缺点。对于小项目来说,以这种方式存储文件可能是值得的,但是如果您正在设计企业级软件,我强烈建议您不要这样做。


它实际上取决于数据库服务器。

例如,对于这种情况,SQLServer2008支持FILESTREAM数据类型。

除此之外,如果使用MemoryStream方法,它有一个ToArray()方法,该方法将转换为byte[]—这可用于填充varbinary字段。


我将描述在SQL Server和Oracle中存储文件的方式。它在很大程度上取决于您如何获取文件,首先,取决于您将如何获取其内容,并取决于您将在其中存储文件的内容所使用的数据库以及存储文件的方式。这是两个单独的数据库示例,其中有两个单独的方法来获取我使用的文件。

SQL服务器

简短的回答:我使用了一个base64字节的字符串,我将它转换成一个byte[]并存储在一个varbinary(max)字段中。

长答案:

假设你是通过一个网站上传的,那么你使用的是控件,或者react dropzone。要获取文件,您需要执行类似于var myFile = document.getElementById("myFileControl")[0];myFile = this.state.files[0];的操作。

从这里,我将使用下面的代码得到base64字符串:将input=file转换为byte数组(使用函数UploadFile2)。

然后我将得到该字符串、文件名(myFile.name和类型(myFile.type到JSON对象中:

1
2
3
4
5
var myJSONObj = {
    file: base64string,
    name: myFile.name,
    type: myFile.type,
}

并使用xmlhttprequest将文件发布到MVC服务器后端,指定application/jsonxhr.send(JSON.stringify(myJSONObj);的内容类型。您必须构建一个视图模型来绑定它:

1
2
3
4
5
6
public class MyModel
{
    public string file { get; set; }
    public string title { get; set; }
    public string type { get; set; }
}

并指定[FromBody]MyModel myModelObj作为传入参数:

1
2
[System.Web.Http.HttpPost]  // required to spell it out like this if using ApiController, or it will default to System.Mvc.Http.HttpPost
public virtual ActionResult Post([FromBody]MyModel myModelObj)

然后您可以将它添加到该函数中,并使用实体框架保存它:

1
2
3
4
5
6
7
MY_ATTACHMENT_TABLE_MODEL tblAtchm = new MY_ATTACHMENT_TABLE_MODEL();
tblAtchm.Name = myModelObj.name;
tblAtchm.Type = myModelObj.type;
tblAtchm.File = System.Convert.FromBase64String(myModelObj.file);
EntityFrameworkContextName ef = new EntityFrameworkContextName();
ef.MY_ATTACHMENT_TABLE_MODEL.Add(tblAtchm);
ef.SaveChanges();

tblAtchm.File = System.Convert.FromBase64String(myModelObj.file);是操作线。

您需要一个模型来表示数据库表:

1
2
3
4
5
6
7
public class MY_ATTACHMENT_TABLE_MODEL
{
    [Key]
    public byte[] File { get; set; }  // notice this change
    public string Name { get; set; }
    public string Type { get; set; }
}

这将把数据保存到varbinary(max)字段中,作为byte[]字段。NameType分别为nvarchar(250)nvarchar(10)。您可以将其作为一个int列和MY_ATTACHMENT_TABLE_MODEL作为public int Size { get; set;}添加到您的表中,并在上面的行tblAtchm.Size = System.Convert.FromBase64String(myModelObj.file).Length;中添加。

甲骨文公司

简短回答:将其转换为byte[],将其分配给OracleParameter,将其添加到OracleCommand,并使用参数的ParameterName值的引用更新表的BLOB字段::BlobParameter

长答案:当我为Oracle执行此操作时,我使用的是OpenFileDialog,我检索并发送字节/文件信息的方式是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
byte[] array;
OracleParameter param = new OracleParameter();
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.Filter ="Image Files (*.jpg, *.jpeg, *.jpe)|*.jpg;*.jpeg;*.jpe|Document Files (*.doc, *.docx, *.pdf)|*.doc;*.docx;*.pdf"
if (dlg.ShowDialog().Value == true)
{
    string fileName = dlg.FileName;
    using (FileStream fs = File.OpenRead(fileName)
    {
        array = new byte[fs.Length];
        using (BinaryReader binReader = new BinaryReader(fs))
        {
            array = binReader.ReadBytes((int)fs.Length);
        }

        // Create an OracleParameter to transmit the Blob
        param.OracleDbType = OracleDbType.Blob;
        param.ParameterName ="BlobParameter";
        param.Value = array;  // <-- file bytes are here
    }
    fileName = fileName.Split('\')[fileName.Split('\').Length-1]; // gets last segment of the whole path to just get the name

    string fileType = fileName.Split('
.')[1];
    if (fileType =="doc" || fileType =="docx" || fileType =="pdf")
        fileType ="application\" + fileType;
    else
        fileType ="image\" + fileType;

    // SQL string containing reference to BlobParameter named above
    string sql = String.Format("INSERT INTO YOUR_TABLE (FILE_NAME, FILE_TYPE, FILE_SIZE, FILE_CONTENTS, LAST_MODIFIED) VALUES ('
{0}','{1}',{2},:BlobParamerter, SYSDATE)", fileName, fileType, array.Length);

    // Do Oracle Update
    RunCommand(sql, param);
}

在Oracle更新中,使用ADO完成:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public void RunCommand(string sql, OracleParameter param)
{
    OracleConnection oraConn = null;
    OracleCommand oraCmd = null;
    try
    {
        string connString = GetConnString();
        oraConn = OracleConnection(connString);
        using (oraConn)
        {
            if (OraConnection.State == ConnectionState.Open)
                OraConnection.Close();

            OraConnection.Open();

            oraCmd = new OracleCommand(strSQL, oraConnection);

            // Add your OracleParameter
            if (param != null)
                OraCommand.Parameters.Add(param);

            // Execute the command
            OraCommand.ExecuteNonQuery();
        }
    }
    catch (OracleException err)
    {
       // handle exception
    }
    finally
    {
       OraConnction.Close();
    }
}

private string GetConnString()
{
    string host = System.Configuration.ConfigurationManager.AppSettings["host"].ToString();
    string port = System.Configuration.ConfigurationManager.AppSettings["port"].ToString();
    string serviceName = System.Configuration.ConfigurationManager.AppSettings["svcName"].ToString();
    string schemaName = System.Configuration.ConfigurationManager.AppSettings["schemaName"].ToString();
    string pword = System.Configuration.ConfigurationManager.AppSettings["pword"].ToString(); // hopefully encrypted

    if (String.IsNullOrEmpty(host) || String.IsNullOrEmpty(port) || String.IsNullOrEmpty(serviceName) || String.IsNullOrEmpty(schemaName) || String.IsNullOrEmpty(pword))
    {
        return"Missing Param";
    }
    else
    {
        pword = decodePassword(pword);  // decrypt here
        return String.Format(
          "Data Source=(DESCRIPTION =(ADDRESS = ( PROTOCOL = TCP)(HOST = {2})(PORT = {3}))(CONNECT_DATA =(SID = {4})));User Id={0};Password{1};",
           user,
           pword,
           host,
           port,
           serviceName
           );
    }
}

FILE_CONTENTS列的数据类型为BLOBFILE_SIZENUMBER(10,0)LAST_MODIFIEDDATE,其余为NVARCHAR2(250)


确认除了先前使用varbinary(max)列列出的版本外,我还能够使用Madboy发布的、Otiel在MS SQL Server 2012和2014上编辑的答案。

如果您想知道为什么在SQL Server表设计器中不能将"filestream"(在单独的答案中指出)作为数据类型,或者为什么不能使用T-SQL将列的数据类型设置为"filestream",这是因为filestream是varbinary(max)数据类型的存储属性。它本身不是数据类型。

请参阅以下有关在数据库上设置和启用文件流的文章:https://msdn.microsoft.com/en-us/library/cc645923(v=sql.120).aspx

http://www.kodyaz.com/t-sql/default-filestream-filegroup-is-not-available-in-database.aspx

配置后,可以按如下方式添加启用了文件流的varbinary(max)列:

更改表表名

添加columnname varbinary(max)filestream NULL


是的,通常在数据库中存储文件的最佳方法是将字节数组保存在blob列中。您可能需要一些列来额外存储文件的元数据,如名称、扩展名等。

在数据库中存储文件并不总是一个好主意,例如,如果在其中存储文件,数据库大小将快速增长。但这完全取决于您的使用场景。


您使用的是什么数据库?通常不会将文件保存到数据库,但我认为SQL 2008支持它…

文件是二进制数据,因此这里UTF 8不重要。

当您试图将字符串转换为字节数组时,UTF 8很重要…不是文件到字节数组。