Monday, September 29, 2008

Passing Predicates into Compiled Queries

I've recently been looking at generating LINQ predicates on the fly in a mapping layer between a set of business domain entities and a set of related, but different, database entities.  One of the problems that I've encountered is to do with the way in which predicates are handled when using CompiledQueries.

To start off, lets consider the easy non-compiled version.  Here's a method:

// Get an employee by a predicate


static void GetEmployee(Expression<Func<Employee, bool>> predicate)


{


   // Just perform the select, and output the results


   using (DataClasses1DataContext context = new DataClasses1DataContext())


   {


      var results = context.Employees.Where(predicate);


 


      Console.WriteLine("Number of employees: {0}", results.Count());


   }


}




The usage of this is nice and simple, and the sort of thing in LINQ examples all over the web:





GetEmployee(e => e.EmployeeID == 1);




Calling this does exactly what you'd expect.  My next step was to look at how this approach could be used with compiled queries.  I started with a simple method:





// Get an employee by a predicate, using a compiled expression


static void GetEmployeeCompiled(Expression<Func<Employee, bool>> predicate)


{


   // Compile the query


   var compiledQuery =


      CompiledQuery.Compile((DataClasses1DataContext context) => context.Employees.Where(predicate));


 


   // and using the compiled query, output the results.  This crashes :(


   using (DataClasses1DataContext context = new DataClasses1DataContext())


   {


      var results = compiledQuery(context);


 


      Console.WriteLine("Number of employees: {0}", results.Count());


   }


}




Obviously, this is pointless since it just recompiles the query every time.  But let's ignore that small fact - it should, after all, still work.  Alas, it doesn't. 



At the point where the results are enumerated, it explodes with a "NotSupportedException".  Specifically, it fails due to an "Unsupported overload used for query operator 'Where'.".  Looking at the expression tree that is being compiled, in conjunction with some help from Reflector to look at what LINQ is doing under the cover, it can be seen that the issue is down to how the predicate is included in the final query expression. 



Remember, the compiler is not generating executable code here, it is just building a lambda expression.  When it sees the parameter to the Where() method, it has little choice but to be "lift" this variable its own class, and it is a property on this lifted class that is passed as a parameter to the Where() method.  Although the non-compiled version handles this just fine, it causes the CompiledQuery object to barf.  This is just the same as any other query that uses a local variable or parameter.



I've experimented with a number of ways of constructing the query that I'm trying to compile, but all ultimately end up with the same problem.  The solution I've found is a little nasty, but it does work.  If I've missed a cleaner way, then I'd love to hear about it!



Anyhow, the solution.  It is based on the fact that it is the act of "passing" the predicate into the Where() method that is the problem.  So the solution is to not pass in the predicate, but instead pass in some dummy predicate.  Then do some expression tree walking to swap out the dummy predicate for the real one.  The code looks like this:





// Get an employee by a predicate, using a compiled expression


static void GetEmployeeCompiled2(Expression<Func<Employee, bool>> predicate)


{


   // Setup the required query, using a dummy predicate (c => true)


   Expression<Func<DataClasses1DataContext, IEnumerable<Employee>>> compiledExpression =


      context => context.Employees.Where(c => true);


 


   // Dig out the dummy predicate from the expression tree created above


   Expression template = ((UnaryExpression)((MethodCallExpression)(compiledExpression.Body)).Arguments[1]).Operand;


 


   // Swap out the template for the predicate


   compiledExpression = (Expression<Func<DataClasses1DataContext, IEnumerable<Employee>>>) 


                                 ExpressionRewriter.Replace(compiledExpression, template, predicate);


 


   // Compile the query


   var compiledQuery = CompiledQuery.Compile(compiledExpression);


 


   // and using the compiled query, output the results.  This works :)


   using (DataClasses1DataContext context = new DataClasses1DataContext())


   {


      var results = compiledQuery(context);


 


      Console.WriteLine("Number of employees: {0}", results.Count());


   }


}




