Hugoware

The product of a web developer with a little too much caffeine

Anonymous Types As Object Templates

with 6 comments

I’ve got a bit of an addiction to intellisense. Dynamic types are interesting but I still prefer being able to see a nice neat list of properties on each of my objects.

Since anonymous types are ‘real’ classes then you can actually create them at runtime. This means you can use them as templates for other objects if you can provide all of the required parameters.

Below is a simple class that allows you to perform queries against a database and use an anonymous type to provide a template for the records to return.

/// <summary>
/// Helps working with database connections
/// </summary>
public class DataConnection {

    #region Constructors

    /// <summary>
    /// Creates a new DataConnection for the connection string
    /// </summary>
    public DataConnection(string connection) {
        this.ConnectionString = connection;
    }

    #endregion

    #region Properties

    /// <summary>
    /// The connection string to use with this DataConnection
    /// </summary>
    public string ConnectionString { get; private set; }

    #endregion

    #region Performing Queries

    /// <summary>
    /// Performs a query for the connection without any parameters
    /// </summary>
    public IEnumerable<T> Query<T>(string query, T template)
        where T : class {
        return this.Query(query, (object)null, template);
    }

    /// <summary>
    /// Performs a query for the connection with the provided paramters
    /// </summary>
    public IEnumerable<T> Query<U, T>(string query, U parameters, T template)
        where T : class 
        where U : class {

        //prepare the connection and command
        Type type = template.GetType();

        //create the connection - (thanks @johnsheehan about the 'using' tip)
        using (SqlConnection connection = new SqlConnection(this.ConnectionString)) {
            using (SqlCommand command = new SqlCommand(query, connection)) {

                //assign each of the properties
                if (parameters != null) {
                    this._UsingProperties(
                        parameters,
                        (name, value) => command.Parameters.AddWithValue(name, value));
                }

                //execute the query
                connection.Open();
                SqlDataReader reader = command.ExecuteReader();

                //start reading each of the records
                List<T> results = new List<T>();
                Dictionary<string, int> fields = null;
                while (reader.Read()) {

                    //prepare the fields for the first time if needed
                    fields = fields == null ? this._ExtractFields(reader) : fields;

                    //generate the record
                    T record = this._CreateAnonymousResult(reader, fields, type, template);
                    results.Add(record);
                }

                //return the results for the query
                return results.AsEnumerable();
            }

        }

    }

    #endregion

    #region Helpers

    //creates a new anonymous type from 
    private T _CreateAnonymousResult<T>(SqlDataReader reader, Dictionary<string, int> fields, Type templateType, T template)
        where T : class {

        //create a container to queue up each record
        List<object> values = new List<object>();

        //use the template to find values
        this._UsingProperties(template, (name, @default) => {

            //try and find the field name
            if (fields.ContainsKey(name)) {

                //check if this is a value
                int index = fields[name];
                object value = reader[index];
                Type convert = @default.GetType();

                //try and copy the va;ue
                if (value != null) {
                    values.Add(Convert.ChangeType(value, convert));
                }
                //not the same type, use the default
                else {
                    value.Equals(@default);
                }

            }
            //no field was found, just use the default
            else {
                values.Add(@default);
            }

        });

        //with each of the values, invoke the anonymous constructor
        //which accepts each of the values on the template
        try {
            return Activator.CreateInstance(templateType, values.ToArray()) as T;
        }
        catch (Exception e) {
            Console.WriteLine(e.Message);
            return template;
        }

    }

    //reads a set of records to determine the field names and positions
    private Dictionary<string, int> _ExtractFields(SqlDataReader reader) {
        Dictionary<string, int> fields = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
        for (int i = 0; i < reader.FieldCount; i++) {
            fields.Add(reader.GetName(i), i);
        }
        return fields;
    }

    //performs an action with each of the properties on an object
    private void _UsingProperties(object obj, Action<string, object> with) {
        
        //if there isn't anything to work with, just cancel
        if (obj == null) { return; }

        //get the type and peform an action for each property
        Type type = obj.GetType();
        foreach (PropertyInfo prop in type.GetProperties()) {
            with(prop.Name, prop.GetValue(obj, null));
        }

    }

    #endregion

}

This class allows us to perform basic queries against a database and then provide a template of the records to return. This means that if the information is found then the database value is used but if it is missing then the template value is used instead.

