Any (Windows user) CLI enthusiast should already be aware about PowerShell, MS latest bet in this area.
I must confess that even being aware about it I haven't had the time to use it other than for really simple tests. You know,
most of the time we need to get something done instead of learning a new set of shell commands, syntax, philosophy, etc.
But this changed recently due to some cool guys asking about how they could use Db4o to store PowerShell objects.Now
I have the motivation (and some allocated time) to pursue this goal, cool!
I do suggest that you give PowerShell a serious try (this way you'll gain some familiarity with it) before you continue to read this post.
Have you gotten yourself comfortable with PowerShell already? Ok, grab the source code for this post and have fun :) (svn co https://source.db4o.com/db4o/trunk/sandbox/Adriano/Blogs/PowerShell/)
My first step was to download PowerShell 2 (CTP 3) and install it. Then I just took a sample from the powershell community, compiled
and installed the new cmdlet.
Well, "just" is an euphemism of course. It took me some time to understand what was required, so, in order to make it simple to you, I'll give a short explanation about what's required:
Now we are ready to run our cmdlet.PS C:\Users\adriano> select-db4o-object
Once I get past this step I just tried to store / query a simple POCO (defined in the same project) using Db4o and it just worked ®.
Since it worked flawlessly I started to dig into how to define objects dynamically inside PowerShell as this could pose problems for Db4o. After some research
I found the add-member cmdlet, which is used to dynamically add members to objects. So, the next thing I've tried was:
PS C:\> $obj = ""PS C:\> $obj = $obj | add-member -membertype noteproperty Name Adriano -passthruPS C:\> $obj = $obj | add-member -membertype noteproperty Country Brazil -passthruPS C:\> $obj = $obj | add-member -membertype scriptmethod Print {$this.Name + ": " + $this.Country} -passthru
This creates a new object with properties Name and Country and a Print() method .
Then I've tried to persist this object into Db4o and got an exception back saying that some collection object could not be stored in Db4o because it had no usable public constructors (a constructor that doesn't throw when called with default values (null for references, 0 for int, etc.). As explained here, some classes (for instance collections) require their constructor to be called in order to get a valid object
so this was a show stopper for me. Again, after some investigation I realized that I could write a custom typehandler and persist only PSObject's "interesting" bits. At least for the basic
scenario it worked fine:
PS C:\> add-db4o-object -DatabasePath test.odb -Item $objPS C:\> $t = select-db4o-object -DatabasePath test.odb PS C:\> $t.Print()Adriano: Brazil
(note that the database file [in this sample, test.odb] will always be created / looked up in the user home folder, i.e, $env:homepath) (of course you can change this behavior passing a full path to Db4o)
Basically this typehandler just acts as a translator, creating (and storing) an instance of PSObjectWrapper (a more Db4o friendly class) for each PSObject. When reading it just goes trough the inverse path: it reads a PSObjectWrapper from the database, instantiate a new PSObject and set its properties based on the just read object; finally it returns this new object as if it was read from the database.
What is still missing in this cmdlet is a way to query for objects in the database and to handle multiple references to an object correctly but I'll leave this as an exercise for the reader ;)
Let me know if you have ideas, suggestions or questions.
Best
Adriano