|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Chapters
Services
Feature Zones
|
IntroductionOver a year ago, I read an article by Jarno Peschier in .NET Magazine. The article, written in Dutch, is called "Blob + Stream = BlobStream" and can be found here. It discusses the use of streams in conjunction with blobs in an SQL Server database. Blob is short for "Binary Large OBject" and generally refers to stored pictures, PDFs and other binary files. Jarno's idea is to use The more widespread techniques to get blobs into and out of a database generally use byte arrays that contain the entire blob. For most scenarios and/or with relatively small blobs, this shouldn't be a problem. However, the transfer of a whole blob in one piece might not be a good idea in a web scenario with potentially hundreds of users requesting the same large file from the database at the same time. The big advantage of using streams is that the blob is transported in small chunks, thus keeping the memory footprint on the application server as small as possible. Having used Jarno's technique in some of my recent projects, I decided to take it a step further and provide an extensible framework to manipulate blobs from other databases than SQL Server. As this is a rather large subject, I've split it up in parts. For the remainder of these articles, I shall no longer be talking about blobs, but about lobs instead. Of course, lob stands for "Large OBject." The reason is that lob streams can be used not only with binary data, but with text data as well. Part 2 of this article is also available. The philosophical discussionThis article does not take a stand in the big debate about whether or not it is a good idea to store pictures and other binary files in a database instead of using the file system. A rule of thumb that Dino Esposito gave in his book "Programming Microsoft ASP.Net 2.0 Applications – Advanced Topics" (Chapter 9) is:
In another article Dino added, "If you need to frequently edit the images, I suggest that you store the images as separate files. If your files are essentially read-only and relatively static, and if you measure the size in Kb, then using a database is generally fine." Personally, I agree with Dino. In the projects where I stored pictures in the database, they were smaller than 1 Mb most of the time and served merely as an illustration to a text. Thus, they were static by nature. The frameworkThe purpose of the framework is to provide a few classes and types that aid in the use of lob streams. The heart of the framework is an abstract class called LobStream. LobStream serves as the general base class for all subsequent implementations of lob streams that are database-specific. LobStream derives from The abstract LobStream class has two abstract subclasses: SqlLobStream and OleDbLobStream. As the names reveal, these classes refer to the two main .NET Data Providers for accessing databases. In the future, the framework can be extended with other .NET Data Provider specific classes like, for example, OdbcLobStream or OracleLobStream. See the interesting article on working with Oracle lobs for more information. The .NET Data Provider specific abstract subclasses serve as the basis for the actual implementation of a database-specific lob stream, as we will see further. Furthermore, the framework exposes a LobDataType enumeration that indicates the type of database and data type of the lob inside the database. Finally, the framework implements a LobStreamFactory class named CreateLobStream that exposes three overloaded static methods. The CreateLobStream methods accept some parameters and return an appropriate lob stream object. The framework was created in Visual Studio 2005 as a class library project. It outputs to LobStream.dll. As long as the client code knows how to deal with an instance of LobStream that implements the ILobStream interface, the underlying code in the DLL can be changed and extended over time. Several design goals are achieved in this way:
The full picture of the framework is displayed here:
ILobStreamThe implementation of ILobStream is pretty straightforward: public interface ILobStream
{
IDbConnection Connection { get; set;}
IDbCommand Validate { get; set;}
bool IsValid { get; set;}
string TableName { get; set;}
string KeyColumn { get; set;}
string LobColumn { get; set;}
string LobID { get; set;}
void WriteTo(Stream destination, bool closeWhenFinished, long bufferSize);
void WriteTo(Stream destination);
void WriteTo([In, Out] byte[] bytes);
void ReadFrom(Stream source, bool closeWhenFinished, long bufferSize);
void ReadFrom(Stream source);
byte[] ToByteArray();
}
The interface makes sure that, in order to be able to communicate with a database, at least the following is available in the implementing class:
All of the above ultimately has to be supplied by the client code to the implementing class. In addition, ILobStream contains a few useful methods to enable the LobStream to communicate with other streams:
Finally, the Inheriting from System.IO.StreamAny class that derives from Properties:
Methods:
Since the LobStream is deriving from LobStreamWe reach the point that we must provide an actual implementation of the abstract methods of Stream in order to produce a base class for specific lob streams. In addition, we make LobStream an abstract class as well so that it can not be instantiated directly. The LobStream class serves only as a base class for database-specific implementations in a subclass. Implementing the abstract properties of System.IO.StreamCanSeek, CanRead, CanWriteA stream should support seeking if the position in the dataflow matters and if the read/written chunks of data are meaningless unless they are ordered properly. Since the LobStream is all about the transfer of smaller binary data chunks to and from a database, it is obvious that these chunks must be kept in the proper order. Therefore, #region CanSeek, CanRead, CanWrite
public override bool CanSeek
{
get { return true; }
}
public override bool CanRead
{
get { return true; }
}
public override bool CanWrite
{
get { return true; }
}
#endregion
Position
private long _position = 0;
public override long Position
{
get
{
return _position;
}
set
{
_position = value;
}
}
Length
public override long Length
{
get
{
throw new Exception("The property is not implemented.");
}
}
Implementing the abstract methods of System.IO.StreamThe Flush methodSome stream implementations perform local buffering of the underlying data to improve performance. For such streams, the public override void Flush()
{
//No internal buffers are used. So, nothing to flush...
}
The Seek methodThe public override long Seek(long offset, SeekOrigin origin)
{
long start = 0;
switch (origin)
{
case SeekOrigin.Begin:
start = 0;
break;
case SeekOrigin.Current:
start = this.Position;
break;
case SeekOrigin.End:
start = this.Length;
break;
}
this.Position = start + offset;
return this.Position;
}
Read and Write methodsAlthough the public override int Read(byte[] buffer, int offset, int count)
{
throw new Exception("The Read method is not implemented.");
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new Exception("The Write method is not implemented.");
}
The SetLength methodWith this method, the length of the stream is set to a desired length either by truncating or expanding the stream. However, the LobStream needs to manipulate the binary data exactly as it is, without truncating or expanding. public override void SetLength(long value)
{
throw new Exception("The SetLength method is not supported.");
}
Providing more base functionality in LobStreamConstructorThe LobStream class provides two constructors, one with and one without parameters. The constructor with the parameters is useful to accept at once all necessary user-supplied information. public LobStream(IDbConnection connection, string tableName,
string keyColumn, string lobColumn, string lobID)
{
this.Connection = connection;
this.TableName = tableName;
this.KeyColumn = keyColumn;
this.LobColumn = lobColumn;
this.LobID = lobID;
}
It is important to note that lob stream classes expect to receive an open connection from the calling code. On the other hand, when the lob stream class is disposed of, the connection is closed automatically. This is explained in the "Cleaning up" section. Cleaning upThe LobStream uses a database connection for transport of the lob. After the work is done, this connection should be closed and released. The question is where. The most intuitive solution is to override the public virtual void Close()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
The implementation of the // Implements IDisposable.
// Do not make this method virtual.
// A derived class should not be able to override this method.
// This method disposes the stream, by writing any changes to
// the backing store and closing the stream to release resources.
public void Dispose()
{
this.Close();
}
// Dispose(bool disposing) executes in two distinct scenarios.
// If disposing equals true, the method has been called directly
// or indirectly by a user's code. Managed and unmanaged resources
// can be disposed.
// If disposing equals false, the method has been called by the
// runtime from inside the finalizer and you should not reference
// other objects. Only unmanaged resources can be disposed.
protected virtual void Dispose(bool disposing)
{
if(disposing && (this._asyncActiveEvent != null))
{
this._CloseAsyncActiveEvent(
Interlocked.Decrement(ref this._asyncActiveCount));
}
}
In LobStream, we override the protected override void Dispose(bool disposing)
{
if(!this.disposed)
{
try
{
if(disposing) this.Connection.Close();
this.disposed = true;
}
finally
{
// Call Dispose on the base class.
base.Dispose(disposing);
}
}
}
In summary, calling using (LobStream.LobStream s = LobStreamFactory.CreateLobStream(
LobDataType.AccessBinaryFromImage, connectionString,
"Blob", "blobID", "blobImage", "1"))
{
//Do some stuff with it
}
The using statement defines a scope outside that the LobStream will be disposed to automatically. A validation mechanismFollowing the "check your input" rule , I included a validation mechanism by which the LobStream can verify that the indicated lob is indeed present in the database. LobStream has an abstract property, private bool _isValid = false;
public virtual bool IsValid
{
get
{
int result = Convert.ToInt32(this.Validate.ExecuteScalar());
this._isValid = (result == 1) ? true : false;
return this._isValid;
}
set
{
_isValid = value;
}
}
public abstract IDbCommand Validate
{
get;
set;
}
Communicating with the outside worldIntroductionAt some point, the lob stream interacts with client code, either to get the lob content from the database or to write lob content to the database. This interaction can come in two flavors:
BinaryReader subscribes to the orthodox stream logic and is heavily dependent on the A little side-note here: do not be confused that BinaryReader and WriteTo/ReadFrom in LobStreamOnce instantiated, two important methods are responsible for accepting outside data and writing it into the LobStream or supplying data to an outside stream or byte array. These overloaded methods are public virtual void ReadFrom(Stream source,
bool closeWhenFinished, long bufferSize)
{
try
{
int bytesRead;
byte[] buffer = new byte
As the code shows, the lob is read one buffer at a time in the The LobStreamFactoryLobStreamFactory is a factory class that returns the most appropriate LobStream class based upon the data type and database of the lob. The data type of the lob is provided through the public enum LobDataType : int
{
Unknown = 0,
SqlServerBinaryFromImage = 100,
SqlServerBinaryFromVarBinary = 101,
AccessBinaryFromImage = 200,
SqlServerTextFromImage = 500,
SqlServerTextFromVarBinary = 501,
AccessTextFromImage = 600
}
The LobStreamFactory provides three overloaded static methods called public static LobStream CreateLobStream(LobDataType lobDataType)
{
LobStream result = null;
switch (lobDataType)
{
case LobDataType.SqlServerBinaryFromImage:
case LobDataType.SqlServerTextFromImage:
result = (LobStream)new SqlLobStreamFromImage();
break;
case LobDataType.SqlServerBinaryFromVarBinary:
case LobDataType.SqlServerTextFromVarBinary:
result = (LobStream)new SqlLobStreamFromVarBinary();
break;
case LobDataType.AccessBinaryFromImage:
case LobDataType.AccessTextFromImage:
result = (LobStream)new AccessLobStream();
break;
}
return result;
}
The fact that We outlined above that the LobStream always expects an open connection. In Windows Forms applications, this would seldom cause performance problems. However, in a web scenario we should use connections judiciously and keep them open as briefly as possible. Therefore the second overload of ConclusionIn this first part, we have cut through the more technical details of setting up a framework for streams that can work with binary or text data stored in a database. The framework is generic enough to provide a solid foundation for subclasses of LobStream, which will manage the flow of data to and from all kinds of databases. In the next part, we'll go into the further details of implementing a lob stream for a specific database. History
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||