Anonymous Types As Object Templates
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.
[…] See the original post here: Anonymous Types As Object Templates « Hugoware […]
Webmaster Crap » Blog Archive » Anonymous Types As Object Templates « Hugoware
August 30, 2010 at 1:20 am
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
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
thanks for admin wonderfull information…
aşk büyüsü
October 6, 2010 at 8:30 am