In order to create an anonymous type we need to know the type and each of the properties in the order they appear. After collecting this information we simply need to use the Activator.CreateInstance method to instantiate the class and we’re set! (method _CreateAnonymousResult)

This means we can write a ‘dynamic’ query with results that have intellisense!

//set up the connection
DataConnection data = new DataConnection(CONNECTION_DATA);

//perform the query
var results = data.Query(
    "select * from orders where orderId > @orderId",
    new { orderId = 11000 },
    new {
        orderId = -1,
        shipName = "missing",
        shipRegion = "none"
    });

//access properties
var record = results.First();
int id = record.orderId; //intellisense!

I’ve actually used this same approach before in CSMongo as a way to provide intellisense without knowing anything about the database in advance.

Of course, this is just some code I hacked out this evening. You aren’t going to be able to do a lot with it but it is a good example of how you can reuse anonymous types in your code.

Written by hugoware

August 30, 2010 at 12:19 am

6 Responses

Subscribe to comments with RSS.

  1. […] See the original post here: Anonymous Types As Object Templates « Hugoware […]

  2. Very nice!

    I wrote a simple ado.net wrapper that’s very similar. Only for object hydration I was using reflection with .SetValue, I bet using the Activator is MUCH faster, I should look into that. (plus I wasn’t trying to use anon types)

    I was reading Ayende just yesterday, and he talks about spinning up DTO’s and stuff all over the place, and I’m thinking “dude, its not THAT easy to make all these classes everywhere, especially if your trapped in a language like vb.net”

    But on the other side, I’m beginning to understand where he’s coming from, if you think of your views and whatnot as just a bag of data that really isn’t attached to a specific entity, you can really decouple your code nicely.

    Anyway, all that to say, maybe using Anon types (especially in clever ways like this) can help bridge the gap between building classes all over the place, and also reaching for that decoupling.

    Mike

    August 31, 2010 at 6:51 am

    • The problem with anonymous types is that they only work in their local scope. Which makes passing data around or caching much more difficult than an actual class (as in your example)

      If you have a link to your class I bet it would be a helpful alternative to my code (since mine only works with anonymous types)

      Thanks Mike!

      hugoware

      September 2, 2010 at 12:48 am

      • Half the work on this wrapper was for work, but the other half was on my own time. I’ll probably try and make it a tad more independently coherent later on, but you can sort of see what’s going on.

        http://code.google.com/p/simpledata/

        I think what relates more is the Mapper can do things like:
        IEnumerable results = SimpleData.ExecProc(“pr_get_Album”,new sdParm(“ArtistId”,58)).Cast();

        The .Cast is an extension method on DataTables that hydrates your specified type on any matching column names to the properties of your object.

        Not rocket science, but it’s pretty quick for simple stuff.

        Mike

        September 2, 2010 at 9:29 am

  3. Nice work!

    You know, I was going to not think about programming for a three day weekend and I ended up working on an idea, and it’s all your fault!! Friday morning I read this post, and it got me thinking about providing a method for querying a list of AnonymousTypes.

    The result of my madness is here: http://activeengine.wordpress.com/2010/09/19/persistent-anonymoustypes-and-querying-with-linq/.

    I modified the Anonymous type class to include a method Has() and with that method you can perform LINQ queries like:

    var dept13 = agentList.AsQueryable()
    .Where(x => x.Has(“Department”, Compare.Equal, 13);

    Really getting into it, I used PredicateBuilder to come up with this:

    var predicate = PredicateBuilder.False();
    predicate = predicate.Or(x => x.Between(“Age&”, 40, 60));
    predicate = predicate.Or(x => x.Has(“Name”, Compare.Contains, “St”));

    var salesAgentsBetwee40_60_STInName = agentList.AsQueryable()
    .Where(predicate);

    Here’s a link to the source project and I have tests that lay this all out:

    http://www.box.net/shared/ijflzxpfef

    Again you’ve got me thinking and that’s a good thing. I’d like any feedback that you may have. Keep up the great ideas!

    ActiveEngine Sensei

    September 19, 2010 at 7:14 pm

  4. thanks for admin wonderfull information…

    aşk büyüsü

    October 6, 2010 at 8:30 am


Leave a reply to hugoware Cancel reply