So the required query is itself stored as an expression, with a dummy predicate (c => true) used to get the correct "shape" of tree.  This predicate is then located and the expression tree is rewritten, swapping out the dummy predicate for the real one.  This new query expression then compiles and executes just fine.



For completeness, the ExpressionRewriter class is defined as:





class ExpressionRewriter : ExpressionVisitor


{


   static public Expression Replace(Expression tree, Expression toReplace, Expression replaceWith)


   {


      ExpressionRewriter rewriter = new ExpressionRewriter(toReplace, replaceWith);


 


      return rewriter.Visit(tree);


   }


 


   private readonly Expression _toReplace;


   private readonly Expression _replaceWith;


 


   private ExpressionRewriter(Expression toReplace, Expression replaceWith)


   {


      _toReplace = toReplace;


      _replaceWith = replaceWith;


   }


 


   protected override Expression Visit(Expression exp)


   {


      if (exp == _toReplace)


      {


         return _replaceWith;


      }


      return base.Visit(exp);


   }


}




where the ExpressionVisitor base class can be found on MSDN

Tuesday, September 23, 2008

Unsafe code without the Unsafe keyword

I've been playing around with some code lately that uses dynamic method generation fairly extensively.  In the course of doing so, I've written the odd dodgy bit of IL out.  Interestingly, a couple of time I got some very strange results when assigning fields from one object to another - specifically, if I got the types mismatched I just got garbage in the destination rather than some form of Cast exception (which I'd expect the runtime to generate during execution) or Verification exception (which I'd expect when I finally surface my generated method through a call to DynamicMethod.CreateDelegate()).

Finally had some time today to take a closer look, and the results are very interesting and not at all clear from the documentation.  Specifically, if you create a dynamic method using the following constructor:

public DynamicMethod(


    string name,


    Type returnType,


    Type[] parameterTypes,


    Module m


)




and pass in "Assembly.GetExecutingAssembly().ManifestModule" for the module, then it appears that all type safety within the generated code is turned off.  i.e., you can pretty much assign anything to anything.  The following code, for example, enables you to dump the memory address of any reference type:





/// <summary>


/// Return a method that gives the memory address of any object


/// </summary>


static Func<object, int> Get_GetAddress_Method()


{


   DynamicMethod d = new DynamicMethod("", typeof (int), new Type[] {typeof (Object)},


                                       Assembly.GetExecutingAssembly().ManifestModule);


 


   ILGenerator ilGen = d.GetILGenerator();


 


   ilGen.Emit(OpCodes.Ldarg_0); // Load arg_0 onto the stack (of type object)


   ilGen.Emit(OpCodes.Ret);     // And return - note that the return type is an int...


 


   return (Func<object, int>)d.CreateDelegate(typeof(Func<object, int>));


}




You can use this in the following way:





Func<object, int> getAddress = Get_GetAddress_Method();


const string greeting = "Hello";


 


// Get the address of the "Hello" string


int x = getAddress(greeting);




x now contains the memory address of the string "Hello".  So what?  Well, you can also write a method like this:





/// <summary>


/// Return a method that "maps" any type to a particular memory location


/// </summary>


static Func<int, T> Get_ObjectAtAddress_Method<T>()


{


   DynamicMethod d = new DynamicMethod("", typeof (T), new Type[] {typeof (int)},


                                       Assembly.GetExecutingAssembly().ManifestModule);


 


   ILGenerator ilGen = d.GetILGenerator();


 


   ilGen.Emit(OpCodes.Ldarg_0);  // Load arg_0 onto the stack (of type int)


   ilGen.Emit(OpCodes.Ret);      // And return - note that the return type is T


 


   return (Func<int, T>)d.CreateDelegate(typeof(Func<int, T>));


}




This chap lets you take any memory address, and "pretend" that an object of type T resides there.  So you can do something like this:





Func<int, byte[]> getData = Get_ObjectAtAddress_Method<byte[]>();


 


// Get a byte array on the same location


byte[] data = getData(x);




