ECO III: Using IOclService

By: Holger Flick

Abstract: ....or: The story about IObject and IElement.

    Introduction

It is rather easy to find ECO Objects in the ECO Space using ExpressionHandles. It is even easier when using WinForms applications and bind the result to a DataGrid or any other visual control. The developer does not need to handle any objects as the result of the expression is shown in the visual control immediately.

However, the need to query the ECO Space using code might arise. Yes, we know there is the OclService, which has a method called Evaluate that executes an OCL Query on the ECO Space. The result values of this operation are documented rather briefly - not to say insufficiently. I will show you:

  • how to handle different types of result values that you might encounter and
  • how to handle null-values that can occur

    Getting a reference to the OclService

As already mentioned, we want to use the OclService to retrieve information from the ECO Space. I assume that there is a global variable named "EcoSpace" that refers to the currently active ECO Space. To get a reference to the OclService the following line of code will help you:

IOclService oclService = 
EcoSpace.GetEcoService( typeof( IOclService ) ) as IOclService;

In ECO III it is also possible to use a helper-class named "EcoServiceHelper". This class has a method named "GetOclService", which takes one parameter of type System.Object. This parameter might be a reference to an ECO Space, an object of the ECO Space etc. ECO will return the correct OclService that will evaluate expressions on the ECO Space that the parameter belongs to. The next example shows how to get the OCL Service by passing the reference to an ECO Space:

IOclService oclService = EcoServiceHelper.GetOclService(EcoSpace) 

    A detailed look at Evaluate

oclService is an instance of the ECO OclService. There is a method called "Evaluate" that is overloaded many times. I will only list two basic overloads in this aritcle. The easiest way to use it, is simply to use an OCL String Expression as the only parameter and query the ECO Space with it:

IElement myResultA = oclService.Evaluate( "Content.allInstances->size" );

The thing I never understood at first is the fact why "IElement"? Sometimes you see examples on the internet that list "IObject". The reason for that is easy. Having a look at the next class diagram you can see the meaning that IElement has. IElement is an interface that is never used directly, but other interfaces derive from it. Two of these are the sub-interfaces named IObject and IPrimitive. This makes it very clear that it will fail sometimes when you cast the return type of Evaluate to IObject all the time. In this very article I will show you how to distinguish between IPrimitive and IObject.

Hide image
model

Let's look at the expression I use in the example above - performance aspects aside. I get the number of instances of the class Content inside my ECO Space. The result of "Evaluate" is a number. A number in programming is an "integer", "int" in C#. Is it an object? Yes, it is, but we have to consider ECO (it is mapped to System.Int32 by C# and that class is derived from System.Object). In ECO any type that is not a domain class in your ECO model has to be cast to IPrimitive. Examples for these are "string", "int" and "double". However, this is only the case if you have one single return value. If you get a list of integer values because of the evaluation, it is a different case. I will get to that in the next section. The following example will make it clear:

IElement myResultB = 
  oclService.Evaluate( "Content.allInstances->first" );

The expression above yields the first object of the class "Content" - leave aside which object that might be, as the order of the objects inside an ECO Space is not supposed to be an issue here. The datatype of this clearly is "Content". Is it an object? Yes, it is. It is also a class in our ECO model. So, there you have it. The very first fundamental difference that might occur when using "Evaluate" this way: when you are allowed to use "IObject" (...and that already is the difference). "IObject" may only be used when an object (mind the singular!) of an ECO Space is being returned. Thus, the second line of the example above might be rewritten as:

IObject myResultB = 
  oclService.Evaluate( "Content.allInstances->first" ) as IObject;

We have the basics covered already. But we also need to dive from the "IElement world" into the ".NET world", so that you can access your objects or your data type. Because right now, you only have dealt with instances of IElement or IObject and those do not help you much. I start with the easy one first, the object of "Content". Every IObject has a property named "AsObject", which it inherits due to IElement. This way you can simply write the next line in order to cast to the Content class:

Content myContent = myResultB.AsObject as Content;

That single statement alone is not safe though. You need to check myContent for "null" after casting!

Content myContent = myResultB.AsObject as Content;
if ( myContent != null )
{
   //* do some work with Content... *//
}

The question I had reading that was: "myResultB might be null, too!?". My answer to that is simply "No.". The ECO developers made sure that "myResultB" in that case always is not equal to null, so that you can check "AsObject".

Now to the first example with myResultA being an IElement. To say the least: It has to be implemented differently. In that case you cannot access "AsObject", because it is not guaranteed that "myResultA.AsObject" is unequal to null. Thus, you need to declare myResultA as IPrimitive or you cast it directly like this:

int myInt = 
  (int)oclService.Evaluate( "Content.allInstances->size" ).AsObject;

After that you will have not have to check myInt if it is equal to null before you use it. ECO makes sure that if there is no matching instance, the value of myInt will simply be "0".

