CODEGATOR

.NET info worth sinking your teeth into!
Welcome to CODEGATOR Sign in | Join | Help
in Search

Martin Cook

Yet another C# developer with a blog.

CG.Storage - Some Examples

Introduction

If you are like me, then you have written code to store files/blobs/data/objects/whatever into databases at least a thousand times. I have written that code so often in my career that I finally decided to put the functionality into a library that I could reuse. That's the reasoning behind my CG.Storage library. CG.Storage is a small reusable library that can be used to store and retrieve things. It's like storing a BLOB into a database except that my code adds a layer of abstraction that allows for storing into other devices (like a file on a disk drive) by changing a single line of text in a configuration file.

 

StorageManager

The library contains a single "manager" class (see my CG.Manager library for an explaination of what a manager is) called StorageManager that provides the following methods:

/// <summary>

/// Stores the contents of the specified stream.

/// </summary>

/// <param name="stream">A stream containing data to be copied to storage.</param>

/// <returns>An identifier that represents the newly created storage

/// record.</returns>

public virtual Guid Create(

    Stream stream

    );

 

// ******************************************************************

 

/// <summary>

/// Stores the contents of the specified file.

/// </summary>

/// <param name="filePath">The path to a file whose contents will be

/// copied to storage.</param>

/// <returns>An identifier that represents the newly created storage

/// record.</returns>

public virtual Guid Create(

    string filePath

    );

 

// ******************************************************************

 

/// <summary>

/// Stores a byte array.

/// </summary>

/// <param name="buffer">An array of data that will be copied to

/// storage.</param>

/// <returns>An identifier that represents the newly created storage

/// record.</returns>

public virtual Guid Create(

    byte[] buffer

    );

 

// ******************************************************************

 

/// <summary>

/// Stores an object reference.

/// </summary>

/// <param name="obj">An object whose state will be copied to

/// storage.</param>

/// <returns>An identifier that represents the newly created storage

/// record.</returns>

public virtual Guid Create<T>(

    T obj

    )

    where T : class, new();

 

// ******************************************************************

 

/// <summary>

/// Updates a storage record with the contents of the specified file.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="stream">A stream containing data to be copied to

/// the storage record.</param>

public virtual void Update(

    Guid id,

    Stream stream

    );

 

// ******************************************************************

 

/// <summary>

/// Updates a storage record with the contents of the specified file.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="filePath">The path to a file that will be read from.</param>

public virtual void Update(

    Guid id,

    string filePath

    );

 

// ******************************************************************

 

/// <summary>

/// Updates a storage record with the contents of an array.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="buffer">An array of data that will be copied to

/// storage.</param>

public virtual void Update(

    Guid id,

    byte[] buffer

    );

 

// ******************************************************************

 

/// <summary>

/// Updates a storage record with the contents of an array.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="obj">An object whose state will be copied to storage.</param>

public virtual void Update(

    Guid id,

    object obj

    );

 

// ******************************************************************

 

/// <summary>

/// Deletes the specified storage record.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

public virtual void Delete(

    Guid id

    );

 

// ******************************************************************

 

/// <summary>

/// Reads the contents of the specified storage record.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="stream">A stream to contain the data for the storage

/// record.</param>

public virtual void Read(

    Guid id,

    out Stream stream

    );

 

// ******************************************************************

 

/// <summary>

/// Reads the contents of the specified storage record.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="filePath">The path to a file that will be created

/// to contain a copy of the data for the storage record. Note that

/// this file will be overwritten!</param>

public virtual void Read(

    Guid id,

    string filePath

    );

 

// ******************************************************************

 

/// <summary>

/// Reads the contents of the specified storage record.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="buffer">A buffer containing the data for the

/// specified storage record.</param>

public virtual void Read(

    Guid id,

    out byte[] buffer

    );

 

// ******************************************************************

 

/// <summary>

/// Reads the contents of the specified storage record.

/// </summary>

/// <param name="id">The identifier for the storage record.</param>

/// <param name="obj">A deserialized instance of the specified

/// storage record.</param>

public virtual void Read<T>(

    Guid id,

    out T obj

    )

    where T : class, new();

 

// ******************************************************************

 

/// <summary>

/// Returns a list of identifiers to represent all the storage records.

/// </summary>

/// <returns>A list of identifiers.</returns>

public virtual List<Guid> FindAll();

