¿Es la conversión de un archivo a una matriz de bytes la mejor manera de guardar CUALQUIER formato de archivo en el disco o en la base de datos var columna binaria?
Entonces, si alguien quiere guardar un archivo .gif o .doc/.docx o .pdf, ¿puedo convertirlo en un bytearray UFT8 y guardarlo en la base de datos como un flujo de bytes?
Ya que no se menciona a qué base de datos quiere decir que estoy asumiendo SQL Server. A continuación la solución funciona tanto para 2005 como para 2008.
Tienes que crear una tabla con VARBINARY(MAX)
como una de las columnas. En mi ejemplo, he creado la tabla Raporty
con la columna RaportPlik
siendo VARBINARY(MAX)
columna.
Método para poner file
en la base de datos de drive
:
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();
}
}
Este método es obtener file
de la base de datos y guardarlo en drive
:
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);
}
}
}
Este método es obtener file
de la base de datos y ponerlo como MemoryStream
:
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;
}
Este método es poner MemoryStream
en la base de datos:
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;
}
Codificación feliz :-)
Si bien puede almacenar archivos de esta manera, tiene compensaciones significativas:
Estos son solo algunos de los inconvenientes que se me ocurren en la cabeza. Para proyectos pequeños, puede valer la pena almacenar archivos de esta manera, pero si está diseñando software de nivel empresarial, le recomiendo encarecidamente que no lo haga.
Realmente depende del servidor de base de datos.
Por ejemplo, SQL Server 2008 admite un tipo de datos FILESTREAM
para exactamente esta situación.
Aparte de eso, si usa una MemoryStream
, tiene un método ToArray()
que se convertirá en un byte[]
- esto se puede usar para llenar un campo varbinary
..
Confirmando que pude usar la respuesta publicada por MadBoy y editada por Otiel en MS SQL Server 2012 y 2014, además de las versiones enumeradas anteriormente usando las columnas varbinary (MAX).
Si se pregunta por qué no puede "Filestream" (anotado en una respuesta por separado) como un tipo de datos en el diseñador de tablas de SQL Server o por qué no puede configurar el tipo de datos de una columna en "Filestream" usando T-SQL, es porque FILESTREAM es un almacenamiento atributo del tipo de datos varbinary (MAX). No es un tipo de datos por sí mismo.
Consulte estos artículos sobre cómo configurar y habilitar FILESTREAM en una base de datos: 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
Una vez configurada, una columna varbinary (max) habilitada para filestream se puede agregar así:
ALTER TABLE TableName
ADD ColumnName varbinary (max) FILESTREAM NULL
IR
Voy a describir la forma en que he almacenado los archivos, en SQL Server y Oracle. Depende en gran medida de cómo obtenga el archivo, en primer lugar, de cómo obtendrá su contenido, y dependerá de qué base de datos esté utilizando para el contenido en el que lo almacenará y cómo lo almacenará. Estos son 2 ejemplos de bases de datos independientes con 2 métodos separados para obtener el archivo que usé.
SQL Server
Respuesta corta: utilicé una cadena de bytes base64 que convertí en un byte[]
y almacené en un campo varbinary(max)
.
Respuesta larga:
Digamos que estás subiendo a través de un sitio web, así que estás usando un control <input id="myFileControl" type="file" />
, o React DropZone. Para obtener el archivo, estás haciendo algo como var myFile = document.getElementById("myFileControl")[0];
o myFile = this.state.files[0];
.
A partir de ahí, obtendría la cadena base64 utilizando el código aquí: Convertir input = archivo en una matriz de bytes (usar la función UploadFile2
).
Luego obtendría esa cadena, el nombre del archivo (myFile.name
) y escribo (myFile.type
) en un objeto JSON:
var myJSONObj = {
file: base64string,
name: myFile.name,
type: myFile.type,
}
y publique el archivo en un servidor MVC backend usando XMLHttpRequest, especificando un Tipo de Contenido de application/json
: xhr.send(JSON.stringify(myJSONObj);
. Tienes que construir un ViewModel para enlazarlo con:
public class MyModel
{
public string file { get; set; }
public string title { get; set; }
public string type { get; set; }
}
y especifique [FromBody]MyModel myModelObj
como el parámetro pasado en:
[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)
Luego, puede agregar esto a esa función y guardarlo usando Entity Framework:
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);
siendo la línea operativa.
Necesitarías un modelo para representar la tabla de la base de datos:
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; }
}
Esto guardará los datos en un campo varbinary(max)
como un byte[]
. Name
y Type
eran nvarchar(250)
y nvarchar(10)
, respectivamente. Puede incluir el tamaño agregándolo a su tabla como int
columna & MY_ATTACHMENT_TABLE_MODEL
como public int Size { get; set;}
, y agregue la línea tblAtchm.Size = System.Convert.FromBase64String(myModelObj.file).Length;
anterior.
Oracle
Respuesta corta: conviértala en un byte[]
, asigne un OracleParameter
, agréguela a su OracleCommand
, y actualice el campo BLOB
de su tabla usando una referencia al valor ParameterName
del parámetro: :BlobParameter
Respuesta larga: cuando hice esto para Oracle, estaba usando una OpenFileDialog
y recuperé y envié la información de bytes/archivos de esta manera:
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);
}
Y dentro de la actualización de Oracle, hecho con ADO:
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
);
}
}
Y el tipo de datos para la columna FILE_CONTENTS
era BLOB
, el FILE_SIZE
era NUMBER(10,0)
, LAST_MODIFIED
era DATE
, y el resto era NVARCHAR2(250)
.
Sí, generalmente la mejor manera de almacenar un archivo en una base de datos es guardar la matriz de bytes en una columna BLOB. Es probable que desee un par de columnas para almacenar adicionalmente los metadatos del archivo, como el nombre, la extensión, etc.
No siempre es una buena idea almacenar archivos en la base de datos; por ejemplo, el tamaño de la base de datos aumentará rápidamente si almacena archivos en ella. Pero todo depende de su escenario de uso.
¿Qué base de datos estás utilizando? normalmente no guardas archivos en una base de datos, pero creo que SQL 2008 tiene soporte para eso ...
Un archivo es datos binarios, por lo tanto, UTF 8 no importa aquí.
UTF 8 importa cuando intentas convertir una cadena a una matriz de bytes ... no un archivo a una matriz de bytes.