Jonas Högström mailed me an excellent example that makes it very clear, why ECO has to deal with integers in this manner:

Content.allInstances->select(false)->first.title

Considering that "select(false)" never will select any instance, "first.title" would trigger an exception if it would be "null" or something similar. The ECO developers took the easy route and made sure that this expression will evaluate just fine and you can handle the return type accordingly. You do not have to think about the fact that your OCL might become "null" (or some OCL-equivalent) at any position.

    Result sets with more than one item

It is good what we learned so far. But right now, the result "sets" only are trivial, they contain exactly one or no element at all. It is important to note one thing right from the start: When you evaluate an expression that might contain more than one element ECO will never return an IObject or an IPrimitive. Have a look at the following expression:

expr = "Content.allInstances"

Even if there is only one instance of Content in the ECO Space, ECO will always return a list of elements. Thus, we need to use evaluate like this:

IObjectList myList = oclService.Evaluate( "Content.allInstances" ) as IObjectList;

Then we can get a reference to all the objects returned using a foreach loop, e.g.

foreach (IObject o in myList)
{
  Content c = o.AsObject as Content;
  // do something with c
}

That would give us IObject-s, which we can transform to our own types as above. This requires a cast as above using the AsObject-property. You might have noticed that I do not check for "null". This is not necessary, because the loop will simply never be entered if the result set is empty. Furthermore, you can use the "Count" property of IObjectList if you need to know the number of objects that have been returned.

Finally, I have to show you how to retrieve multiple values of IPrimitive. Consider this:

IElement myIntList = oclService.Evaluate( "Content.ageInDays" );

Let us assume that the attribute "ageInDays" is of type "integer". Thus, this expression will retrieve a list of integers. You can cast the result to IElementCollection, an interface which implements IElement and ICollection. As it implements IElement, you can use AsObject to step from the "ECO World" into the ".NET World" again. You only need to cast the attribute correctly. In this case, AsObject will deliver an ICollection. Thus, we get the following lines of code in order to access the list of integers:

IElementCollection myIntList = oclService.Evaluate( "Content.ageInDays" ) as IElementCollection;
ICollection myCollection = ( (ICollection)myIntList.AsObject );

You might have guessed by now, that if there are no entries retrieved by the expression being used, the collection "myCollection" will simply be empty, but you will not have to watch out for "null" at any point using the code above. Use "myCollection.Count>0" to check if there has been at least one return value. The items of this collection are of the type in the ".NET World" you are interested in, "int" in this case. Thus, you can cast every item of "myCollection" to "int" to retrieve your results.

The following schema sums up everything you have seen in this section. It might be quite handy when starting to use the OclService in code.

Hide image
worlds

    OclService and the context for the expression

As you know, the result of an OCL query depends on the context it is being executed in all the time. The term "context" is sometimes a little bit difficult to understand. You might also define the context in ECO as the "starting point" or "root node" (depending on how you imagine a model in your mind). I will give you a very basic example. The model used in this article has a class named "Content". If I set the context to no element of the ECO Space in particular, I can execute statements like "Content.allInstances->select(...)" e.g. However, I cannot get the associated category of one specific instance of Content. For that to work, I have to set the context of the OCL Service to the Content Object as the starting point of executing. Then I can simply state "self.Category" and I will get one or multiple results, depending on the association in place between Content and Category.

You can define the context easily as the first parameter of evaluate. The expression becomes the second parameter then:

// assuming that myContent contains a reference to a Content object (".NET World")
Category myCategory = oclService.Evaluate(myContent.AsIObject(), "self.Category").AsObject;

Take note of the fact that the first parameter has to be an object in the "ECO World". In case you only have an object handy from the .NET World, e.g. an instance of Content as shown in the example, use the AsIObject method. You may think of "AsIObject" as the inverse of "AsObject". It allows you to step from the ".NET World" into the "ECO World".

    References

    The Author

Hide image
holger_formal_small

Holger Flick, born and living in Germany, studies Computer Science at the University of Dortmund, Germany. His major subjects at University include Logical Systems, Computer Networks and Distributed Systems, Information Systems, Artificial Intelligence (esp. data mining with focus on Knowledge Discovery in Databases and Modeling), Systems Analysis and Evolutionary Computation. He will graduate with diploma later this year as he writes his thesis at the Chair for Artificial Intelligence at the moment. Next to his work at University his major interest is Borland's ECO, which helped him develop client as well as web applications. At the moment, he works on a web site that is administrated using a Content Management System in connection with an ECO ASP.NET application.

    Thanks

  • Jonas Högström, Borland R&D
    Jonas did an amazing job to proof read this document from the technical perspective
  • Olaf Monien, EDV-Beratung Monien
    Olaf triggered my interest in uncovering the internals about IElement and IObject.
  • Alois Schmid, DV-Service Schmid
    Alois wrote and published the first book that covers ECO III in detail. It is available in German and in English on his website http://www.ecospace.de.

Server Response from: ETNASC03