So, looking at these method signatures it should be clear that my StorageManager class handles creating, reading, updating, and deleting files, streams, byte arrays, and serializable objects. The idea behind StorageManager is that you give it something to store and it gives you back a GUID that you can use later to retrieve what you just stored. StorageManager doesn't remember any metadata related to what you store, it just stores things and retrieves them. The lack of associated metadata keeps the code simple and increases the likelihood that the CG.Storage library could be incorporated into existing projects (both mine and yours). Of course, the lack of metadata also means that you can't use StorageManager to search for files using metadata like keywords, etc. For that, you'll either have to write your own code or use my CG.Archive library. (Yep, CG.Archive is in the downloads section of CODEGATOR)

Library Configuration

Let's start by adding some configuration XML that CG.Storage requires at runtime:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <section name="CG.Storage.StorageManager" type="CG.Manager.ManagerConfigurationSection, CG.Manager, Culture=neutral, PublicKeyToken=a9f0e63f80ee0325"/>

  </configSections>

  <connectionStrings>

    <clear />

    <add name="LocalHostSqlServer" providerName="System.Data.SqlClient" connectionString="Data Source=localhost;Initial Catalog=cg_storage;Integrated Security=True"/>

  </connectionStrings>

  <CG.Storage.StorageManager defaultProvider="DiskStorageProvider">

    <providers>

      <add name="DbStorageProvider" type="CG.Storage.Data.Provider.DbStorageProvider, CG.Storage, Culture=neutral, PublicKeyToken=a9f0e63f80ee0325" connectionString="LocalHostSqlServer" chunkSize="8040" />

      <add name="DiskStorageProvider" type="CG.Storage.Data.Provider.DiskStorageProvider, CG.Storage, Culture=neutral, PublicKeyToken=a9f0e63f80ee0325" rootFolder="c:\RootFolder" chunkSize="8040" />

    </providers>

  </CG.Storage.StorageManager>

</configuration>

(Sorry, I don't know of any way to pretty up XML - see why I almost never include XML in my blog?) This configuration XML is stored in a file called App.config, and should be incorporated into your application. It configures the StorageManager class to load a provider at runtime. That provider performs the actual storage on behalf of the StorageManager class. As shown, StorageManager is configured to use the DiskStorageProvider, and will store things into a folder on the hard drive at the 'C:\RootFolder' folder on your hard drive. The SQL-Server provider can also be used by simply changing the defaultProvider attribute to 'DbStorageProvider' and changing the connectionString to point to your database. Both the disk and database providers are included in my CG.Storage library. You can also easily create your own storage provider be extending my base provider class. That's a subject for another blog entry though...

Sample Code

OK, so we have configured things, now, how do we use the StorageManager? Here is a quick example of storing a file:

StorageManager target = Singleton<StorageManager>.Default;

string filePath = Path.GetTempFileName();

File.AppendAllText(filePath, "hello world");

Guid id = target.Create(filePath);

Pretty easy stuff. You give StorageManager a file path and it stores the contents of the file internally and gives you back a GUID.

Here is an example of storing an object. Let's start by defining a class for our example:

[Serializable]

public class TestObject

{

    private string name;

 

    public string Name

    {

        get { return name; }

        set { name = value; }

    }

 

    public TestObject()

    {

        name = string.Empty;

    }

}

 Now here is the code to create an instance of TestObject and store it:

StorageManager target = Singleton<StorageManager>.Default;

TestObject objA = new TestObject();

objA.Name = "hi there!";

Guid id = target.Create<TestObject>(objA);

Once again, easy stuff. Retrieving things is just as easy. Here is an example of that:

StorageManager target = Singleton<StorageManager>.Default;

TestObject objB = null;

Guid id = // set id to a valid GUID

target.Read<TestObject>(id, out objB);

 

Conclusion

CG.Storage is a good "drop-in" tool for whenever you need to store things in your project and you don't feel like writing your own custom SQL code. It also partially answers an age-old question related to storing BLOB's, which is, should they be stored in a file or in the database. If you are using my StorageManager class then you can decide for yourself and setup your configuration accordingly. If you change your mind later you can simply tweak the XML instead of rewriting your application.

The library code was recently factored out of another library and is still under development. I have performed basic testing of what I've deployed to CODEGATOR and everything seems to be working. Look for further changes to CG.Storage down the road.

 

Finally, I didn't bother to explain the internals of the CG.Storage library today but I may do that in the near future when the codebase settles down a bit more. 'Till then, download the CG.Storage library and stop writing the same old "store a BLOB in a database" code over and over again! Sheesh! Don't we have better things to do with our time???

 

Comments

No Comments

Leave a Comment

(required) 
(optional)
(required) 
Submit

About Martin

I work as a software engineer specializing in designing and building object-oriented business solutions for Windows platforms using C#. I have been programming professionally for roughly 20 years.

This Blog

Syndication

Terms of Service | Privacy Statement