Wednesday, May 23, 2007
Milos Training: many videos are now available online!
We've created a bunch of videos to help us training new team members on Milos, and we figured these videos should also be helpful for our clients who are using Milos, as well as for those who'd like to check out what kinds of things are part of Milos. Below is a list of the videos currently available, in the respective order they should be watched. We are going to have more videos coming up soon. Stay tuned!
If there are things that you really would like to see explained in videos, please send us an email, and we will definitely consider it.
Posted @ 1:48 PM by Lassala, Claudio (firstname.lastname@example.org) -
Wednesday, May 23, 2007
Improvements to the Business Object / Entity generator
One of our main tools in Milos has always been our Business Object / Entity generator, that's available within the EPS Developer Services tool. That feature had some crucial limitations, though, so we've revamped it, including a number of fixes and improvements that we are sure a lot of users are going to appreciate. I'll list some of them here:
- Integration with Visual Studio.
Generation of more than one collection at a time.
- Previously, the tool worked stand-alone, and the files generated had to be manually included in the project in VS. The current version works as a plugin, running within VS, and that way the files generated get immediately added to the project.
Creation of entities that aren't modeled from a SQL Server database.
- Previoulsy, one could only create one child collection and one xlink collection at a time. Now any number of collections can be added at a single pass.
Classes can be regenerated.
- Previously, one could only generate entities based on a table/view in SQL Server. Now we support the creation of an entity from scratch, and underlying data for this entity is stored in XML files. This works great for prototyping modules and creating unit tests without having a need to create tables on the database, which means the developer can concentrate first on the classes to create, and only worry about how to persist that on the backend later. We'll have another post dedicated to this subject soon. It's also worth pointing out that the underlying engine on the generator has gone through a big refactoring to decouple it from SQL Server so to support the entities based on XML data sources. This means that now we can more easily work on adding support to create entities based on other backends such as Oracle, MySql, etc.
Separation of class in individual files and partial classes.
- Previously, if the developer needed to add things to a class (say, more properties to a business entity), she'd need to run the tool again, creating the entity as if it were the first time, and then manually move pieces of code from the newly generated files into the existing ones. Yup, painful. The new tool saves the definitions of the objects created, which allows for the tool to recreate the entities without forcing the developer to remember to recreate it as it was done before.
- Previously the tool created one file for the business object class, and one file for the business entity class along with its collection and business item classes (collection items). The new tool creates a subfolder in the project, named after the business object (such as CustomerBusinessObject), and put the new files in that folder. Every class goes into its own separate file, and every class is also separated in two files (as a partial class): one for the code-generated content, and one for the custom code developers add to it. This means that the developer can regenerate code for business entities and objects without wiping out the custom code. Yeah, it was about time, right? ;)
The main point is: the new generator gives us everything we used to have, plus a LOT more, so this is definitely the way to go moving forward. There is only one thing we don't have on the new tool yet: it doesn't auto-update yet. Since it's a VS plug-in, we couldn't just use ClickOnce out of the box. So, if you're creating and working with business objects and entities, send me an email soliciting a copy of the plug-in, and I'll send it to you (of course, you have to be a Milos user to request it. My email is my first name at eps-software.com). I'll put you on my mailing list to receive updates, until we get the auto-update feature into the plug-in.
Also, the installation and usage of this plug-in has been documented in videos. I'll post a blog entry about this shortly. Make sure to check it out!
Posted @ 1:31 PM by Lassala, Claudio (email@example.com) -
Thursday, May 17, 2007
Data binding simplification
Milos controls support native data binding. It has been like that since the beginning of (Milos-) time. So for instance, if you want to bind a textbox to a property on a business entity, you can simply set up the binding by specifying the name of the entity and property you want to bind to, using the ControlSource property of the textbox.
This is pretty straightforward, but there is one somewhat tricky part to this: The member the control binds to is specified from the control's point of view. So for instance, to bind a textbox on a form to a business entity's "LastName" property (assuming the business entity is a member of the same form as is the case with the default Milos data entry forms), the ControlSource has to be set to "this.Parent.MyEntity.LastName". Or, one can also use the VB version: "Me.Parent.MyEntity.LastName". Both these versions are interchangeably and work regardless of the language you use. In fact, the "this/Me" part is completely optional. You could as well set the ControlSource to this: "Parent.MyEntity.LastName".
So far, so good. The tricky part is that if the textbox is inside another container, the hierarchy changes. For instance, if the textbox exists inside a tab page inside a tab control, the ControlSource needs to be set to this: "this.Parent.Parent.Parent.MyEntity.LastName". That is because the textbox exists inside the tab page (Parent), which exists inside a tab control (Parent.Parent), which exists inside the form (Parent.Parent.Parent), which has the entity (Parent.Parent.Parent.MyEntity).
It is perfectly logical, but it is also a common source of frustration, since it is a) easy to get the exact number of parent references wrong, and b) when the control moves into a different container, the path changes. We have now added a simplification to the expressions used by the ControlSource property. The approach described above is still perfectly valid, but the whole "parent" chain is now optional. So the ControlSource can now be set to "MyEntity.LastName", regardless of where the textbox (or any other control with a ControlSource property) lives.
The way this works is that Milos auto-discovers the hierarchy. It starts out with the current control. If it has a "MyEntity" member, then that is used for binding. If not, Milos goes to the parent control and looks for it there. It then continues up the chain until there is no parent anymore (typically at the window level). If no member of the specified name is found, a null reference exception is thrown.
So this is nice, because it simplifies things a lot. There are two downsides though: 1) This discovery is done at runtime and thus introduces runtime overhead. It is pretty fast, but if you have a form with hundreds of controls, you should probably consider specifying the full path (at least that is an optimization option in case you run into form startup performance issues). 2) The expression may be ambiguous. If you want to bind to the MainEntity on the form, but there happens to be another object in the chain that has a member of the same name, you may accidently bind to the wrong object. In those cases, one also has to specify the full path.
Some people say it is a bit odd to just bind to "MyEntity.LastName", because one cannot tell where MyEntity lives. To make things a bit clearer, you can use other pointers, such as "thisform", "class", or "container". They all mean the same exact thing, which is "diddly-squat". In other words: These keywords are ignored completely. But some think it is more readable to set the control source to "thisform.MyEntity.LastName", "Container.MyEntity.LastName", or "class.MyEntity.LastName". So go ahead and use these keywords if you feel they make things clearer. Be aware that they do not actually do anything. "thisform" for instance does not necessarily reference a form. It can also be used in panes for instance. It also does not solve the ambiguity problem, as the auto-discovery mechanism may still pick up "in between" object references from the hierarchical chain. (What do you think about this actually? I'd be interested in feedback on this...).
Anyway: Hopefully, this will simplify the task of specifying the control source expression.
Note: This feature has been implemented and has undergone almost complete testing, so it will most likely show up in the next Milos build...
Posted @ 11:48 AM by Egger, Markus (firstname.lastname@example.org) -
Saturday, May 05, 2007
Milos and O/R Mapping: Static and Dynamic Queries
Following the series about Milos ORM capabilities based on this article, up next is the support for Static and Dynamic Queries. Milos does support both.
Essentially, every business object class inherits an ExecuteQuery method. This method takes a Command object. The SQL statement can either be defined during compile-time, with only the parameters filled in at runtime, or any other part of the statement could be built on the fly (like the WHERE clause, or the ORDER BY clause). Below are some ways one could execute queries from within a Milos Business Object.
IDbCommand command = this.NewDbCommand("SELECT * FROM Customers");
DataSet results = this.ExecuteQuery(command);
Simple Query with Parameter
IDbCommand command =
this.NewDbCommand("SELECT * FROM Customers WHERE PK_Customer = @PK");
this.AddDbCommandParameter(command, "@PK", someGuid);
DataSet results = this.ExecuteQuery(command);
A few things to notice so far:
- We program to the ADO.NET interfaces such as IDbCommand, instead of programming to the concrete implementations, such as SqlCommand, or OracleCommand;
- We use Factory Methods to create things like the command object or the parameter objects (NewDbCommand and AddDbCommandParameter methods, respectively).
- The ExecuteQuery method wraps the calls out to the data services layer to deal with connections, data adapters, or anything else that is specific to the data service being used, which can be configured either during runtime or compile time.
- Since the command text (the SQL statement) is passed as a string, it can be built dynamically depending on some criteria gathered from the user/application. Of course, the database security will prevail, and if a dynamic SQL is created to access some database object that the current user does not have access to, and backend will block it.
Helper methods for simple queries
For some simple queries, one can use some of the helper methods provided by the Milos Business Object. The benefit of using such methods is that we let Milos decided whether to build the SQL statement to be sent to the database, or to call Stored Procedures instead (depending on how the business object is set to process commands). That makes it also easier to swap between using direct SQL statements and stored procedures, or even swap between using different backends, such as SQL Server or MySQL. See some examples below:
Getting all records from a given table
DataSet results = this.QueryAllRecords("Customers");
If the business object is set to process commands using "IndividualCommands", Milos will create the CommandText as SELECT * FROM Customers. On the other hand, if the object is set to use "StoredProcedure", Milos creates a CommandText such as "milos_getCustomersAllRecords". The prefix ("milos_") is configurable, and the other parts of the name of the stored procedure follow a naming convention so that Milos knows what store procedure to look for (we have tools that generate those stored procedures for us!).
The QueryAllRecords method also allows for some customization of what gets retrieved:
DataSet results =
this.QueryAllRecords("Customers", "PK_Customer, CustomerName", "CustomerName");
Milos would create the following statement for the call above:
SELECT PK_Customer, CustomerName FROM Customers ORDER BY CustomerName
Of course, if the business object is set to use Stored Procedures, then the list of fields and ORDER BY clause on the method call are ignored, and the query in the stored procedure dictates the results.
Retrieving a Single Record
DataSet result =
this.QuerySingleRecord("Products", "PK_Product, ProductName", "PK_Product", 10);
Milos translates the call above into this:
SELECT PK_Product, ProductName FROM Products WHERE PK_Product = 10
Retrieving Multiple Records by a given Key
DataSet results =
this.QueryMultipleRecordsByKey("Customers", "CustomerName", "State", "TX");
The call above is translated into this:
SELECT CustomerName FROM Customers WHERE State = "TX"
This helper method is also great to get all the children rows for a specific parent, like this:
DataSet results =
this.QueryMultipleRecordsByKey("Orders", "OrderDate", "FK_Customer", someGuid);
that translates into SELECT OrderDate FROM Orders WHERE FK_Customer = someGuid
On the next post, we'll talk a little more about Stored Procedures. Stay tuned!
Posted @ 11:37 AM by Lassala, Claudio (email@example.com) -