Archive for August 2010
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.
Using jLinq With JSON APIs
I’ve had a few e-mails asking how to perform a query on JSON data from an external source, for example Flickr Services. This post we will go over accessing Flickr data but also performing a secondary query on the information returned.
You can download a .zip
file at the end of the post with the files used in the examples.
Let’s take a look at what we’re going to create…
You can see we’ve included a search box along with a few additional check boxes with options we can use for the secondary query. These options aren’t available for the API call, but we can intercept the records and apply additional filters before we use them on the page.
To start, we need to create an extension method for downloading the data from the Flickr APIs. jLinq allows you to create extension methods and plug them directly into the framework.
//extend jLinq to search Flickr jlinq.flickr = function(search, action) { search = escape(search); $.getJSON( "http"+"://api.flickr.com/services/feeds/photos_public.gne?"+ "tagmode=any&format=json&jsoncallback=?&tags="+search, function(data) { var query = jlinq.from(data.items); action(query); }); };
This example creates a method named flickr
that resides with the rest of the jLinq library. The method accepts a keyword to search with and an anonymous function that will be used to work with the data. This is because we have to wait for the server to return a response.
This means you would use the function with a search phrase and a jLinq query to perform.
//start a new jLinq query jlinq.flickr("cake", function(query) { //perform a normal query and use the data query.contains("title", "red") .sort("title"); });
At this point we need to wire up all of the UI elements on the page. I won’t go into detail on this part but you can always refer to the example for help if you get stuck.
So lets take a step back and take a look at how the filtering is done for the records. You’ll notice that we can selectively perform certain parts of the query depending on which check boxes are selected.
//sample.js - line 31 - - - - - - //if excluding titles with digits if (flickr.ui.filterNumbers.is(":checked")) { query.notMatch("title", /\d/g); } //if requiring a photo to have a title if (flickr.ui.requireTitle.is(":checked")) { query.notEmpty("title"); } //if requiring sorting of titles if (flickr.ui.sortTitle.is(":checked")) { query.sort("title"); } //snip...
You’ll notice that you don’t have to chain the entire query together. You can do sections at a time. This means you can decide when certain parts of the script run and when they are skipped.
Now, we can write queries against Flickr and also perform our own secondary queries using jLinq. In this case we are sorting data and filtering out records that wasn’t part of the query to the server.
Of course, this is a simple example but is does show some of the neat ways you can you can use jLinq to help you work with JSON data.
[Sample Files]
http://hugoware.com/examples/jlinq-flickr.zip
Null Instance Extension Methods
Checking for null
can be time consuming and add a lot of extra noise to the code you write. In some ways it can reduce the readability of your code and obscure the purpose of your functions.
For example, check out all of the null
checks required for this bit of code.
public static int GetVersion() { //check if this exists if (!File.Exists(PATH_APP_DETAILS)) { return DEFAULT_VERSION; } //try and load the document XDocument document = null; try { document = XDocument.Load(PATH_APP_DETAILS); } catch { return DEFAULT_VERSION; } if (document == null) { return DEFAULT_VERSION; } //find the element XElement release = document.XPathSelectElement("app/release"); if (release == null) { return DEFAULT_VERSION; } //get the value XAttribute version = release.Attribute("version"); if (version == null) { return DEFAULT_VERSION; } //convert the version int current = int.TryParse(version.Value, out current) ? current : DEFAULT_VERSION; return current; }
Yikes… that is a lot of code for such a small task but what else can you do? You can’t just call methods on something that is null
or your app is going to blow up… or can you?
Null Instance Extension Methods
Extension methods feel magical the first time you see them in action but ultimately they are just static methods that can be invoked from anywhere in your project.
But here is the neat thing — Since an extension method isn’t tied to the instance of a class then they can be invoked without an instance of the class.
Sound confusing? Here is an example of what I mean…
//create an extension method public static class StringExtensions { public static void Test(this string value) { Console.WriteLine("Hello from a null test!"); } } //and then call it string nothing = null; nothing.Test(); //ok! nothing.ToUpper(); //crash
You’ll notice that the call to Test
worked fine – If you put a break point in the method and check the value argument then you’ll notice it is actually null
! We were able to call the method Test
even though there wasn’t an instance of a string.
So how can this help us? Well let’s take the same XML problem from earlier in the post and create some extension methods that don’t care if the instance being invoked is null or not.
//helper extension methods for XML documents public static class XmlExtensions { //loads an xml document to use public static XDocument LoadDocument(this XDocument document, string path) { if (!File.Exists(path)) { return null; } try { return XDocument.Load(path); } catch { return null; } } //uses an XPath to find an element public static XElement GetElement(this XDocument document, string path) { return document is XDocument ? document.XPathSelectElement(path) : null; } //returns the value of an attribute public static string GetAttribute(this XElement element, string name) { XAttribute attribute = element is XElement ? element.Attribute(name) : null; return attribute is XAttribute ? attribute.Value : string.Empty; } }
Now let’s take a look at how we can solve the same problem as before but with these new extension methods…
public static int GetVersion() { //open the document (Mono) XDocument document = document.LoadDocument(PATH_APP_DETAILS); //It appears you have to do it like this in Visual Studio //XDocument document = ((XDocument)null).LoadDocument(PATH_APP_DETAILS); //find the element XElement release = document.GetElement("app/release"); //get the version string version = release.GetAttribute("version"); //convert and return the value int current = int.TryParse(version, out current) ? current : DEFAULT_VERSION; return current; }
As far as I can tell, no matter what you throw at this code, you will either get the default version or the value in the XML document. This definitely reduces the lines of code count… but the clarity gains might be debatable.
- A developer modifying this code might not realize the value could be
null
or could be a real object. If they call an instance method and the value ends up beingnull
then the application is going to crash. - Some developers are going to scratch their heads when they read the line
XDocument document = document.LoadDocument(...)
. Invoking a method on a value you’re declaring and should be null and returning the result it to itself? Huh? - The more forgiving your code is then the more prone to external errors it will be, which in turn will introduce subtle runtime errors in your applications. Sometimes it’s a better idea just to throw an exception and keep the program from running.
So, while this approach has benefits it also has pitfalls. Make sure to evaluate the risks before using something like this in your code. If implemented carefully this could reduce complexity in your application…
…or at least make for a neat parlor trick the next time you’re with your dev team.
Installing CobaltMVC
Got an interesting piece of anonymous feedback today reminding me of something rather obvious…
How exactly do you install CobaltMVC?
Oh yeah… That would be handy information… and it probably should have already been released. That said, lets get right to it.
Download CobaltMVC
The new CobaltMVC site is still in beta (like the rest of the framework) but you can still download the libraries you need to get started.
Add CobaltMVC To Your Project
Unpack the files to an easy to reach location and then add them as references to your project. You don’t need to add both, the Cobalt.dll
is good enough.
‘Initialize’ CobaltMVC
Next, open up the Global.asax
file and include the Cobalt.CobaltConfiguration.Initialize();
call to the Application_Start
method. This makes sure that CobaltMVC is set up fully before it begins processing requests.
Update Web.config (sorta optional)
This step can be skipped but it will make coding your pages less convenient since you’ll have to add the namespaces all over the rest of your project. Add the Cobalt
and the Cobalt.Web
namespaces to your system.web/pages/namespaces
section. This opens up the extension methods used to start Cobalt commands from your views.
You’re Done!
At this point we can test CobaltMVC to see if it is working. For example, here is a command you can run on the default MVC Index view.
And you’ll get the following results…
Not bad, huh?
It is worth mentioning that CobaltMVC might have unexpected results if you use .browser
files with your project. Why?
Well, part of the Initialize()
call actually creates a .browser
file that is used to capture rendering for content on the page.
So, if you see this file floating around in your project, don’t delete it.
Who knows what happens when too many .browser
files are fighting for the same thing but I doubt it will be a good thing.
Good luck!
jLinq Demo Site
If you’ve seen the early versions of jLinq then you know there is a dedicated online section to test out the project before you download it.
Well the new jLinq demo is now online with a shiny new logo and 25 code samples to show how to perform common (and not so common) tasks.
Of course, the rest of the site needs to be built… and the documentation needs to be written… well pretty much all the not fun coding parts still need to be done…
But in the meantime check out the new site and feel free to send any feedback!
3 Things To Know Before Using jLinq
Since I released the beta code for the new jLinq I’ve had several of the same questions show up. Here are some answers to help you get started.
Where Is ‘Manual’, Query Commands Are ‘Auto-pilot’
I’ve had a few questions where people start trying to use the where
command and find that it isn’t solving anything.
As far as I can tell, a lot of other LINQ style Javascript libraries use Where
as their query command and have the developer manually provide the comparisons. Granted, I don’t know the other frameworks very well (hint, I wrote my own) but a lot of the code samples look something like this.
var results = lib.From(data) .Where(function(record) { return record.name.substr(0, 1) == "a"; }) .OrderBy(function(record) { return record.name; }) .Select();
I’m sure that these libraries are doing a lot of stuff for you in the background, but the where
command in itself seems like placing too much work on the developer.
jLinq approaches this differently. Instead, you’ll find many common query methods included as part of the library. These methods also evaluate the data types to determine the best way to query the property.
Here is an example, consider you have some data that looks like this.
var data = [{ name: "James", permissions: ["read", "write", "delete"] }, /* snip... */];
Instead of checking these records manually, you can use the same command for both properties and they will handle the data differently for each.
//with string types jlinq.from(data).contains("name", "e").select(); //matches the 4th 'e' in 'James' //with array types jlinq.from(data).contains("permissions", "write").select(); //matches the 2nd 'write'
This reduces the typing you do since you no longer have to perform manual comparisons.
Of course, jLinq does include a where
command that uses the same syntax as the example above (anonymous function), but it is recommended to create extension methods instead of using the where
command.
Operators Are Query Commands Too
Operators are an important part of jLinq. Sometimes you’d like to match on more than one condition or ignore records that do not meet a certain condition. You can use operators as individual commands in your queries.
//Examples only - Shorter syntax explained in Section 3 jlinq.from(data) .starts("first", "a") .or() .starts("first", "b"); jlinq.from(data) .not() .starts("first", "d")
jLinq takes operators a step further by creating operator aliases for all of the query commands in your library. This means that instead of only having the query command starts
you actually have 6 – starts
, orStarts, andStarts
, notStarts
, orNotStarts
, andNotStarts
. The queries above can be rewritten as.
//Examples only - Shorter syntax explained in Section 3 jlinq.from(data) .starts("first", "a") .orStarts("first", "b") .select() jlinq.from(data) .notStarts("first", "d");
It is worth noting that and
is the implied default unless you provide an or
– the and
versions of the query commands are for aethestetics only. 🙂
DRY — I Repeat, DRY!
If you aren’t familiar with the term DRY, it means “Don’t Repeat Yourself”. The previous samples reduced some of our typing, but jLinq can actually take it a step further.
jLinq uses field and command memorizing, meaning after you say it once it is implied until you say something different.
jlinq.from(data) .starts("first", "a") .or("b") .or("c"); jlinq.from(data) .starts("first", "a") .ends("b") .or("last", "a");
The first example memorizes the command being used is starts
and the field being queried is first
. After that, the remaining calls to ‘or’ will repeat the previous command and field unless something else is used.
The second example shows how you can mix up commands and field names without needing to repeat yourself. Basically, here is what the query is doing.
- Records with first names starting with ‘a’…
- then records with first names ending with ‘b’…
- then records with last names ending with ‘a’
You can see that jLinq makes it easy to avoid repeating the same commands and field names to reduce the typing.
This is just some of the first basics to understand about jLinq. I’ll be blogging more (and writing documentation) for the next few days. Feel free to contact me with questions.
jLinq Reloaded
[jLinq Beta is now online – go check it out!]
My first ‘open source’ project I ever released was a Javascript library that allowed you perform LINQ style queries with arrays of objects. The library supported operators, extension methods and even memorized commands to reduce typing.
Overall, it did a good job of making it easier to select information that normally would need to be handled with a for loop. Unfortunately, it turns out jLinq is rather slow. I recently blogged about a performance evaluation that came back with less than flattering results.
However, this is exactly where ‘open source’ helps make better software. If I hadn’t released my code then I might never know how it could be improved. This is the sort of thing that inspires software developers to improve their code — and that is exactly what is did for me.
So for the past few days I’ve been completely rewriting jLinq from the ground up and the gains have been incredible to say the least.
- About 60% smaller (around 8kb compressed)
- 99% faster queries
- Easier to extend library
- Minty fresh scent (your results may vary)
If you’re looking for a download link then you’re going to have to contact me instead. The code should be released soon but for now I’m still writing tests and preparing to rerelease the project with a brand new site (and probably a new name).
[http://github.com/hugoware/jlinq-beta/]
There has been a lot of interest already in seeing the new code so I’ve started a new repository on github.com. This is beta code so I suspect I’ll have errors in it. Please feel free to contact me to log an issue on the site.
If you’re interested in how jLinq improved so much then read on…
jLinq… But On Caffeine
My first theory about why jLinq was so slow had to do with the use of eval
s in my code.
At the time, I didn’t understand you could pass functions as arguments, I certainly had no idea what enclosures were, so jLinq would build an entire query in a string and then eval
it into a method that could be invoked against each record.
I also used eval
to get values from records instead of just referring to them by their names as an index. I don’t know why eval
is so much slower, but the difference is undeniable.
//using index names var fast = function(obj, path) { path = (path+"").split(/\./g); var index = 0; while(obj != null && index < path.length) { obj = obj[path[index++]]; } return obj; }; //being lazy with an eval var slow = function(obj, path) { return eval("obj."+path); }; //looping 100,000 times loop(100000, function() { fast(data, "name.first"); }); //128ms loop(100000, function() { slow(data, "name.first"); }); //6.8 minutes
So what kind of improvement do these two changes result in? Big ones.
Single Argument Query (850 Records)
Previously: ~430ms
Now: ~10ms
Multi-Argument Query with Strings (850 Records)
Previously: ~2730ms
Now: ~35ms
Simple Join (850 Records)
Previously: ~6200ms
Now: ~135ms
The list of improvements goes on but I’ll talk about them more in later blog posts.
So What Is Next?
It’s screaming fast and much smaller but unfortunately it is too new to even suggest that it is put into production. I’ll definitely feel more confident in the code after I’ve finished the tests.
But I also plan to rebrand the library this time. Why?
As it turns out, the name LINQ has caused some confusion with non-.NET developers. I’ve heard that some developers see the name and say ‘Well, I don’t use .NET so I don’t need this.’, which is clearly not the response I’d like to hear.
jLinq isn’t only for web pages. In fact, the neat thing about jLinq is that it can be plugged into anything running Javascript and it works great. I’d like to see jLinq be exposed to more developers.
So, with a new name, new site and all brand new code I think there is a chance to redefine what jLinq can do for the community.
The code is mostly finished so now it’s time for the hard part… coming up with a good name…
More Control When Parsing Values
As much as you might try, sooner or later you’re going to wind up parsing string values and converting them into their correct type. It is… inevitable.
If you’re going for clarity then this only takes a couple lines of code to accomplish. For example…
int value = default(int); int.TryParse("44", out value);
No big deal, in fact it is possible to do on a single line of code…
int value = int.TryParse("44", out value) ? value : default(int);
Of course, this line of code is going to draw more questions from some of the less experienced developers on your team, but hey… doesn’t it just scream ‘Alpha Programmer’?
Parsing integers isn’t really a big deal in itself but wouldn’t it be nice if the entire process could be done on one line with one method and allow you to supply default values… and without causing confusion?
Below is an example of how you might create a class that handles parsing for you.
/// <summary> /// Handles parsing string values to appropriate types /// </summary> public static class Parse { #region Static Constructor //prepares the Parse class static Parse() { Parse._CustomParsing = new Dictionary<Type, Func<string, object>>(); } #endregion #region Properties //holds custom parse methods private static Dictionary<Type, Func<string, object>> _CustomParsing; #endregion #region Public Methods /// <summary> /// Registers a custom parsing method /// </summary> public static void RegisterType<T>(Func<string, object> compare) { Type type = typeof(T); Parse._CustomParsing.Remove(type); Parse._CustomParsing.Add(type, compare); } /// <summary> /// Attempts to parse a value and return the result /// but falls back to the default value if the conversion fails /// </summary> public static T TryParse<T>(string value) { return Parse.TryParse(value, default(T)); } /// <summary> /// Attempts to parse a value and falls back to the /// provided default value if the conversion fails /// </summary> public static T TryParse<T>(string value, T @default) { value = (value ?? string.Empty).ToString(); Type type = typeof(T); //so much can go wrong here, just default to the //fall back type if the conversions go bad try { //perform custom parsing first if (Parse._PerformCustomParse(type, value, ref @default)) return @default; //check if this is a nullable and if we should use a child type //this might not work with VB since the nullable name could be different if (type.IsGenericType && type.Name.StartsWith("Nullable`")) { //underlying type for a nullable appears to be the first argument type = type.GetGenericArguments().FirstOrDefault(); //if no type was found then five up if (type == null) { return @default; } //try custom parsing with the underlying type if this was a nullable if (Parse._PerformCustomParse(type, value, ref @default)) return @default; } //try the remaining parsing methods if (type.IsEnum && Parse._PerformEnumParse(type, value, ref @default)) return @default; if (Parse._PerformParse(type, value, ref @default)) return @default; //finally, just try a conversion Parse._PerformConvert(type, value, ref @default); return @default; } //settle for the default catch { return @default; } } #endregion #region Checking Values //uses custom parsing methods private static bool _PerformCustomParse<T>(Type with, string value, ref T result) { //if there is no custom type, cancel if (!Parse._CustomParsing.ContainsKey(with)) { return false; } //find the conversion Func<string, object> parse = Parse._CustomParsing[with]; //attempt to parse try { object converted = parse(value); bool success = converted is T; if (success) { result = (T)converted; } return success; } //if the attempt failed catch { return false; } } //tries to parse using an Enum private static bool _PerformEnumParse<T>(Type with, string value, ref T result) { //check for a result try { object parsed = Enum.Parse(with, value, true); bool success = parsed is T; if (success) { result = (T)parsed; } return success; } catch { return false; } } //searches for a 'Parse' method private static bool _PerformParse<T>(Type with, string value, ref T result) { //make sure a try parse was even found MethodInfo method = with.GetMethods().FirstOrDefault(item => item.Name.Equals("parse", StringComparison.OrdinalIgnoreCase) && item.IsStatic); if (method == null) { return false; } //check for a result try { object parsed = method.Invoke(null, new object[] { value, result }); bool success = parsed is T; if (success) { result = (T)parsed; } return success; } catch { return false; } } //performs common conversions private static bool _PerformConvert<T>(Type type, string value, ref T result) { object convert = Convert.ChangeType(value, type); bool success = convert is T; //update the type if needed if (success) { result = (T)convert; } return success; } #endregion }
The general idea behind this code is to attempt to parse using a variety of ways and then fall back to a default value if nothing works out. This way we can get in and out of parsing values without needing to split up the code.
There is an ugly little bit of reflection in there, so if your application can’t incur that kind of penalty hit you might want to fall back to the classic parsing methods.
int count = Parse.TryParse("44", 0); // 44 bool success = Parse.TryParse("cheesecake", true); // true decimal = Parse.TryParse("3.01", 3M); // 3.01
Additionally, this code allows you to add your own parsing methods to the class and use the same method across the entire project.
//register our own parsing method Parse.RegisterType<Color>(value => ColorTranslator.FromHtml(value)); Parse.RegisterType<decimal>(value => { value = Regex.Replace(value, @"[^0-9\.]", string.Empty); return decimal.Parse(value); }); //use it just like any other value Color color = Parse.TryParse("#ff0000", Color.Blue); decimal total = Parse.TryParse("$45.33", 0M);
You could take the extension methods a step further by adding the same functionality to your strings. For example.
/// <summary> /// Extensions to quickly parse string types /// </summary> public static class StringExtensions { /// <summary> /// Parses a string to the correct type or default value /// </summary> public static T ToType<T>(this string value) { return value.ToType(default(T)); } /// <summary> /// Parses a string to the correct type or default value /// </summary> public static T ToType<T>(this string value, T @default) { return Parse.TryParse(value, @default); } }
This would allow you to perform the conversions from the string itself and avoid the call to a static method floating around in the project.
bool admin = "true".ToType<bool>(); int count = "3334".ToType<int>(); Color blue = "something".ToType(Color.Blue); Color green = "#00ff00".ToType(Color.Red);
Parsing values isn’t hard but is certainly doesn’t hurt to make it a bit easier.
jLinq Performance Analyzed
After writing this post I went back and rewrote jLinq from the ground up to improve performance.
So here is something interesting. Another developer named Dan Stocker has been working on a Javascript sorting library named jOrder and used jLinq for comparing benchmarks. The results are interesting to say the least.
Dan has a good write up of the results in the wiki for his project.
I’m disappointed, but in a good way. I thought jLinq was ‘good enough’. It performed the way it should and that was good enough for me. These results tell a much different story.
jLinq probably performs well in typical scenarios, whereas these benchmarks are for medium to large arrays queried thousands of times. I’ve used it in several projects with great results.
Regardless, jLinq clearly performs slower than it should. Reviewing the jLinq code I can think of a few potential pain points.
- The entire jLinq object and all of the commands are generated each time a query is started. jLinq there should be a way to cache a standard jLinq object and only rebuild it when needed.
- jLinq uses a lot of
eval
s that probably could have been avoided if I understood Javascript better at the time. This undoubtedly is going to cause a significant performance hit.
jLinq was written just about a year and a half ago and I’ve since learned some of the finer parts of Javascript which would most likely improve the performance of the library. In fact, seeing this review has really inspired me to get into back into the project and see what kind of improvements I can make.
I appreciate Dan showing these results to me. Sharing your code with others is a great way to get feedback about what you write… even if it is a kick in the butt.