where x is a memory location that you've acquired previously.  It doesn't matter if the type that really resides at address x is a byte[] or not.  This basically lets you get access to the whole address space within your AppDomain (and possibly the whole Win32 process) and write whatever you like into it. 



This seems plain wrong to me - I haven't specified the "unsafe" keyword anywhere, nor is this code built with the "Allow unsafe code" box checked.  Without jumping through those hoops, I should not be able to write code like this.  I'll concede that this only works in a full trust environment, but it still smells like a very serious hole in the type safety of .Net.  Interestingly, if you use the DynamicMethod constructor that doesn't take a Module parameter, then everything works as you'd expect - you are politely served a VerficationException when you try to compile the method.  According to the docs, the constructor overload that takes a module is only supposed to allow access to internals of the specified module, not to skip type safety.  I wonder if the implementation of DynamicMethod in that scenario is flawed.



Below is a big lump of code - it compiles and shows the issue quite clearly.  I'd be interested in your views on whether this is a bug or "by design". If the latter, what exactly was the scenario that they were designing for?





using System;


using System.Reflection;


using System.Reflection.Emit;


using System.Text;


 


namespace ConsoleApplication1


{


   class Program


   {


      static void Main()


      {


         // Get some methods generated...


         Func<object, int> getAddress = Get_GetAddress_Method();


         Func<int, byte[]> getData = Get_ObjectAtAddress_Method<byte[]>();


 


         const string greeting = "Hello";


 


         // Print the greeting


         Console.WriteLine(greeting);


 


         // Get the address of the "Hello" string


         int x = getAddress(greeting);


 


         // Get a byte array on the same location


         byte[] data = getData(x);


 


         // Change some data...


         SetString("Bye!!", data);


 


         // And display the greeting again (remember, strings are immutable...)


         Console.WriteLine(greeting);


 


         // And just to show it against other bits of the framework...


         Console.WriteLine(Assembly.GetExecutingAssembly().FullName);


 


         SetString("Hacked!", getData(getAddress(Assembly.GetExecutingAssembly().FullName)));


 


         Console.WriteLine(Assembly.GetExecutingAssembly().FullName);


      }


 


      /// <summary>


      /// Return a method that gives the memory address of any object


      /// </summary>


      static Func<object, int> Get_GetAddress_Method()


      {


         DynamicMethod d = new DynamicMethod("", typeof (int), new Type[] {typeof (Object)},


                                             Assembly.GetExecutingAssembly().ManifestModule);


 


         ILGenerator ilGen = d.GetILGenerator();


 


         ilGen.Emit(OpCodes.Ldarg_0); // Load arg_0 onto the stack (of type object)


         ilGen.Emit(OpCodes.Ret);     // And return - note that the return type is an int...


 


         return (Func<object, int>)d.CreateDelegate(typeof(Func<object, int>));


      }


 


      /// <summary>


      /// Return a method that "maps" any type to a particular memory location


      /// </summary>


      static Func<int, T> Get_ObjectAtAddress_Method<T>()


      {


         DynamicMethod d = new DynamicMethod("", typeof (T), new Type[] {typeof (int)},


                                             Assembly.GetExecutingAssembly().ManifestModule);


 


         ILGenerator ilGen = d.GetILGenerator();


 


         ilGen.Emit(OpCodes.Ldarg_0);  // Load arg_0 onto the stack (of type int)


         ilGen.Emit(OpCodes.Ret);      // And return - note that the return type is T


 


         return (Func<int, T>)d.CreateDelegate(typeof(Func<int, T>));


      }


 


      /// <summary>


      /// Little helper method to copy a string into a byte[]


      /// </summary>


      static void SetString(string requiredString, byte[] dest)


      {


         UnicodeEncoding encoder = new UnicodeEncoding();


         byte[] requiredBytes = encoder.GetBytes(requiredString);


 


         // Need to do the copy by hand, since Array.Copy bleats


         // about the dimensions of the destination.  No surprise really,


         // since the destination isn't really an array...


         for (int i = 0; i < requiredBytes.Length; i++)


         {


            dest[i] = requiredBytes[i];


         }


      }


   }


}


