|
In case you aren't already aware, LINQ (Language INtegrated Query) is a new feature coming out of the .NET Framework 3.5. It allows you to query everything from in-memory collections of objects to remote data stores with syntax that looks like this:
var q = from cust in Customers
where cust.Age > 21
orderby cust.CustomerName
select cust;
The great thing about LINQ is that it gives you a natural expressiveness that you can use to describe the data you're looking for. The problem with it is that what you get back is stagnant. This means that while you might get rows once you ask q for it's enumerator (how fetches are triggered in LINQ), you're going to get a result set that doesn't change. So that means that if someone goes and adds a couple of customers to your original source (this is quite common for networked desktop applications to receive "model updates" from network messages), you're going to have to re-run (re-enumerate) your query to see the new changes. To me, this is unacceptable.
The way it should work is that when someone adds or removes customers from the underlying store, the result set that you obtained should also be modified. When a customer is added to the backing store, it should be re-evaluated against the membership predicate (the clause you specified in the where statement) and if it passes, it should be added to your result set automatically. Likewise, if a property changes on any of the customers currently residing in the backing store, those customers should be re-evaluated against the filter predicate and added to the result set if they now pass the test. For example, let's say your query starts out with 5 items. Then, someone adds a customer to the backing store that is 22. You should now have 6 items in your result set without having re-queried or re-polled. Next, someone changes the age of a 15-year-old customer to 27. This customer now qualifies to be in your result set. As a result, that customer is automatically added to your result set without you having to do anything to make it happen. Finally, each time the result set is modified, the sort is re-evaluated so that if someone changes a customer's name, the sort order of the customer list is re-evaluated so that it stays consistent with the key selector you specified in the orderby clause.
This is exactly what I've done with a language extension that I've written for a class that I've had lying around for a while, a ThreadSafeObservableCollection<T>. This class is really just a descendent of ObservableCollection<T> with some thread-safety semantics bolted on top. Now, anytime you run a LINQ query against this class, you get a live, connected, dynamically updating result set that is notified of all changes to the backing store and updates itself accordingly. I don't have group-by working yet, but I've got sorting and filtering (orderby and where).
Take a look at the following code:
customers = new ThreadSafeObservableCollection<Customer>();
Customer c = new Customer("Bob", 21, 50.00f);
customers.Add(c);
Customer c2 = new Customer("Alice", 30, 156.00f);
customers.Add(c2);
Customer c3 = new Customer("Joyce", 53, 121.00f);
customers.Add(c3);
var query = from cust in customers
where cust.CustomerName.Length > 3
orderby cust.CustomerName descending
select cust;
this.DataContext = query;
Customer c4 = new Customer("Jen", 18, 324.00f);
customers.Add(c4);
Customer c5 = new Customer("Aaron", 54, 123.00f);
customers.Add(c5);
c.CustomerName = "Super Bob";
customers.Remove(c4);
Now, without the language extension, all changes made to the c4 and c5 customers will be ignored by the GUI, and the will not know that the c4 object has been removed. Finally, changing the name of the first customer in the list will go unnoticed (remember that I'm changing the backing store and not the result set!). Additionally, the sort order won't be re-evaluated. You'll have to re-request the enumerator for the query variable to get it to update.
With the language extension that I've written (inspired by the work done at SLINQ, though this particular extension doesn't require a polling timer to see the data and has been written specifically to take advantage of standard classes already in the framework) you can see that not only are the additions made to the backing store reflected by the GUI, but the sort order specified by the query is also respected...live...without requerying.

I'll eventually be posting more of the code for the language extension once it gets tidied up. The names of the classes in the library are still kind of up in the air (they're currently too long), and I need to add support for group by and then come up with a bunch of test case LINQ queries to exercise the library with some edge cases. Once I get to that point, I'll post again with some more code. If anybody can think of a good name for this stuff (I'm thinking of calling it Dynamic LINQ Views, but I'm not sure), I'd apprciate the feedback.
Enjoy!
Hmm, since there's still no comment saying just that:
Congratulations, it seems you've understood the real world problem ;-)
Continue the good work, I might be really interested in your findings and
code if one day we move over to .net 3.5 (or higher) with our .net 2.0
solution.
Thanks.. I'm glad someone noticed the real-world solution I'm working on
here... it's going to come in immensely handy for future applications. The
code I've got is really quite basic, but once I get it a little shinier
I'll start posting the source to the language extension and the supporting
classes.
I think Anders and Microsoft owes you $5 for making LINQ worthwhile.
Thanks for alll your efforts! I'm really looking forward to using this!
Wow, that's really valuable for financial GUI's, where prices keep on
changing.
I extend with SQL Server Service Broker and call it "LINQ Sync".