Subido por oscar piñones

CARGAR Y DESCARGAR MEDIA DESDE SQL SERVER MVC

Anuncio
2019
FILESTREAM: Descargar y
cargar documentos desde
SQL Server a través de
ASP.Net MVC
ISGCAS: ADMINISTRACIÓN DOCUMENTAL EN SQL SERVER
OSCAR PIÑONES SAAVEDRA
OPENDAT S.A. | Uribe 636, Oficina 601
Descargar y cargar medios de SQL Server a través de ASP.Net MVC
Una pregunta frecuente que aparece en los foros de discusión es cómo servir un medio que se almacena en la tabla
de SQL Server desde una aplicación ASP.Net. Desafortunadamente, la respuesta casi siempre es errónea, ya que la
solución predominante consiste en copiar el medio completo. Esta solución funciona bien cuando se prueba con
una carga ligera y devuelve pocos medios pequeños. Pero en el entorno de producción, la memoria requerida por
todos los archivos almacenados como matrices de bytes en la memoria causa una grave degradación del
rendimiento. Una buena solución debe utilizar la semántica de transmisión, transfiriendo los datos en pequeños
fragmentos del servidor SQL al resultado devuelto de HTTP.
Los componentes de SqlClient ofrecen semánticas de transmisión para grandes conjuntos de resultados, incluidos
grandes campos BLOB, pero el cliente tiene que solicitarlo. El 'ingrediente secreto' es el paso en el indicador
CommandBehavior.SequentialAccess hacia SqlCommand.ExecuteReader:
Proporciona una forma para que el DataReader maneje las filas que contienen columnas con
grandes valores binarios. En lugar de cargar toda la fila, SequentialAccess permite al DataReader
cargar datos como una secuencia. Luego puede usar el método GetBytes o GetChars para
especificar una ubicación de byte para iniciar la operación de lectura y un tamaño de búfer
limitado para los datos que se devuelven.
Una carpeta ASP.Net Virtual MVC Media respaldada por SQL Server
Digamos que queremos tener una carpeta de medios virtual en un sitio MVC de ASP.Net, que sirva los archivos de
una base de datos de SQL Server. Una solicitud GET para una URL como "http: //site/Media/IMG0042.JPG" debe
devolver el contenido del archivo llamado IMG0042.JPG de la base de datos. Una solicitud POST a la URL "http: //
site / Media" que contiene un archivo incrustado debe insertar este nuevo archivo en la base de datos y redirigir la
respuesta a la ruta virtual del archivo recién agregado. Así es como subimos el formulario HTML:
<form method="post" action="/Media" enctype="multipart/form-data">
<input type="file" name="file" id="file"/>
<input type="submit" name="Submit" value="Submit"/>
</form>
routes.MapRoute(
"Media",
"Media/{filename}",
new { controller = "Media", action = "GetFile" },
new { filename = @"[^/?*:;{}\\]+" });
26-5-2019
Comenzaremos agregando una ruta especial en Global.asax.cs que actuará como una carpeta virtual para las
solicitudes de descarga de archivos:
1
Tenga en cuenta que la carga será manejada por la ruta MVC predeterminada si agregamos un método Index() a
nuestro controlador que manejará el POST.
Para nuestras solicitudes GET necesitamos una clase FileDownloadModel para representar las propiedades del
archivo solicitado. Para este ejemplo, no necesitamos un modelo POST ya que solo tendremos un único campo de
entrada, el archivo cargado. Vamos a utilizar una interfaz de repositorio que declara dos métodos: GetFileByName
devuelve desde el repositorio un FileDownloadModel dado un nombre de archivo, y PutFile aceptará un archivo
cargado y lo colocará en el repositorio.
public class FileDownloadModel
{
public
public
public
public
public
string FileName {get; internal set;}
string ContentType {get; internal set;}
string ContentCoding {get; internal set;}
long ContentLength {get; internal set;}
Stream Content {get; internal set;}
}
public interface IMediaRepository
{
bool GetFileByName(
string fileName,
out FileDownloadModel file);
void PostFile(HttpPostedFileBase file, out string fileName);
}
Con esta interfaz de Repository podemos codificar nuestra clase MediaController:
public class MediaController: Controller
{
public IMediaRepository Repository {get; set;}
public MediaController()
{
Repository = new SqlMediaRepository();
}
[HttpGet]
public ActionResult GetFile(string fileName)
{
FileDownloadModel model;
if (false == Repository.GetFileByName(
fileName,
out model))
{
return new HttpNotFoundResult
{
StatusDescription = String.Format(
"File {0} not found",
fileName)
26-5-2019
[HttpPost]
public ActionResult Index()
{
string fileName;
Repository.PostFile(Request.Files[0], out fileName);
return new RedirectResult("/Media/" + fileName);
}
2
};
}
if ( null != model.ContentCoding)
{
Response.AddHeader(
"Content-Encoding",
model.ContentCoding);
}
Response.AddHeader(
"Content-Length",
model.ContentLength.ToString ());
Response.BufferOutput = false;
return new FileStreamResult(
model.Content,
model.ContentType);
}
}
Hemos codificado la implementación del Repositorio en SqlMediaRepository, una clase que crearemos en breve.
Un proyecto real probablemente usaría los patrones de inyección de dependencia o inversión de control, tal vez
usando Castle Windsor, por ejemplo. Para mayor brevedad, omitiremos estos detalles, hay muchos blogs y artículos
que describen cómo hacerlo.
Tenga en cuenta el uso de la devolución FileStreamResult, que es una acción suministrada por MVC para devolver
una descarga de un objeto Stream arbitrario. Lo que también nos lleva al siguiente punto, necesitamos
implementar un flujo que lea el contenido de un SqlDataReader.
Una secuencia basada en SqlDataReader
Ahora necesitamos una implementación de la clase Stream abstracta que pueda transmitir una columna BLOB
desde un SqlDataReader. Espera, dices, ¿SqlBytes ya tiene una propiedad Stream que lee un BLOB de un resultado
como un Stream? Desafortunadamente, este pequeño comentario hace que esta clase sea inútil para nuestros
propósitos:
Obtener o configurar la propiedad Stream carga todos los datos en la memoria. Su uso con datos
de gran tamaño puede provocar una excepción OutOfMemoryException.
public class SqlReaderStream: Stream
{
private SqlDataReader reader;
private int columnIndex;
26-5-2019
Así que nos quedamos con la implementación de un Stream basado en un campo BLOB SqlDataReader, un Stream
que devuelve el contenido del BLOB utilizando las llamadas GetBytes adecuadas y no carga todo el BLOB en la
memoria. Afortunadamente, esto es bastante simple ya que solo necesitamos implementar un puñado de
métodos:
3
private long position;
public SqlReaderStream(
SqlDataReader reader,
int columnIndex)
{
this.reader = reader;
this.columnIndex = columnIndex;
}
public override long Position
{
get {return position;}
set {throw new NotImplementedException();}
}
public override int Read(byte[] buffer, int offset, int count)
{
long bytesRead = reader.GetBytes(columnIndex, position, buffer, offset, count);
position += bytesRead;
return (int)bytesRead;
}
public override bool CanRead
{
get {return true;}
}
public override bool CanSeek
{
get {return false;}
}
public override bool CanWrite
{
get {return false;}
}
public override void Flush()
{
throw new NotImplementedException();
}
public override long Length
{
get {throw new NotImplementedException();}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
protected override void Dispose(bool disposing)
{
if (disposing && null != reader)
{
reader.Dispose();
reader = null;
}
base.Dispose(disposing);
}
26-5-2019
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
4
}
Como puede ver, solo necesitamos devolver las respuestas correctas a CanRead (sí), CanWrite (no) y CanSeek
(también no), realizar un seguimiento de nuestra posición actual y necesitamos implementar la lectura para
obtener más bytes del lector, usando GetReader.
También estamos anulando el método Dispose (bool disposing). Esto se debe a que tendremos que cerrar el
SqlDataReader cuando se complete la transferencia de contenido. Si es la primera vez que ve esta firma del método
de Disposición, entonces debe leer Implementación de un método de Disposición
Streaming de carga de datos BLOB
Al igual que recuperar datos BLOB grandes de SQL Server presenta desafíos para evitar la creación de copias
completas en memoria de todo el BLOB, surgen problemas similares cuando se intenta insertar un BLOB. La mejor
solución es en realidad bastante complicada. Implica enviar los datos al servidor en trozos y usar la sintaxis de BLOB
UPDATE en el lugar. La MSDN tiene esto que decir en la sección de Comentarios:
Use la cláusula .WRITE (expresión, @Offset, @Length) para realizar una actualización parcial o completa de los
tipos de datos varchar (max), nvarchar (max) y varbinary (max). Por ejemplo, una actualización parcial de una
columna varchar (max) podría eliminar o modificar solo los primeros 200 caracteres de la columna, mientras que
una actualización completa eliminaría o modificaría todos los datos de la columna. Para obtener el mejor
rendimiento, recomendamos que los datos se inserten o actualicen en tamaños de trozos que sean múltiplos de
8040 bytes.
Para implementar dicha semántica, escribiremos una segunda implementación de Stream, esta vez para las
subidas:
public class SqlStreamUpload: Stream
{
public SqlCommand InsertCommand {get; set;}
public SqlCommand UpdateCommand {get; set;}
public SqlParameter InsertDataParam {get; set;}
public SqlParameter UpdateDataParam {get; set;}
public override bool CanRead
{
get {return false;}
}
public override bool CanSeek
{
get {return false;}
}
public override void Flush()
{
}
public override long Length
{
get {throw new NotImplementedException();}
26-5-2019
public override bool CanWrite
{
get {return true;}
}
5
}
public override long Position
{
get; set;
}
public override int Read(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
byte[] data = buffer;
if (offset != 0 ||
count != buffer.Length)
{
data = new byte[count];
Array.Copy(buffer, offset, data, 0, count);
}
if (0 == Position &&
null != InsertCommand)
{
InsertDataParam.Value = data;
InsertCommand.ExecuteNonQuery();
}
else
{
UpdateDataParam.Value = data;
UpdateCommand.ExecuteNonQuery();
}
Position += count;
}
}
Esta implementación de Stream utiliza dos objetos SqlCommand: un InsertCommand para guardar el primer
fragmento, y un UpdateCommand para guardar los fragmentos subsiguientes. Tenga en cuenta que el tamaño de
los fragmentos (los 8040 bytes óptimos) no se especifica en ninguna parte, lo que se logra fácilmente envolviendo
el SqlStreamUpload en una instancia de BufferedStream.
create table media (
[media_id] int not null identity(1,1),
[file_name] varchar(256),
[content_type] varchar(256),
[content_coding] varchar(256),
[content] varbinary(max),
constraint pk_media_id primary key([media_id]),
constraint unique_file_name unique ([file_name]));
26-5-2019
La tabla MEDIA
6
Esta tabla contiene los archivos multimedia descargables. Los archivos se identifican por su nombre, por lo que los
nombres tienen una restricción única. He agregado una clave primaria de IDENTIDAD, porque en un CMS estos
archivos a menudo son referencias de otras partes de la aplicación e INT es una clave de referencia más corta que
un nombre de archivo. El campo tipo de contenido es necesario para saber el tipo de archivo: "imagen / png",
"imagen / jpg", etc. (consulte los tipos de medios de la IANA registrados oficialmente. El campo de codificación de
contenido es necesario si los archivos se almacenan comprimidos y un HTTP de codificación de contenido El
encabezado con la etiqueta "gzip" o "desinflar" se debe agregar a la respuesta. Tenga en cuenta que la mayoría de
los tipos de imágenes (JPG, PNG) no se comprimen bien, ya que estos formatos de archivo ya incluyen un algoritmo
de compresión y cuando se comprimen nuevamente con el los algoritmos de transferencia HTTP comunes (gzip,
deflate, comprimir) generalmente aumentan de tamaño.
El SqlMediaRepository
La última pieza del rompecabezas: una implementación de la interfaz IMediaRepository que utiliza un servidor de
SQL Server para el almacenamiento de archivos:
public class SqlMediaRepository: IMediaRepository
{
private SqlConnection GetConnection()
{
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(
ConfigurationManager.ConnectionStrings["Images"].ConnectionString);
scsb.Pooling = true;
SqlConnection conn = new SqlConnection(scsb.ConnectionString);
conn.Open();
return conn;
}
public void PostFile(
HttpPostedFileBase file,
out string fileName)
{
fileName = Path.GetFileName(file.FileName);
SqlCommand cmdUpdate = new SqlCommand(
@"UPDATE media
SET content.write (@data, NULL, NULL)
WHERE file_name = @content_disposition;", conn, trn);
cmdUpdate.Parameters.Add("@data", SqlDbType.VarBinary, -1);
26-5-2019
using (SqlConnection conn = GetConnection())
{
using (SqlTransaction trn = conn.BeginTransaction())
{
SqlCommand cmdInsert = new SqlCommand(
@"INSERT INTO media (
file_name,
content_type,
content_coding,
content)
values (
@content_disposition,
@content_type,
@content_coding,
@data);", conn, trn);
cmdInsert.Parameters.Add("@data", SqlDbType.VarBinary, -1);
cmdInsert.Parameters.Add("@content_disposition", SqlDbType.VarChar, 256);
cmdInsert.Parameters["@content_disposition"].Value = fileName;
cmdInsert.Parameters.Add("@content_type", SqlDbType.VarChar, 256);
cmdInsert.Parameters["@content_type"].Value = file.ContentType;
cmdInsert.Parameters.Add("@content_coding", SqlDbType.VarChar, 256);
cmdInsert.Parameters["@content_coding"].Value = DBNull.Value;
7
cmdUpdate.Parameters.Add("@content_disposition", SqlDbType.VarChar, 256);
cmdUpdate.Parameters["@content_disposition"].Value = fileName;
using (Stream uploadStream = new BufferedStream(
new SqlStreamUpload
{
InsertCommand = cmdInsert,
UpdateCommand = cmdUpdate,
InsertDataParam = cmdInsert.Parameters["@data"],
UpdateDataParam = cmdUpdate.Parameters["@data"]
}, 8040))
{
file.InputStream.CopyTo(uploadStream);
}
trn.Commit();
}
}
}
public bool GetFileByName(string fileName, out FileDownloadModel file)
{
SqlConnection conn = GetConnection();
try
{
SqlCommand cmd = new SqlCommand(
@"SELECT file_name,
content_type,
content_coding,
DATALENGTH (content) as content_length,
content
FROM media
WHERE file_name = @fileName;", conn);
SqlParameter paramFilename = new SqlParameter(
@"fileName", SqlDbType.VarChar, 256);
paramFilename.Value = fileName;
cmd.Parameters.Add(paramFilename);
SqlDataReader reader = cmd.ExecuteReader(
CommandBehavior.SequentialAccess |
CommandBehavior.SingleResult |
CommandBehavior.SingleRow |
CommandBehavior.CloseConnection);
if (false == reader.Read())
{
reader.Dispose();
conn = null;
file = null;
return false;
}
string contentDisposition = reader.GetString(0);
string contentType = reader.GetString(1);
string contentCoding = reader.IsDBNull(2) ? null : reader.GetString(2);
long contentLength = reader.GetInt64(3);
Stream content = new SqlReaderStream(reader, 4);
}
finally
{
if (null != conn)
{
conn.Dispose();
}
26-5-2019
file = new FileDownloadModel
{
FileName = contentDisposition,
ContentCoding = contentCoding,
ContentType = contentType,
ContentLength = contentLength,
Content = content
};
conn = null; // ownership transfered to the reader/stream
return true;
8
}
}
}
Normalmente, las aplicaciones MVC tienden a usar un repositorio basado en LINQ. En este caso, aunque no pude
aprovechar LINQ debido a los requisitos peculiares de la implementación de transmisión eficiente para BLOB
grandes. Por lo tanto, este Repositorio usa código SqlClient simple de vainilla.
El método GetFileByName obtiene una fila de la tabla de medios y devuelve un FileDownloadModel con un objeto
SqlReaderStream que está envolviendo el resultado del comando SELECT. Tenga en cuenta que no pude
implementar el patrón típico de "uso" para la SqlConnection desechable porque la conexión debe permanecer
abierta hasta que el comando SqlDataReader termine la transmisión en el BLOB, y la secuencia será controlada por
el marco MVC que ejecuta ActionResult después de que GetFileByName sea terminado. La conexión se cerrará
cuando se elimine el SqlReaderStream, debido a la bandera CommandBehavior.CloseConnection. La secuencia será
eliminada por el método FileStreamResult.WriteFile.
El método PostFile crea dos instrucciones SqlCommand, una para insertar la primera parte de los datos junto con
las otras columnas relevantes, y un comando de actualización que usa actualizaciones parciales de BLOB para
escribir las partes posteriores. Un objeto SqlStreamUpload está utilizando estos dos SqlCommand para transmitir
de manera eficiente en el archivo cargado. El BufferedStream intermedio se utiliza para crear fragmentos de carga
del tamaño crítico de 8040 bytes (ver más arriba). Si hubiera que comprimir el contenido, aquí es donde sucedería,
se colocaría un GZipStream frente a Bufferedstream para comprimir el archivo cargado, y el parámetro
"@content_coding" tendría que configurarse de modo "gzip”.
Almacenamiento en caché HTTP
He dejado fuera de esta implementación el control de almacenamiento en caché HTTP apropiado. El
almacenamiento en caché de HTTP es extremadamente importante en los sitios de alto volumen de tráfico, no hay
mejor manera de optimizar el procesamiento de una solicitud HTTP que nunca reciba dicha solicitud y que la
memoria caché de usuario-agente o algún proxy intermedio sirvan una respuesta. Nuestra aplicación tendría que
agregar los encabezados HTTP apropiados de ETag y Cache-Control y el MediaController tendría que tener una
acción para las solicitudes HEAD. La interfaz de IMediaRepository necesitaría un nuevo método para obtener todas
las propiedades del archivo sin el contenido real. Por el momento, dejaré esto como un ejercicio para el lector ...
Este artículo no trata de abordar la pregunta fundamental de si debe almacenar las imágenes en la base de datos o
en el sistema de archivos. Los argumentos pueden ser a favor y en contra de esto. Russel Sears, Catherine van
Ingen y Jim Gray publicaron un trabajo de investigación en 2006 sobre su análisis de comparación de rendimiento
entre el almacenamiento de archivos en la base de datos y el almacenamiento en el sistema de archivos: A BLOB or
Not a BLOB: Almacenamiento de objetos grandes en una Base de datos o Sistema de archivos ?. Llegaron a la
conclusión de que:
26-5-2019
BLOB o no BLOB
9
El estudio indica que si los objetos son más grandes que un megabyte en promedio, NTFS tiene
una clara ventaja sobre SQL Server. Si los objetos tienen menos de 256 kilobytes, la base de datos
tiene una clara ventaja. Dentro de este rango, depende de la intensidad de escritura de la carga
de trabajo y de la antigüedad de almacenamiento de una réplica típica en el sistema.
26-5-2019
Sin embargo, su estudio no comparaba cómo servir los archivos como respuestas HTTP. El hecho de que el servidor
web pueda servir un archivo de manera eficiente directamente desde el sistema de archivos sin que se ejecute
ningún código en la aplicación web cambia la ecuación bastante, e inclina el rendimiento fuertemente a favor del
sistema de archivos. Pero si ya ha considerado los pros y los contras y ha decidido que las ventajas de una copia de
seguridad / restauración consistente y una integridad referencial sólida ameritan BLOB almacenados en la base de
datos, espero que este artículo resalte una manera eficiente de devolver esos BLOB como respuestas HTTP.
10
FILESTREAM MVC: descarga y carga multimedial de SQL Server
En el artículo anterior, se ha mostrado cómo es posible usar una semántica de transmisión eficiente al descargar y
cargar imágenes desde SQL Server a través de ASP.Net MVC. En este artículo repasaremos un enfoque alternativo
que se basa en los tipos de columna FILESTREAM introducidos en SQL Server 2008.
¿Qué es FILESTREAM?
El almacenamiento FILESTREAM es una nueva opción disponible en SQL Server 2008 y versiones posteriores que
permite que las columnas BLOB se almacenen directamente en el sistema de archivos como archivos individuales.
Como archivos, los datos son accesibles a través de la API de acceso a archivos Win32 como ReadFile y WriteFile.
Pero al mismo tiempo, los mismos datos están disponibles a través de las operaciones normales de T-SQL como
SELECT o UPDATE. No solo eso, sino que los datos están contenidos de forma lógica en la base de datos, por lo que
se incluirán en una copia de seguridad de la base de datos, están sujetos a un comportamiento de confirmación y
retrotracción de transacciones ordinarios, se buscan en los índices de texto completo de SQL Server y siguen la
seguridad normal de SQL Server. Reglas de acceso: si se le otorga el permiso SELECCIONAR en la tabla, puede abrir
el archivo para leer. Hay algunas restricciones, por ejemplo. una base de datos con FILESTREAM no puede ser
duplicada. Para obtener una lista completa de restricciones y limitaciones, consulte Uso de FILESTREAM con otras
características de SQL Server. Tenga en cuenta que la edición de SQL Server Express no admite el almacenamiento
de FILESTREAM.
Otro atributo del almacenamiento de FILESTREAM es la limitación de tamaño: los valores de columna BLOB
normales tienen un tamaño máximo de 2Gb. Las columnas FILESTREAM están limitadas solo por el límite de
tamaño de volumen del sistema de archivos. 2Gb puede parecer un gran valor, pero tenga en cuenta que un
archivo multimedia como una secuencia de películas HD puede subir fácilmente a un tamaño de 5Gb.
Utilizando FILESTREAM
Una forma de utilizar las columnas FILESTREAM es tratarlas como valores BLOB ordinarios y manipularlos a través
de T-SQL. La única restricción es que no se admite la sintaxis de actualización parcial eficiente para BLOB. Es decir,
no se puede ejecutar UPDATE tabla SET columna. WRITE(…) WHERE … en una columna FILESTREAM. Pero cuando el
almacenamiento de FILESTREAM comienza a brillar es cuando se accede a través de la API del sistema de archivos.
Esto permite que la aplicación lea, escriba y busque de manera eficiente en un valor BLOB grande, tal como lo haría
en un archivo. De hecho, la aplicación sí lee, escribe y busca en un archivo :).
El requisito de proporcionar un contexto de transacción al manipular un valor de FILESTREAM a través del archivo
IO API destaca otro aspecto del trabajo con este nuevo tipo: una transacción T-SQL debe iniciarse y mantenerse
abierta mientras se manipula el archivo, y luego debe estar comprometido Si lo piensa, tal requisito es de esperar,
ya que dijimos que las columnas FILESTREAM están sujetas a la semántica normal de confirmación y retrotracción
26-5-2019
Las aplicaciones nativas de Win32 usan una nueva función de API OpenSqlFilestream que abre un HANDLE que
luego se puede usar con las funciones de la API de IO del archivo. Las aplicaciones administradas utilizan la nueva
clase SqlFileStream que expone una secuencia basada en el valor de FILESTREAM subyacente. Tanto la API nativa
como la administrada requieren como entrada dos valores especiales, un Nombre de ruta y un Contexto de
transacción que deben obtenerse previamente del servidor SQL utilizando T-SQL.
11
de transacciones de T-SQL, incluso cuando se manipulan mediante la API de lectura / escritura de archivos de
Windows.
Tabla de multimedios basada en FILESTREAM
Para tener la columna FILESTREAM, debemos tener un grupo de archivos especial en nuestra base de datos, un
grupo de archivos FILESTREAM. Puede agregar un nuevo grupo de archivos a su base de datos existente o puede
crear la base de datos con un grupo de archivos FILESTREAM desde cero. Además, la función FILESTREAM debe
estar habilitada en la instancia de SQL Server. Para todos los detalles, consulte Cómo comenzar con el
almacenamiento de FILESTREAM. Para mi proyecto, simplemente voy a crear una nueva base de datos con un
grupo de archivos FILESTREAM:
create database images
on (name='images_data', filename='c:\temp\images.mdf')
, filegroup FS contains FILESTREAM
(name = 'images_files', filename='c:\temp\images_files')
log on (name='images_log', filename='c:\temp\images.ldf');
go
La tabla de medios utilizada por nuestro proyecto MVC será similar a la utilizada en el artículo anterior, pero a la
columna de contenido se le agregará el atributo FILESTREAM. Es necesario que una tabla que tenga columnas
FILESTREAM tenga una columna ROWGUIDCOL, por lo que también agregaremos una de ellas:
create table media (
[media_id] int not null identity(1,1),
[file_name] varchar(256),
[content_type] varchar(256),
[content_coding] varchar(256),
[media_rowguid] uniqueidentifier not null
ROWGUIDCOL UNIQUE default newsequentialid(),
[content] varbinary(max) filestream,
constraint pk_media_id primary key([media_id]),
constraint unique_file_name unique ([file_name]));
go
Repositorio basado en FILESTREAM
/// <summary>
/// SQL Server FILESTREAM based implementation of the IMediaRepository
/// </summary>
public class FileStreamMediaRepository: IMediaRepository
{
/// <summary>
/// Gets an open connection to the SQL Server back end
/// </summary>
/// <returns>the SqlConneciton object, ready to use</returns>
26-5-2019
Si aún no ha leído el artículo anterior Descargar y cargar imágenes de SQL Server a través de ASP.Net MVC, ahora es
un buen momento para hacerlo. Vamos a reutilizar el mismo código y simplemente proporcionaremos una nueva
implementación para la interfaz IMediaRepository, una implementación que funciona con el almacenamiento de
FILESTREAM:
12
private SqlConnection GetConnection()
{
SqlConnectionStringBuilder scsb = new SqlConnectionStringBuilder(
ConfigurationManager.ConnectionStrings["Images"].ConnectionString);
scsb.Pooling = true;
scsb.AsynchronousProcessing = true;
SqlConnection conn = new SqlConnection(scsb.ConnectionString);
conn.Open();
return conn;
}
/// <summary>
/// Gets a file from the SQL repository
/// </summary>
/// <param name="fileName">filename to retrieve</param>
/// <param name="file">Output, the model for the file if found</param>
/// <returns>True if the file is found, False if not</returns>
public bool GetFileByName(string fileName, out FileDownloadModel file)
{
SqlConnection conn = GetConnection();
SqlTransaction trn = conn.BeginTransaction();
try
{
SqlCommand cmd = new SqlCommand(
@"SELECT file_name,
content_type,
content_coding,
DATALENGTH (content) as content_length,
content.PathName() as path,
GET_FILESTREAM_TRANSACTION_CONTEXT ()
FROM media
WHERE file_name = @fileName;", conn, trn);
SqlParameter paramFilename = new SqlParameter(
@"fileName", SqlDbType.VarChar, 256);
paramFilename.Value = fileName;
cmd.Parameters.Add(paramFilename);
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (false == reader.Read())
{
reader.Close();
trn.Dispose();
conn.Dispose();
trn = null;
conn = null;
file = null;
return false;
}
file = new FileDownloadModel
{
FileName = contentDisposition,
ContentCoding = contentCoding,
ContentType = contentType,
ContentLength = contentLength,
Content = new MvcResultSqlFileStream
{
SqlStream = new SqlFileStream(path, context, FileAccess.Read),
Connection = conn,
Transaction = trn
}
};
conn = null; // ownership transfered to the stream
trn = null;
26-5-2019
string contentDisposition = reader.GetString(0);
string contentType = reader.GetString(1);
string contentCoding = reader.IsDBNull(2) ? null: reader.GetString(2);
long contentLength = reader.GetInt64(3);
string path = reader.GetString(4);
byte[] context = reader.GetSqlBytes(5).Buffer;
13
return true;
}
}
finally
{
if (null != trn)
{
trn.Dispose();
}
if (null != conn)
{
conn.Dispose();
}
}
}
/// <summary>
/// Adds a file to the SQL repository
/// </summary>
/// <param name="file">POST-ed file to be added</param>
/// <param name="fileName">The filename part of the uploaded file
public void PostFile(HttpPostedFileBase file, out string fileName)
{
fileName = Path.GetFileName(file.FileName);
path</param>
using (SqlConnection conn = GetConnection ())
{
using (SqlTransaction trn = conn.BeginTransaction ())
{
SqlCommand cmdInsert = new SqlCommand(
@"insert into media
(file_name, content_type, content_coding, content)
output
INSERTED.content.PathName(),
GET_FILESTREAM_TRANSACTION_CONTEXT ()
values
(@content_disposition, @content_type, @content_coding, 0x)", conn, trn);
cmdInsert.Parameters.Add("@content_disposition", SqlDbType.VarChar, 256);
cmdInsert.Parameters["@content_disposition"].Value = fileName;
cmdInsert.Parameters.Add("@content_type", SqlDbType.VarChar, 256);
cmdInsert.Parameters["@content_type"].Value = file.ContentType;
cmdInsert.Parameters.Add("@content_coding", SqlDbType.VarChar, 256);
cmdInsert.Parameters["@content_coding"].Value = DBNull.Value;
string path = null;
byte[] context = null;
// cmdInsert is an INSERT command that uses the OUTPUT clause
// Thus we use the ExecuteReader to get the
// result set from the output columns
//
using (SqlDataReader rdr = cmdInsert.ExecuteReader())
{
rdr.Read();
path = rdr.GetString(0);
context = rdr.GetSqlBytes(1).Buffer;
}
using (SqlFileStream sfs = new SqlFileStream(
path, context, FileAccess.Write))
{
file.InputStream.CopyTo(sfs);
}
trn.Commit ();
}
}
}
26-5-2019
}
14
La implementación es bastante sencilla. El método PostFile inicia una transacción, inserta una fila en la tabla de
imágenes y obtiene el contexto de transacción PahtName y FILESTREAM en la fila recién insertada, y luego abre un
SqlFileStream y copia en el archivo cargado directamente en el archivo que respalda la columna FILESTREAM. El
método GetFileByName obtiene el contexto de transacción PathName y FILESTREAM del archivo deseado y luego
devuelve un SqlFileStream que accede directamente al archivo en el que se almacena el valor de FILESTREAM.
Lo único que requiere explicación es la clase MvcResultSqlFileStream. Debido a que SqlFileStream usa un contexto
de transacción, se requiere que mantenga SqlConnection y SqlTransaction abiertos hasta que SqlFileStream
termine de acceder al contenido. En el método PostFile, este requisito se logra fácilmente porque todo el uso de
SqlFileStream se completa dentro del marco de la pila, pero el método GetFileByName tiene que devolver un
Stream que a su vez se pasa a un FileStreamResult por la clase MediaController y no hay control sobre cómo El
marco MVC utilizará este ActionResult devuelto. Mi solución es envolver el SqlFileStream en una nueva clase
derivada de Stream (ya que SqlFileStream está sellado, no se puede heredar ...) y luego manejar la administración
de recursos en esta clase derivada ’
/// <summary>
/// Implementation of System.IO.Stream based on a SqlFileStream
/// Disposes the connection, transaction and SqlFileStream objects
/// </summary>
public class MvcResultSqlFileStream: Stream
{
public SqlFileStream SqlStream { get; set; }
public SqlConnection Connection { get; set; }
public SqlTransaction Transaction { get; set; }
public override bool CanRead
{
get { return SqlStream.CanRead; }
}
public override bool CanSeek
{
get { return SqlStream.CanSeek; }
}
public override bool CanWrite
{
get { return SqlStream.CanWrite; }
}
public override void Flush()
{
SqlStream.Flush ();
}
public override long Position
{
get
{
return SqlStream.Position;
}
set
{
SqlStream.Position = value;
}
}
public override int Read(byte[] buffer, int offset, int count)
{
26-5-2019
public override long Length
{
get { return SqlStream.Length; }
}
15
return SqlStream.Read (buffer, offset, count);
}
public override long Seek(long offset, SeekOrigin origin)
{
return SqlStream.Seek(offset, origin);
}
public override void SetLength(long value)
{
SqlStream.SetLength(value);
}
public override void Write(byte[] buffer, int offset, int count)
{
SqlStream.Write(buffer, offset, count);
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
SqlStream.Dispose();
Transaction.Dispose();
Connection.Dispose();
}
base.Dispose(disposing);
}
}
Esta clase adicional se siente más bien innecesaria, pero desafortunadamente es necesaria porque el marco MVC
no tiene un manejo adecuado de los recursos que pasan del controlador. Si nos preguntara, ActionResult debería
implementar IDisposable, pero, por supuesto, nadie nos ha preguntado 😉
Conclusión
El manejo del contenido multimedia almacenado en la base de datos mediante el almacenamiento de FILESTREAM
hace que la experiencia del desarrollador sea más agradable. Se puede acceder al contenido de FILESTREAM
utilizando la semántica de transmisión basada en la clase SqlFileStream o utilizando la API de E / S del archivo
Win32 para aplicaciones nativas. En comparación, el artículo anterior que se basó en T-SQL directo para manipular
los valores BLOB tuvo que hacer algunos trucos muy poco intuitivos para lograr la semántica de transmisión,
especialmente para cargar un archivo. El almacenamiento FILESTREAM simplifica el desarrollo y es compatible con
todas las ediciones de SQL Server, incluido Express.
Si tiene la intención de implementar FILESTREAM en su entorno, se recomienda que revise primero algunos
artículos de la Base de conocimientos y artículos técnicos:
FILESTREAM Storage in SQL Server 2008
How to: Use FILESTREAM in a Firewall-Protected Environment
Recommendations and guidelines for improving SQL Server FileStream performance
26-5-2019



16
FILESTREAM permite a las aplicaciones basadas en SQL Server almacenar datos no estructurados, como
documentos e imágenes, en el sistema de archivos. FILESTREAM habilita a las aplicaciones basadas en SQL Server
almacenar datos no estructurados, tales como documentos e imágenes, en el sistema de archivos. Las aplicaciones
pueden aprovechar las API de transmisión de datos enriquecidas y el rendimiento del sistema de archivos al mismo
tiempo que mantienen la coherencia transaccional entre los datos no estructurados y los datos estructurados
correspondientes.
FILESTREAM integra Motor de base de datos de SQL Server con sistemas de archivos NTFS o ReFS almacenando
datos de objetos binarios grandes (BLOB) varbinary(max) como archivos en el sistema de archivos. Transact-SQL
puede insertar, actualizar, consultar, buscar y realizar copias de seguridad de los datos FILESTREAM. Las interfaces
del sistema de archivos de Win32 proporcionan el acceso de la transmisión por secuencias a los datos.
FILESTREAM usa la memoria caché del sistema NT para almacenar en memoria caché los datos de archivos. Esto
ayuda a reducir cualquier efecto que los datos FILESTREAM podrían tener en el rendimiento de motor de base de
datos. No se usa el grupo de búferes de SQL Server; por consiguiente, esta memoria está disponible para el
procesamiento de consultas.
FILESTREAM no se habilita automáticamente al instalar o actualizar SQL Server. Debe habilitar FILESTREAM
utilizando el Administrador de configuración de SQL Server y SQL Server Management Studio. Para utilizar
FILESTREAM, debe crear o modificar una base de datos que contenga un tipo especial de grupo de archivos. Luego,
debe crear o modificar una tabla de modo que contenga una columna varbinary(max) con el atributo FILESTREAM.
Después de completar estas tareas, puede usar Transact-SQL y Win32 para administrar los datos FILESTREAM.
Cuando se usa FILESTREAM
En SQL Server, los BLOB pueden ser datos de varbinary(max) estándar que almacena los datos en tablas u objetos
FILESTREAM varbinary(max) que almacenan los datos en el sistema de archivos. El tamaño y el uso de los datos
determinan si debería usar el almacenamiento de base de datos o el almacenamiento del sistema de archivos. Si las
condiciones siguientes son verdaderas, debería pensar en usar FILESTREAM:



Los objetos que se están almacenando son, por término medio, mayores de 1 MB.
El acceso de lectura rápido es importante.
Está desarrollando aplicaciones que usan un nivel intermedio para la lógica de la aplicación.
Para objetos de menor tamaño, el almacenamiento de BLOB varbinary(max) en la base de datos a menudo
proporciona un mejor rendimiento de la transmisión de datos.
Almacenamiento de FILESTREAM
Para especificar que una columna debería almacenar datos en el sistema de archivos, especifique el atributo
FILESTREAM en una columna varbinary(max). Esto hace que motor de base de datos almacene todos los datos para
esa columna en el sistema de archivos pero no en el archivo de base de datos.
26-5-2019
El almacenamiento de FILESTREAM se implementa como una columna varbinary(max) en la que los datos están
almacenados como BLOB en el sistema de archivos. Los tamaños de los BLOB están limitados solo por el tamaño del
volumen del sistema de archivos. La limitación varbinary(max) estándar de tamaños de archivo de 2 GB no se aplica
a BLOB que están almacenados en el sistema de archivos.
17
Los datos de FILESTREAM deben estar almacenados en los grupos de archivos FILESTREAM. Un grupo de archivos
FILESTREAM es un grupo de archivos especial que contiene los directorios de sistema de archivos en lugar de los
propios archivos. Estos directorios del sistema de archivos se denominan contenedores de datos. Los contenedores
de datos son la interfaz entre el almacenamiento del motor de base de datos y el almacenamiento del sistema de
archivos.
Cuando use el almacenamiento FILESTREAM, piense en lo siguiente:





Cuando una tabla contiene una columna FILESTREAM, cada fila debe tener un identificador de fila único
distinto de NULL.
Se pueden agregar varios contenedores de datos a un grupo de archivos FILESTREAM.
Los contenedores de datos FILESTREAM no pueden estar anidados.
Cuando se usan clústeres de conmutación por error, los grupos de archivos FILESTREAM deben estar en
recursos de disco compartido.
Los grupos de archivos FILESTREAM pueden estar en volúmenes comprimidos.
Administración integrada
Debido a que FILESTREAM se implementa como columna varbinary(max) y se integra directamente en el motor de
base de datos, la mayoría de las funciones y de las herramientas de administración de SQL Server funcionan sin la
modificación de los datos FILESTREAM. Por ejemplo, puede usar todos los modelos de recuperación y copia de
seguridad con datos FILESTREAM y se realizan copias de seguridad de los datos FILESTREAM con los datos
estructurados de la base de datos. Si no desea realizar una copia de seguridad de los datos FILESTREAM con datos
relacionales, puede usar una copia de seguridad parcial para excluir los grupos de archivos FILESTREAM.
Seguridad integrada
En SQL Server, los datos de FILESTREAM se protegen de la misma manera que los demás datos: concediendo
permisos en el nivel de tabla o columna. Si un usuario tiene permiso para la columna FILESTREAM de una tabla, el
usuario puede abrir los archivos asociados.
Nota
El cifrado no se admite en los datos FILESTREAM.
Solo la cuenta con la que la cuenta de servicio de SQL Server se ejecuta permiten los permisos al contenedor
FILESTREAM. Recomendamos que no se concedan permisos a ninguna otra cuenta en el contenedor de datos.
Nota
26-5-2019
Los inicios de sesión de SQL no funcionarán con contenedores FILESTREAM.SQL Solo la autenticación NTFS o ReFS
funcionará con contenedores FILESTREAM.
18
Acceso a datos BLOB con Transact-SQL y acceso de transmisión de datos del sistema de
archivos
Después de almacenar los datos en una columna FILESTREAM, puede tener acceso a los archivos usando las
transacciones de Transact-SQL o usando las API de Win32.
Acceso a Transact-SQL
Usando Transact-SQL, puede insertar, actualizar y eliminar los datos de FILESTREAM:
Puede usar una operación de inserción para rellenar previamente un campo FILESTREAM con un valor nulo, un
valor vacío o un dato insertado relativamente corto. Sin embargo, se envía una gran cantidad de datos de manera
más eficaz en un archivo que usa interfaces de Win32.
Al actualizar un campo FILESTREAM, modifica los datos de BLOB subyacentes en el sistema de archivos. Cuando un
campo FILESTREAM está establecido en NULL, se eliminan los datos de BLOB asociados al campo. No puede usar
ninguna actualización fragmentada de Transact-SQL, implementada como UPDATE. Write(), para realizar
actualizaciones parciales en los datos.
Al eliminar una fila, o eliminar o truncar una tabla que contiene datos FILESTREAM, elimina los datos de BLOB
subyacentes del sistema de archivos.
Acceso a la transmisión por secuencias del sistema de archivos
La compatibilidad de transmisión por secuencias de Win32 funciona en el contexto de una transacción de SQL
Server. Dentro de una transacción, puede usar las funciones FILESTREAM para obtener una ruta de acceso al
sistema de archivos de UNC lógica de un archivo. Tras ello, use la API OpenSqlFilestream para obtener un
identificador de archivos. Después, este identificador lo pueden usar las interfaces de transmisión por secuencias
de archivo de Win32, como ReadFile() y WriteFile(), para obtener acceso y actualizar el archivo a través del sistema
de archivos.
Dado que las operaciones de archivo son transaccionales, no puede eliminar ni cambiar el nombre de los archivos
FILESTREAM a través del sistema de archivos.
Modelo de la instrucción
El acceso del sistema de archivos de FILESTREAM modela una instrucción de Transact-SQL usando la apertura y el
cierre de archivo. La instrucción se inicia cuando un identificador de archivos se abre y finaliza cuando se cierra el
identificador. Por ejemplo, cuando se cierra un identificador de escritura, cualquier posible desencadenador de
AFTER que esté registrado en la tabla se desencadena como si la instrucción UPDATE estuviera
En FILESTREAM, el motor de base de datos controla el espacio de nombres del sistema de archivos físico de BLOB.
Una nueva función intrínseca, PathName, proporciona la ruta UNC lógica del BLOB que se corresponde con cada
celda de FILESTREAM de la tabla. La aplicación usa esta ruta de acceso lógica para obtener el identificador de
Win32 y funcionar en los datos de BLOB usando las interfaces del sistema de archivos de Win32 normales. La
función devuelve NULL si el valor de la columna FILESTREAM es NULL.
26-5-2019
Espacio de nombres de almacenamiento
19
Acceso al sistema de archivos transaccionales
Una nueva función intrínseca, GET_FILESTREAM_TRANSACTION_CONTEXT(), proporciona el token que representa
la transacción actual a la que la sesión está asociada. Se debe haber iniciado la transacción y no haberse anulado ni
confirmado todavía. Al obtener un token, la aplicación enlaza las operaciones de transmisión por secuencias del
sistema de archivos FILESTREAM con una transacción iniciada. La función devuelve NULL en caso de no haber
ninguna transacción explícitamente iniciada.
Se deben cerrar todos los identificadores de archivo antes de que la transacción se confirme o se anule. Si un
identificador se deja abierto más allá del ámbito de transacción, las lecturas adicionales frente al identificador
producirán un error; las escrituras adicionales frente al identificador tendrán éxito pero los datos reales no se
escribirán en el disco. De igual forma, si la base de datos o la instancia del Motor de base de datos se cierra, se
invalidan todos los identificadores abiertos.
Durabilidad transaccional
Con FILESTREAM, al confirmar la transacción, el Motor de base de datos asegura la durabilidad de la transacción
para los datos de BLOB FILESTREAM que se modifican del acceso a la transmisión por secuencias del sistema de
archivos.
Semántica de aislamiento
La semántica de aislamiento se rige por los niveles de aislamiento de transacción del Motor de base de datos. Se
admite el nivel de aislamiento de lectura confirmada para Transact-SQLTransact-SQL y el acceso al sistema de
archivos. Se admiten operaciones de lectura repetibles, así como serializables y aislamientos de instantáneas. No se
admite la lectura de datos sucios.
Las operaciones de apertura de acceso al sistema de archivos no esperan ningún bloqueo. En su lugar, se produce
un error inmediato de las operaciones de apertura si no pueden obtener acceso a los datos debido al aislamiento
de transacción. Se produce un error en las llamadas de API de transmisión por secuencias con
ERROR_SHARING_VIOLATION si la operación de apertura no puede continuar debido a la infracción de aislamiento.
Para permitir que se realicen actualizaciones parciales, la aplicación puede emitir un control FS de dispositivo
(FSCTL_SQL_FILESTREAM_FETCH_OLD_CONTENT) para capturar el contenido anterior en el archivo al que hace
referencia el identificador abierto. Esto desencadenará una copia de contenido antiguo de servidor. Para un mejor
rendimiento de la aplicación, y para evitar encontrarse con posibles tiempos de espera mientras trabaja con
archivos muy grandes, recomendamos que use E/S asincrónica.
Si se emite FSCTL una vez que se haya escrito en el identificador, se conservará la última operación de escritura y se
perderán las escrituras anteriores realizadas en el identificador.
Cuando una API del sistema de archivos no puede abrir un archivo a causa de una infracción de aislamiento, se
devuelve una excepción ERROR_SHARING_VIOLATION. Esta infracción de aislamiento se produce cuando dos
transacciones intentan acceder al mismo archivo. El resultado de la operación de acceso depende del modo en el
que se abrió el archivo y de la versión de SQL Server en la que se ejecute la transacción. En la tabla siguiente se
explican resumidamente los posibles resultados de dos transacciones que están accediendo al mismo archivo.
26-5-2019
API del sistema de archivos y niveles de aislamiento admitidos
20
Transacción 2 Resultado en SQL Server 2008
Resultado en SQL Server 2008 R2 y versiones
posteriores
Abrir para
lectura.
Abrir para
lectura.
Ambas son correctas.
Ambas son correctas.
Abrir para
lectura.
Abrir para
escritura.
Ambas son correctas. Las operaciones de escritura
de la transacción 2 no influyen en las operaciones
de lectura realizadas en la transacción 1.
Ambas son correctas. Las operaciones de escritura
de la transacción 2 no influyen en las operaciones
de lectura realizadas en la transacción 1.
Abrir para
escritura.
Abrir para
lectura.
Se produce un error en la operación de apertura de
la transacción 2 con una excepción
Ambas son correctas.
ERROR_SHARING_VIOLATION.
Abrir para
escritura.
Abrir para
escritura.
Se produce un error en la operación de apertura de Se produce un error en la operación de apertura de
la transacción 2 con una excepción
la transacción 2 con una excepción
ERROR_SHARING_VIOLATION.
ERROR_SHARING_VIOLATION.
Abrir para
lectura.
Abrir para
SELECT.
Ambas son correctas.
Ambas son correctas.
Abrir para
lectura.
Abrir para
UPDATE o
DELETE.
Ambas son correctas. Las operaciones de escritura
de la transacción 2 no influyen en las operaciones
de lectura realizadas en la transacción 1.
Ambas son correctas. Las operaciones de escritura
de la transacción 2 no influyen en las operaciones
de lectura realizadas en la transacción 1.
Abrir para
escritura.
Abrir para
SELECT.
La transacción 2 se bloquea hasta que la transacción
1 se confirme o finalice la transacción. O bien, se
Ambas son correctas.
agota el tiempo de espera de bloqueo de la
transacción.
Abrir para
escritura.
Abrir para
UPDATE o
DELETE.
La transacción 2 se bloquea hasta que la transacción
1 se confirme o finalice la transacción. O bien, se
agota el tiempo de espera de bloqueo de la
transacción.
La transacción 2 se bloquea hasta que la transacción
1 se confirme o finalice la transacción. O bien, se
agota el tiempo de espera de bloqueo de la
transacción.
Abrir para
SELECT.
Abrir para
lectura.
Ambas son correctas.
Ambas son correctas.
Abrir para
SELECT.
Abrir para
escritura.
Ambas son correctas. Las operaciones de escritura
de la transacción 2 no influyen en la transacción 1.
Ambas son correctas. Las operaciones de escritura
de la transacción 2 no influyen en la transacción 1.
Abrir para
UPDATE o
DELETE.
Abrir para
lectura.
Se produce un error en la operación de apertura de
la transacción 2 con una excepción
Ambas son correctas.
ERROR_SHARING_VIOLATION.
Abrir para
UPDATE o
DELETE.
Abrir para
escritura.
Se produce un error en la operación de apertura de Se produce un error en la operación de apertura de
la transacción 2 con una excepción
la transacción 2 con una excepción
ERROR_SHARING_VIOLATION.
ERROR_SHARING_VIOLATION.
Abrir para
SELECT con
REPEATABLE
READ.
Abrir para
lectura.
Ambas son correctas.
Ambas son correctas.
26-5-2019
Transacción 1
21
Transacción 1
Transacción 2 Resultado en SQL Server 2008
Abrir para
SELECT con
REPEATABLE
READ.
Abrir para
escritura.
Resultado en SQL Server 2008 R2 y versiones
posteriores
Se produce un error en la operación de apertura de Se produce un error en la operación de apertura de
la transacción 2 con una excepción
la transacción 2 con una excepción
ERROR_SHARING_VIOLATION.
ERROR_SHARING_VIOLATION.
Escritura continua desde clientes remotos
El acceso del sistema de archivos remoto a los datos FILESTREAM está habilitado por el protocolo Bloque de
mensajes de servidor (SMB). Si el cliente es remoto, no se almacena en caché ninguna operación de escritura del
lado cliente. Las operaciones de escritura siempre se enviarán al servidor. Los datos pueden se pueden almacenar
en memoria caché en el servidor. Recomendamos que las aplicaciones que se están ejecutando en clientes remotos
consoliden pequeñas operaciones de escritura para realizar menos operaciones de escritura mediante un tamaño
de datos mayor.
No se admite la creación de vistas asignadas de memoria (E/S asignada de memoria) usando un identificador
FILESTREAM. Si la asignación de memoria se usa para los datos FILESTREAM, el motor de base de datos no puede
garantizar la coherencia y la durabilidad de los datos o la integridad de la base de datos.
Por defecto, ASP.NET permite que la carga del tamaño máximo de archivo sea de 4 MB. Si utiliza una carga de
archivos ASP.NET de terceros para cargar archivos grandes y recibe una notificación de que la carga ha fallado
debido al límite máximo de tamaño de archivo, el componente correspondiente no admite la carga de archivos
grandes.
Para anular la restricción del tamaño máximo de archivo, debe modificar el archivo web.config y habilitar el
proceso de carga de archivos para manejar archivos más grandes. Con RadAsyncUpload (Telerik) puede subir
rápidamente archivos de más de 4 MB. RadAsyncUpload lo ayuda a superar la limitación de carga de 4 MB en
ASP.NET dividiendo los archivos grandes en trozos más pequeños y subiéndolos posteriormente. Puede controlar el
tamaño de los trozos y, por lo tanto, la cantidad de solicitudes al servidor necesarias para cargar el archivo, lo que
puede mejorar el rendimiento de su aplicación.
Use las Herramientas de desarrollo de su navegador para monitorear el cambio en el número de solicitudes al
servidor, cuando cambia el tamaño de los fragmentos, AsyncUpload divide los archivos grandes en fragmentos más
26-5-2019
pequeños para superar la limitación de ASP.NET de 4MB.
22
Vea la demostración para ver cómo el control de carga de ASP.NET de Telerik le permite cargar fácilmente archivos
grandes en ASP.NET sin modificar el valor en el archivo web.config. Nuestra documentación en línea proporciona
más detalles sobre cómo cargar archivos grandes con RadAsyncUpload.
Tareas Relacionadas










Habilitar y configurar FILESTREAM
Crear una base de datos habilitada para FILESTREAM
Crear una tabla para almacenar datos FILESTREAM
Obtener acceso a datos FILESTREAM con Transact-SQL Crear aplicaciones cliente para datos FILESTREAM
Obtener acceso a los datos FILESTREAM con OpenSqlFilestream
Realizar actualizaciones parciales de los datos FILESTREAM
Evitar conflictos con operaciones de base de datos en aplicaciones FILESTREAM
Mover una base de datos habilitada para FILESTREAM
Configurar FILESTREAM en un clúster de conmutación por error
Configurar un Firewall para el acceso de FILESTREAM
Contenido relacionado
Compatibilidad de FILESTREAM con otras características de SQL Server
Vistas de administración dinámica de secuencia de archivo y FileTable (Transact-SQL)
Vistas de catálogo de secuencia de archivo y FileTable (Transact-SQL)
Procedimientos almacenados del sistema de Filestream y FileTable (Transact-SQL)
26-5-2019




23
PRINCIPIO DE RESPONSABILIDAD UNICA (SINGLE RESPONSIBILITY)
PRINCIPIO OPEN/CLOSED
PRINCIPIO DE SUSTITUCION DE LISKOV (LISKOV SUBSTITUTION)
PRINCIPIO DE SEGREGACION DE INTERFACES (INTERFACE SEGREGATION)
PRINCIPIO DE INVERSION DE DEPENDENCIAS (DEPENDENCY INVERSION)
26-5-2019
REFACTORIZAR
CLASE BASE
CLASE DERIVADA
CLASE ABSTRACTA
CLASE SELLADA
METODO ABSTRACTO
ALTA COHESION
BAJO ACOPLAMIENTO
INYECCION DE DEPENDENCIA
SERVICE LOCATOR
HERENCIA
POLIMORFISMO
INMUTABILIDAD
INSTANCIA
24
En DevExpress ofrecemos dos bibliotecas principales que admiten el
marco MVC ASP.NET de Microsoft:
Controles de DevExpress ASP.NET MVC
DevExtreme MVC Controls
En esta publicación, nos sumergiremos en las dos bibliotecas
aparentemente similares que ofrecen controles para ASP.NET MVC. Veremos
las diferencias, similitudes, ventajas, desventajas y consecuencias.
Comencemos observando el desarrollo web del lado del servidor y del
lado del cliente, porque esta elección será fundamental en qué
biblioteca funcionará para usted.
Una de las preocupaciones más importantes para la elección de una
plataforma de desarrollo web es la arquitectura: la representación
tradicional de la interfaz de usuario del lado del servidor o del lado
del cliente. Es importante porque tiene muchas consecuencias.
Recomiendo leer esta sección, pero para aquellos que no tienen tiempo,
aquí hay un breve tldr:
26-5-2019
Elecciones arquitectónicas y consecuencias
25
La elección de la interfaz de usuario representada de servidor
contra cliente influye en la arquitectura general de un sistema de
software hasta el punto de que es recomendable analizar este asunto
antes de tomar decisiones sobre bibliotecas de componentes
específicos.
Lado del servidor
Si usa la IU representada por el servidor, esto implica que necesita un
servidor lo suficientemente potente como para representar las páginas
dinámicamente (por solicitud). Por lo general, esto funciona utilizando
archivos de plantilla en el servidor que describen las páginas, con un
modelo de extensión para insertar el contenido dinámico. Ambos marcos
usan este enfoque con ASP.NET WebForms que usan páginas ASPX y ASP.NET
MVC que usa archivos Razor como plantillas.
Si bien los modelos de componentes son diferentes entre WebForms y MVC,
ambos se representan en el entorno .NET del lado del servidor. Esto
significa que se pueden usar los métodos de acceso a datos basados en
.NET, la lógica de negocios escrita en C# y el estado en el lado del
servidor. Incluso la representación dinámica del navegador o específica
del usuario se puede realizar en el servidor, quizás dependiendo de la
configuración regional del usuario o los permisos consultados desde un
sistema de seguridad.
También hay una brecha para ser superada con este modelo: por ejemplo,
podría ser necesario escribir ciertos fragmentos de código dos veces,
en C# y JavaScript, como los requeridos para realizar tareas de
26-5-2019
Este modelo permite a los programadores que se sientan como en casa en
el entorno del servidor. Sin embargo, puede ser costoso con ASP.NET
WebForms debido a los numerosos viajes de ida y vuelta del servidor
requeridos para propósitos de representación. También hace que sea casi
imposible implementar la aplicación web resultante en entornos donde un
servidor podría no estar disponible, como las aplicaciones de
escritorio basadas en Electron o las aplicaciones móviles basadas en
Cordova. Se requiere mucho cuidado para evitar problemas de
escalabilidad porque los recursos del servidor son preciosos. Además,
no siempre es fácil agregar infraestructura, por ejemplo, cuando la
administración de estado del lado del servidor no se ha estructurado
correctamente.
26
validación de datos tanto para comentarios inmediatos del usuario en el
lado del cliente como para fines de lógica de negocios del lado del
servidor.
Sin embargo, si está creando un sitio web público, la representación
del lado del servidor es ideal para SEO (optimización de motores de
búsqueda). Debido a que el contenido HTML se genera en el servidor, los
motores de búsqueda como Google pueden rastrearlo, indexarlo y
almacenarlo en caché. La representación del lado del cliente no puede
hacer esto fácilmente sin trabajo adicional.
Lado del cliente
Con la IU representada por el cliente, un servidor no es estrictamente
necesario. El contenido HTML y JavaScript se abre camino en el cliente
de alguna manera: se puede descargar como contenido estático desde un
servidor, pero también se puede implementar como un paquete de
instalación para destinos de escritorio o móviles.
Los componentes utilizados por el cliente para representar la IU
compleja se escriben en JavaScript (o un lenguaje que se compila con
JavaScript, como TypeScript), y los desarrolladores generalmente eligen
los marcos de aplicación como Angular o React que los ayudan a
estructurar la aplicación del lado del cliente, pero también Requieren
integración con bibliotecas de componentes. Este modelo es una
reminiscencia de las aplicaciones cliente/servidor simples, y solo
utiliza protocolos web estándar para la comunicación de servicio y el
acceso a datos. Es posible crear sistemas de aplicación basados en
JavaScript de pila completa si los servicios están escritos en
JavaScript (probablemente para Node.js), y debido a la naturaleza sin
estado inherente de los servicios, generalmente es fácil escalar los
servidores según sea necesario. En general, los requisitos de
infraestructura del lado del servidor son menos complejos con esta
arquitectura, por lo que las implementaciones en la nube son fáciles y
permiten configuraciones de escalado automáticas.
26-5-2019
Debido a que el navegador del cliente ahora manejará el HTML y la
representación de esos elementos de la interfaz de usuario, la
aplicación del lado del cliente en su mayoría necesita los datos del
servidor. Por lo general, deberá crear servicios web para proporcionar
estos datos. Se puede utilizar la WebApi de ASP.NET MVC para
implementar dichos servicios, lo que nuevamente brinda al desarrollador
la oportunidad de usar mecanismos de acceso a datos basados en .NET,
pero se requieren protocolos adicionales, por ejemplo, para consultar
datos dinámicamente desde los servicios y transferirlos de vuelta al
cliente.
27
La IU representada por el cliente es el modelo arquitectónico de
elección para la mayoría de las aplicaciones web / móviles modernas y
un número creciente de aplicaciones de escritorio, que incluyen Office
365, Skype y Microsoft Visual Studio Code. La mejor recomendación para
tomar esta decisión es sopesar los pros y los contras cuidadosamente,
ya que se aplican a sus planes, la experiencia de los desarrolladores
en su equipo, su base de clientes y otros factores.
Esto nos lleva a la gran diferencia entre nuestros dos controles de
MVC. Mientras que ambos son controles de ASP.NET MVC del lado del
servidor, los Controles de MVC de DevExtreme representarán la IU del
lado del cliente debido a que envuelven los controles de JavaScript de
DevExtreme del lado del cliente en los controles de ASP.NET MVC. Por
otro lado, los controles DevExpress ASP.NET MVC representarán el HTML
del lado del servidor y luego lo entregarán al cliente.
Veamos ahora cómo estas dos bibliotecas comenzaron a comprender
nuestras motivaciones para crearlas.
Historia
1. DevExpress ASP.NET MVC Controls
En abril de 2009, Microsoft lanzó ASP.NET MVC y muchos desarrolladores
se interesaron rápidamente en este nuevo e interesante marco web.
Muchos de nuestros clientes solicitaron que proporcionáramos controles
de UI para ASP.NET MVC.
En ese momento, nuestra biblioteca ASP.NET WebForms proporcionaba
controles sólidos, estables y ricos en funciones. Así que tomamos la
decisión de tomar la API, las características y la representación de
nuestros controles de formularios web de ASP.NET y proporcionarlos como
controles MVC nativos. Este enfoque tuvo la ventaja de ahorrar tiempo y
ofrecerle un conjunto sólido de controles con muchas funciones.
Casi al mismo tiempo, JavaScript también estaba ganando popularidad
entre los desarrolladores web...
26-5-2019
En 2010, presentamos los controles DevExpress ASP.NET MVC como beta con
solo cinco controles. Luego, durante los siguientes siete años, con
cada lanzamiento continuamos aumentando los controles, las funciones y
el rendimiento. Hoy en día, el conjunto de controles DevExpress MVC es
sólido y tiene más de 65 controles.
28
2. Controles DevExtreme MVC
Desde finales de la década de 2000, la popularidad de JavaScript ha
seguido aumentando. Y hace muchos años, el desarrollo web cambió
decididamente hacia tecnologías basadas en JavaScript del lado del
cliente como jQuery, Node.js, Angular, Backbone, React, etc. sin razón,
porque el modelo de desarrollo del lado del cliente ofrece beneficios.
Muchos de nuestros clientes web existentes nos pidieron que
proporcionáramos controles de JavaScript que se representaban
exclusivamente en el lado del cliente.
Por lo tanto, en 2011, asignamos a un grupo independiente de nuestros
desarrolladores la tarea de crear un conjunto de controles de
JavaScript totalmente del lado del cliente que tuvieran características
similares a las de nuestros controles del lado del servidor. Después de
un año de desarrollo, la biblioteca DevExtreme nació en 2012. Hemos
continuado desarrollando esta biblioteca del lado del cliente desde
entonces para proporcionar más controles, características, estabilidad
y soporte para otros marcos del lado del cliente. Luego, alrededor de
2014, algunos de nuestros clientes web expresaron la necesidad de un
conjunto de controles MVC que utilizaron el enfoque de representación
del cliente. Así que decidimos envolver los controles de JavaScript
DevExtreme del lado del cliente como controles MVC del lado del
servidor.
En junio de 2016, los controles DevExtreme MVC se introdujeron como un
avance tecnológico comunitario (CTP). Ahora, después de varias
versiones en las que el equipo ha trabajado duro para mejorar los
controles DevExtreme MVC, proporcionan más de 65 controles,
características y una gran integración con ASP.NET MVC (y ASP.NET
Core).
¿Qué es lo correcto para ti?
Si bien las consideraciones arquitectónicas deberían ayudar a impulsar
la mayoría de los proyectos en su dirección preferida, también es
posible comparar nuestras bibliotecas en base a méritos técnicos
específicos. Es posible que la siguiente descripción general no sea
exhaustiva. No dude en preguntar sobre los temas que le faltan y
ampliaremos la publicación.
¿Qué recomiendas para mi escenario?
¿Cuáles son las principales diferencias entre los dos productos?
26-5-2019
Recientemente, hemos recibido preguntas de desarrolladores que
preguntan:
29
Para ayudarlo a decidir, lea las siguientes cuatro secciones que
comparan las fortalezas y debilidades de estas dos bibliotecas:
1. ¿Qué está incluido y qué falta?
A primera vista, parece que ambas bibliotecas tienen más de 65 juegos
de controles MVC y proporcionan controles clave como Cuadrícula de
datos, Gráficos, Editores, Navegación, etc. Sin embargo, no todos los
controles están disponibles en ambas bibliotecas y esto puede ser Un
factor decisivo clave. Por ejemplo, si necesita controles de hoja de
cálculo, cinta de opciones o RichEdit, estos solo estarán disponibles
en la suite de Controles DevExpress ASP.NET MVC.
Aquí hay un desglose de qué controles están disponibles en cada
biblioteca:
DevExpress ASP.NET MVC Controls (Server-Side)
DevExtreme MVC Controls (Client-Side)
Grid
PivotGrid
TreeList, TreeView
Scheduler
TreeList, TreeView
Scheduler
Navigation Controls
Editor Controls
Charts & Gauges
Forms
Reporting Suite
RichEdit
HTML Editor
Ribbon
26-5-2019
Spreadsheet
30
FileManager
CardView
Vertical Grid
Docking Suite
Spell Checker
ActionSheet, ScrollView, SlideOut
Bar, Circular, Linear Gauge
Map, TreeMap, VectorMap
RangeSelector
Bullet
Sparkline
FilterBuilder
Integrated into other
controls
Box, ResponsiveBox
Toast
2. Representación Pros / Contras
Los controles DevExpress ASP.NET MVC proporcionan representación del lado del
servidor.
Pros: Sólido con (muchos más) años de lanzamientos. Controles más potentes
disponibles.
Contras: Windows IIS servidores de alojamiento. Las devoluciones de llamada, sin
embargo, los datos se envían a través de formularios nativos, formularios MVC o
formularios Ajax MVC y se puede acceder a ellos mediante la acción de un controlador.
Pros: emiten un marcado semántico que es representado por el navegador del
cliente. La interacción del usuario es más rápida. Integración más estrecha con el
marco MVC de ASP.NET.
Contras: para la interactividad y la personalización en tiempo de ejecución,
tendrás que lidiar con los widgets principales de JavaScript.
A. Desempeño
26-5-2019
Los controles DevExtreme MVC proporcionan una representación del lado del cliente.
31
Los controles DevExtreme MVC tienen algunas ventajas menores en términos de
rendimiento debido a su representación del lado del cliente. Principalmente, la
interacción del usuario se sentirá más receptiva.
Por ejemplo, al agrupar filas en la cuadrícula de datos de DevExtreme MVC, encontrará
que solo recuperará los datos de las filas que necesita actualizar para mostrar el
diseño agrupado. En la imagen de las herramientas del navegador a continuación,
podemos ver que DevExtreme MVC Grid solo obtiene JSON para las filas de datos del
servidor y Grid mostrará y actualizará la interfaz de usuario en consecuencia:
26-5-2019
Sin embargo, DevExpress ASP.NET MVC GridView realizará una actualización parcial de la
página. Lo que significa que recuperará el HTML para el nuevo diseño de agrupación del
servidor y luego los entregará al cliente y los procesará en su lugar. Una
actualización parcial de la página es similar a la siguiente cuando se envía a través
de la cadena de solicitud / respuesta:
32
Las devoluciones de llamada requieren un esfuerzo adicional del servidor para volver a
representar estos fragmentos de código HTML para (potencialmente) un gran número de
clientes y, por lo tanto, aumentar el volumen de datos. Sin embargo, en una conexión
rápida, es probable que no note una gran diferencia en la velocidad.
B. Procesamiento semántico de HTML
26-5-2019
Como se mencionó, los controles DevExtreme MVC proporcionan un marcado semántico
cuando se procesan (haga clic en la imagen para una versión más grande):
33
26-5-2019
Los controles de DevExpress ASP.NET MVC más grandes, como el Grid, utilizan un diseño
basado en tabla para la representación. Sin embargo, los controles de navegación
(paginador, menú, barra de navegación, etc.) tienen una representación semántica que
se basa en listas, divs, etc.
34
Ambos enfoques de representación cumplen con los estándares HTML5.
3. Enlace de datos
Las Extensiones DevExpress ASP.NET MVC son controles del lado del servidor y, por lo
tanto, pueden enlazarse fácilmente a cualquier capa de datos .NET del lado del
servidor. Sin embargo, el enlace del lado del cliente no es compatible con estos
controles del lado del servidor.
Los controles DevExtreme MVC proporcionan varias formas de enlazar controles
conscientes de los datos en el lado del cliente. Estos controles también proporcionan
una capa de datos del lado del servidor que le permite enlazar en el lado del servidor
también. Este mecanismo no cambia las consideraciones arquitectónicas sobre el enlace
de datos, simplemente proporciona servicios web para el acceso a los datos de forma
automática, según los orígenes de datos existentes del lado del servidor.
Ambas bibliotecas son compatibles con .NET Framework 4+ y el mismo conjunto de
navegadores.
26-5-2019
4. Soporte de .NET Core
35
Sin embargo, los controles DevExtreme MVC también son compatibles con ASP.NET Core
1.0.1 y versiones posteriores (incluyendo 1.1, 2.0+), mientras que los controles
DevExpress ASP.NET MVC no lo hacen.
Si bien no es parte de esta comparación, los nuevos controles DevExpress ASP.NET
Bootstrap Core son compatibles exclusivamente con el marco Core de ASP.NET.
Utilizar juntos
Tenga en cuenta que si elige el marco completo clásico ASP.NET MVC (no ASP.NET Core),
puede usar ambos controles MVC juntos para complementarse entre sí. Por ejemplo, puede
usar DevExpress ASP.NET MVC Spreadsheet y DevExtreme MVC Grid en el mismo proyecto
ASP.NET MVC.
¡Estas dos bibliotecas MVC también se ofrecen en la Suscripción DevExpress ASP.NET!
La decisión es tuya
Para resumir, las dos suites de control MVC le ofrecen muchos controles de interfaz de
usuario excelentes y también se pueden usar juntos.
Sin embargo, si necesita elegir entre ellos, le ofreceré mis observaciones como
administrador del programa web que interactuó con muchos desarrolladores:
Los desarrolladores que prefieren la representación del lado del cliente suelen
elegir los controles DevExtreme MVC
Los fans de C # y la representación del lado del servidor normalmente eligen los
controles DevExpress ASP.NET MVC
Con suerte, la información anterior le ayudará a elegir. Si no es así, deje un
comentario a continuación sobre cómo puedo ayudar.
Autor:
26-5-2019
Email: [email protected]
36
Descargar