Monday, September 15, 2008

Debugging Services

We all know the problem - to debug a program (particularly if it's the startup procedures that you need to look at), you simply load the solution in Visual Studio and hit F5.  Except if it's a service.  With a service, you can't just run it but instead it needs to be launched via the Service Control Manager, which makes debugging its startup a real pain.

The solutions that I've used in the past have either been to have a command line option to enable the process to launch as a regular process as opposed to being control by the SCM, or to have it sleep for a number of seconds when it is launched.  Either of these gives me a route to get a debugger attached before anything "interesting" happens.  But both of these also mean I've got code present that isn't going to be in the live, and I much prefer to be debugging "the real thing" rather than some (albeit close) approximation.

But there's a third way, which I'd not heard of before - there is a registry setting for "Image File Execution Options" which, amongst other things, allows you to specify that when an app is launched (via CreateProcess(), which covers most scenarios), instead of just firing up the exe it instead runs the required debugger.  Nice :)

For more details, check out this blog on the subject.  There's also this entry which describes a few more of the settings that are available.

Monday, September 08, 2008

Naming Tests

Following on from Hadi's post, I found this recent blog on the same topic.  It says pretty much the same thing, and acts as a good re-enforcement to the general point, which is to give your tests good names so that the next chap who looks at the code understands what the code being tested is supposed to do.  It's such a valuable addition to the project documentation.

Exactly which form you choose isn't really that important, providing that it is both descriptive and consistent.  What do you use for your test names?

Thursday, September 04, 2008

More on the Chrome EULA

Google obviously read my blog* and caved in without a fight.  Clause 11 in the EULA has now been changed to:

11. Content license from you

11.1 You retain copyright and any other rights you already hold in Content which you submit, post or display on or through, the Services.

Much better.

* Ok, perhaps it wasn't just my blog that did it :)

Wednesday, September 03, 2008

Chrome EULA

There's an interesting clause in the EULA for Chrome:

11.1 You retain copyright and any other rights you already hold in Content which you submit, post or display on or through, the Services. By submitting, posting or displaying the content you give Google a perpetual, irrevocable, worldwide, royalty-free, and non-exclusive license to reproduce, adapt, modify, translate, publish, publicly perform, publicly display and distribute any Content which you submit, post or display on or through, the Services. This license is for the sole purpose of enabling Google to display, distribute and promote the Services and may be revoked for certain Services as defined in the Additional Terms of those Services.

where Services is defined as:

1.1 Your use of Google’s products, software, services and web sites (referred to collectively as the “Services” in this document and excluding any services provided to you by Google under a separate written agreement) is subject to the terms of a legal agreement between you and Google. “Google” means Google Inc., whose principal place of business is at 1600 Amphitheatre Parkway, Mountain View, CA 94043, United States. This document explains how the agreement is made up, and sets out some of the terms of that agreement

Chrome is, I believe, a Google product, and so falls into the definition of Services.  Hence, according to the EULA, Google can do pretty much anything with any information that you "submit, post or display".  I suspect that this is a mistake on their part, and that they've just cut'n'pasted a little to much from the EULA's for some of their other services.  It's certainly at odds with the privacy policy for Chrome.

However, if you're worried about such things, then I'd suggest that you don't use Chrome for anything sensitive.  Note that this licence only applies to the executable installation; if you download the source and build it yourself then you are covered by a regular Open Source licence.  It'll be interesting to see how long it takes them to re-word this...

U.S. Employer Identification Number (EIN)

Any non-US company that wishes to sell iPhone applications in the US via the AppStore needs to obtain an Employer Identification Number to complete the contract details with Apple.  The Apple site is not particularly helpful with regard to how this is achieved, and simply points you at the IRS Form SS-4 PDF.  You can fill this out and send it off, but my understanding is that if you do so it will be several weeks before you receive your EIN. 

Alternatively, try dialling this number: +1-215-516-6999 - I just did, and after a few minutes with a very helpful chap I received my EIN. Fast and easy, just how I like it :)

Google Chrome

No doubt this is old news already, but for those that haven't seen it there's a cartoon strip that describes the new google browser here (although it's an odd format, it's actually quite a good read).  The browser itself is available for download here.  No Mac or Linux support yet, but supposedly that's on the way.

My first impressions are pretty good - it's seems to be fairly snappy and correctly renders most of the pages that I use.  I like the UI - it's nicely un-cluttered, and having the tabs right at the top seems to work well. 

Tuesday, September 02, 2008

Pseudo-Predicates in Specifications

Following on from my previous blog entry in which I asserted that I don't normally bother just referencing other people's blog, here's another reference :)

It's quite a long article, but well written and definitely makes it's point.  To anyone writing specifications, and to anyone reading them (which I think covers just about everyone who's reading this!), it's well worth a read:

Tasty Beverages

It's talking about predicates in the context of security, but I think the lesson is actually broader than that - I don't think it's all that unusual to see pseudo-predicates in pretty much any form of specification, and the danger with them is that the human brain is pretty adept at filling in what it thinks is missing (hence, they can be hard to spot).  Alas, what the brain makes up isn't always what the author of the spec was thinking, leading to the wrong thing being developed.

Friday, August 29, 2008

Enable SSH on ESXi

Don't normally bother writing blogs that just reference another blog, but this one gem me a while to find, so if only for my own reference I'm copying it here:

  • Go to the ESXi console and press alt+F1
  • Type: unsupported
  • Enter the root password
  • At the prompt type “vi /etc/inetd.conf”
  • Look for the line that starts with “#ssh” (you can search with pressing “/”)
  • Remove the “#” (press the “x” if the cursor is on the character)
  • Save “/etc/inetd.conf” by typing “:wq!”
  • Restart the management service “/sbin/services.sh restart”

    Original entry was here, nice one :)

  • Thursday, August 28, 2008

    Targeting .Net 2.0 from VS2008

    Following on from the previous post about old assemblies being updated with the latest .Net 3.5 service pack, I thought it also worth mentioning the other gotcha that can happen when targeting old versions of .Net from VS2008.

    If you have a project that is set to target .Net 2.0, VS will dutifully only allow you to add 2.0 assemblies to the project, ensuring that you don't accidentally make use of 3.0 or 3.5 framework features that perhaps aren't installed on the machines of your target audience.  However, VS will still use the latest compiler to compile the code, hence you can make use of 3.0 language features such as var, lambdas etc.  These work, even on a box with just the 2.0 framework installed, because they are all handled via compiler magic - there was no change to the resulting IL to support the new 3.0 language features.

    Now this can either be a good or a bad thing.  Good, because you can start making use of the new language tools without requiring your customer to upgrade anything.  Why bad?  Three reasons I can think of:

    • For a big project, switching compilers is not a simple decision.  Your app has all been fully tested, both internally and through man-years of usage in the field, but by switching the compiler, you have (potentially) just changed every line of code.  The only sane thing to do would be a full regression test of the whole system - who's to say that the new 3.0 compiler doesn't have some bugs in it, or (equally likely, I think), it fixes some bugs that were present in the 2.0 compiler, but which your code unknowingly relied on to function correctly.
    • If you start using 3.0 features on your dev machines, don't forget that you'll also need to upgrade your CI build servers.  If they are only building your project, that might be an easy choice.  If they are shared by several teams, that may be more difficult.
    • If you ship code to the customer rather than binaries, you need to ensure that your customer is also happy to upgrade from VS2005 to VS2008, otherwise they won't be able to build your code anymore.

    What can you do?  Well, if you're in the "it's a good thing" camp, then just get coding.  If the bad things are important to you, then the bad news is that there's not much you can do on your development machines to help (other than go back to VS2005!).  What you can do is to ensure that on your CI build machines you specify the TargetFramework option to MSBuild.  This forces MSBuild to use the appropriate versions of the compiler and other tools.  See this blog for more details. That way, if a dev uses some 3.0 language feature, it will cause the CI build to fail and hence get spotted nice and quickly. You don't have a CI server?  Shame on you.

    Wednesday, August 27, 2008

    There have been a number of posts about the changes introduced in .Net 3.5 SP1, but this is one I've not seen before.  I'm not sure that the title, "breaking changes" is entirely correct, but it probably got your attention :)

    In .Net 3.0, there was a new class, System.Collections.ObjectModel.ObservableCollection<T> (I'm sure you can guess what it does).  It had two constructors, ObservableCollection<T>() and ObservableCollection<T>(List<T>).  Clearly the guy who wrote it was having a bad day - List<T> as a constructor parameter?  What's all that about?

    This stayed the same in .Net 3.5, but changed with .Net 3.5 SP1.  Now, ObservableCollection<T> has an additional constructor, ObservableCollection<T>(IEnumerable<T>) - i.e., the one that they should have had in the first place instead of List<T>.  All is good in the world.

    Now there's a nice feature in VS2008 whereby you can indicate in the project properties which version of .Net you are building for.  This means that VS makes sane choices as to which assemblies you can reference, meaning that the code you write should run just fine on a machine that's only got old versions of the framework, even if you've got all the latest spanky stuff installed.  (There's another issue to do with the compiler, but I'll cover that in another blog).

    Here's the problem.  If you have .Net 3.5 SP1 installed, and you create a project that targets 3.0, VS will happily only add 3.0 references.  Including the reference to the assembly (WindowsBase, if you care) that contains ObservableCollection<T>.  Which SP1 upgraded. So you can write code that takes advantage of the new constructor, such as:

    ObservableCollection<int> x = new ObservableCollection<int>(new int[] { 1, 2 });

    which will compile and run just fine.

    When you finally complete all your coding and have all your tests passing, you pass it to your customer confident in the knowledge that you've done a good job.  He installs and runs the code, and the very first impression he gets of your work is a MethodNotFound exception, since you're calling a constructor that he doesn't have on his machine.

    So what do you do?  There's not much you can do on your development system, other than be careful, which isn't much help.  What you can, and should, do is to make sure that your build server and test environments exactly match the minimum that you expect your code to be running on.  That way, a problem like the one above will get spotted during the development process where it can be easily fixed.  Note that it's probably a good idea to turn off automatic updates on build & test machines, otherwise you'll probably end up getting stuff pushed down that you didn't want.


    [Update]

    Just found out that FxCop has a rule to detect and warn on the usage of such methods - see Brad Abrams blog posting on the subject here.  That helps no end :)

    Monday, July 14, 2008

    Default Gateway of 0.0.0.0 in Vista

    Strange one just occurred - woke up my machine from sleep, and plugged in the various cables in no particular order.  Everything looked good, except no network access.  Could ping the local network (including the gateway), but nothing outside of that.  Everyone else on the same subnet was seeing external IPs just fine. 

    A quick investigation with IPConfig showed that I had two default gateways, once with the correct address but another with an address of 0.0.0.0.  And it was the one that was first in the list.

    No idea where it came from - Vista does use that address if you are using dial-up networking, but I wasn't (although my mobile was plugged in through USB to charge it, so perhaps it is related in someway).  Anyhow - that was clearly the problem, so it was just a case of getting rid of the rogue gateway address.

    Tried "ipconfig /renew".  Tried "ipconfig /release" then "ipconfig /renew".  Tried unplugging network cables, mobile phones etc.  No luck.  Reboot was looking likely.  Last attempt was to use netsh.  Success :)

    For those of you who may get stuck in a similar situation, here's the netsh rune that you need to cast:

    netsh interface ipv4 delete address "Local Area Connection"
    addr=a.b.c.d gateway=0.0.0.0

    where a.b.c.d is your local IP address.  netsh can do just about anything to your local network config - well worth some exploration if you're *really* bored.

    Friday, June 20, 2008

    You can find this on a few sites, but most didn't have everything that I needed.  So for future reference, if you want to talk to a Sybase SQL Anywhere network server using ADO.NET, you need a connection string that looks something like this:

    "ENG=[your server name];DBN=[your database name];LINKS=tcpip(Host=[server ip addr]; ServerPort=[port]);UID=[username];PWD=[password]"

    Obviously, swap the stuff in square brackets for values that make sense in your environment.

    Thursday, June 19, 2008

    Crashplan Restore

    Here's the scenario - you've read the previous post and installed the Crashplan stuff.  Several weeks pass, and everything has ticked along nicely.  Your backup is sat there with many gigs of data on a remote drive.  Vista "does its thing", so you decide it's time for the bi-annual reinstall of the OS.

    When you install Crashplan again, alas it gives you a new 'identity' and reports that you've got a whole pile of stuff to backup.  Grrr.  It's my only complaint so far with the software - there's no easy way to tell it that this is just a reinstall and that it really doesn't have to start from scratch.

    Fortunately, there is a way.  Add the following key to your registry before installing Crashplan:

    HKLM\Software\CrashPlan\Identity

    In there, add the following values:

    • email, REG_EXPAND_SZ, [your email address]
    • guid, REG_EXPAND_SZ, [your Crashplan machine guid]
    • orgName, REG_EXPAND_SZ, "CrashPlan"

    If you don't know your Guid, whoever you're backing up to will be able to tell you from their CrashPlan UI.

    After that, install CrashPlan and it should work out who you are.  It worked for me :)

    BTW, if you're on Vista64, the reg key needs to be HKLM\Software\Wow6432Node\CrashPlan\Identity.  That took a few minutes to work out :)

    Crashplan

    Checkout www.crashplan.com - it's a relatively cheap way of doing offsite backups that doesn't involve your data sitting off in some nameless datacenter 1000's of miles away.  All you do is get together with one or more friends and buy a couple of external drives; a couple of you then host the drives, and everyone else uses the Crashplan software backs up across the Internet.  What's really sweet is that for the initial backup (which could be many gigs), you can take the drive to your house and seed it locally; this was, the only traffic going over the Internet is just the deltas from the point that you seeded, so hopefully nothing too huge.  Of course, if you're doing a big photo / movie import or something, you can always grab the dive back off your mate and let it sync up locally.

    The other nice touch is in the event of a disaster that means you need the backup - rather than waiting several weeks to download it, you can just grab the drive and take it home.

    I've been running it for several weeks now, and it seems to be working well.  You have to buy the software, but it's a one-off purchase and it's not megabucks.

    Friday, June 13, 2008

    Tracepoints in VS2008

    There's a really useful feature in VS2008 that allows you to insert a Tracepoint on a line of code.  It's rather like a breakpoint, but instead of halting the execution it simply outputs the Tracepoint expression to the output window.  See this for more details.

    Thursday, June 05, 2008

    Here's a link explaining how to do a full download of Visual Studio 2008 SP1 Beta; Useful if you're going to be installing it more than once...

    Sunday, November 18, 2007

    It's been a long time since the last entry - I'm still around, just been busy with a big development project lately. Hopefully my part will be done in a few more weeks and then I should have some more time to write.

    Thursday, February 01, 2007

    I've finally got my ADSL connection up and running again! I've been here in Spain for about 2 1/2 years now, and I've had ADSL pretty much from day 1 - however, a while back there were a whole string of ISPs being bought out by others, which meant that my provider kept changing. They actually did quite well for a while at keeping me online, but finally (and predicably!) it all went pear shaped.

    After a bit of research, I chose Spantel for my new contract - it's taken a while, since the last provider hadn't released my line, but at last I'm online properly again :) I've been on the phone to Spantel a number of times over the course of this to get progress reports etc, and they've always been pretty quick at picking up and providing useful answers. When the kit arrived this week, it turns out that there was also a problem on my line - again, Spantel tech support have been great, organising the Telefonica engineers to get it sorted.

    So "Well Done" to Spantel - let's hope the good service continues.