OJB.Net and Generics
OJB.Net and Generics
The released versions of the .Net framework don’t support Generics. However, I am using the Visual Studio Community Technology Preview and have been attempting to use Generics with OJB.Net. In this post/article I’ll describe those attempts.
You can read about OJB.Net at this link:
https://ojb-net.sourceforge.net/
You can read about Generics at these links:
https://blogs.msdn.com/tims/archive/2003/10/28/57420.aspx
Hopefully this can be of help to two groups:
- The OJB.Net user group for assistance in enabling the use of Generics within OJB.Net now.
- The OJB.Net developer group for developing/extending support for Generics within OJB.Net once the latest version of the framework once it is released.
The support for generics in OJB.Net that I’ll describe here can be placed in two groups; Query support and Lazy Loaded Collection support.
Generics Support for Queries
The QueryFacade is the part of OJB.Net used to retrieve objects from the data store. Generics support in the QueryFacade provides for three things:
- Strong type checking of code at compile time
- Strongly typed retrieval of objects and collections that creates cleaner code
- Strongly typed retrieval of collections that eliminates the need to iterate through the collections to create arrays.
The two generic methods that I created on the QueryFacade are:
Old object retrieval
public Cart GetCartById(int cartId)
{
Criteria criteria = new Criteria();
criteria.AddEqualTo("_primaryKey", cartId);
Cart cart = (Cart)QueryFacade.FindObject(typeof(Cart), criteria);
return cart;
}
New object retrieval
public Cart GetCartByIdGenerics(int cartId)
{
Criteria criteria = new Criteria();
criteria.AddEqualTo("_primaryKey", cartId);
Cart cart = QueryFacade.FindObject<Cart>(criteria);
return cart;
}
Old collection retrieval
public IList GetAllCarts()
{
Criteria criteria = new Criteria();
criteria.AddEqualTo("_primaryKey", "*");
IList carts = QueryFacade.Find(typeof(Cart), criteria);
return carts;
}
New collection retrieval
public List<Cart> GetAllCartsGenerics()
{
Criteria criteria = new Criteria();
criteria.AddEqualTo("_primaryKey", "*");
List<Cart> carts = QueryFacade.Find<Cart>(criteria);
return carts;
}
Since Richard was nice enough to develop this as an open source project it allows us to (among other things) alter the code to suit our needs. To enable generics support when querying for objects from OJB.Net you need to make a few additions to the OJB.Net codebase. I’ll list those changes here:
First (and easiest) the QueryFacade.FindObject method:
Add to QueryFacade…
public static T FindObject<T>(Criteria criteria)
where T : IPersistable
{
return (T)new TransactionalQuery().GetObjectByQuery(QueryFactory.NewQuery(typeof(T), criteria));
}
You’ll see that this method is just allowing you to specify the type once and have that type used for the return value and the query type. This simply provides for some cleaner code in the methods that call QueryFacade.
Second (and a little more involved) the QueryFacade.Find method:
Add to QueryFacade…
public static System.Collections.Generic.List<T> Find<T>(Criteria criteria)
where T : IPersistable
{
return new TransactionalQuery().GetRealListByQuery<T>(QueryFactory.NewQuery(typeof(T), criteria));
}
Add to TransactionalQuery…
public System.Collections.Generic.List<T> GetRealListByQuery<T>(IQuery query)
{
return PersistenceBroker.GetRealListByQuery<T>(query);
}
Add to PersistenceBroker…
public static System.Collections.Generic.List<T> GetRealListByQuery<T>(IQuery query)
{
return (System.Collections.Generic.List<T>)GetRealListByQuery(typeof(System.Collections.Generic.List<T>), query);
}
You’ll notice that I didn’t take the use of Generics as far as it could go here. I could have, for example, created a QueryFactory.NewQuery generic method. But my goal in using generic methods with the QueryFacade.Find method was to satisfy the first and third items on the list above – strong type checking and strongly typed retrieval of collections that eliminate the need to iterate through the collections to create arrays.
The reason for going three methods deep was to pass System.Collections.Generic.List<T> into the GetRealListByQuery method as the collection class instead of the standard System.Collections.ArrayList.
Completing this change means that I don’t need to iterate through the returned collection to create an array, since I can just use the List<T>.ToArray() method.
Generics Support for Lazy Loaded Collections
I’ll let you know right now that I didn’t get this working, and I would appreciate any help anyone can give me on this.
The ListProxy is the part of OJB.Net used to retrieve a collection from the data store transparently to the user in a “load on first access” manner. This is commonly referred to as Lazy Loading.
The first hurdle on the way to using generic collection types is that you can’t use the IList<> (System.Collections.Generic) interface type, since there are explicit casts to IList (System.Collections) in the OJB.Net code and IList<> does not implement IList.
Given this limitation, I had to create my own collection class that inherited from IList:
Attempt 1
You can’t have a class that inherits from IList<> as well as IList, since the implementing class will have unresolvable ambiguities (go on... try it!).
Attempt 2
I created a custom built generic list class that internally uses List<> and a custom built interface that implements System.Collections.IList, but this resulted in an exception
Ip3.Tdsf.ApplicationFacades.DataAccess.Tests.GenericsDataAccessTests.TestGetAllCartsGenerics : System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> System.BadImageFormatException : An attempt was made to load a program with an incorrect format.
Server stack trace:
at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, SignatureStruct sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Ojb.Net.Core.Proxy.LazyLoadingObjectProxy.Invoke(IMessage request) in C:\Src\Ip3.Tdsf\Ip3.Tdsf\core\Ojb.Net.Core\Proxy\LazyLoadingObjectProxy.cs:line 100
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at Ip3.Tdsf.Model.ECommerce.Cart.get_CartItems() in c:\src\ip3.tdsf\ip3.tdsf\ip3.tdsf.model\ecommerce\cart.cs:line 112
at Ip3.Tdsf.ApplicationFacades.DataAccess.Tests.GenericsDataAccessTests.TestGetAllCartsGenerics() in c:\src\ip3.tdsf\ip3.tdsf\ip3.tdsf.applicationfacades\dataaccess\tests\genericsdataaccesstests.cs:line 73
--TargetInvocationException
at Ip3.Tdsf.Model.Common.Collections.Ip3IList`1.ToArray()
at Ip3.Tdsf.Model.ECommerce.Cart.get_CartItems() in c:\src\ip3.tdsf\ip3.tdsf\ip3.tdsf.model\ecommerce\cart.cs:line 112
Attempt 3
I created a custom built collection class that inherits from ArrayList and a custom built collection interface that implements System.Collections.IList. This was very similar to the CustomCollection class provided with the OJB.Net source (https://cvs.sourceforge.net/viewcvs.py/*checkout*/ojb-net/ojb-net/src/test/Ojb.Net.Test/TestType/Collection/CustomList.cs?content-type=text%2Fplain&rev=1.1). This worked nicely and had my energy up to attempt some additional scenarios.
Attempt 4
I took the custom built collection class and interface and added two methods; a standard method and a generic method. The resulting code was:
public interface Ip3IMethodTypedArrayList : System.Collections.IList
{
void DoNothing();
void DoNothingTyped<T>();
}
public class Ip3MethodTypedArrayList : ArrayList, Ip3IMethodTypedArrayList
{
public Ip3MethodTypedArrayList() : base()
{
}
public Ip3MethodTypedArrayList(ICollection collection) : base(collection)
{
}
public Ip3MethodTypedArrayList(int capacity) : base(capacity)
{
}
public void DoNothing()
{
Console.WriteLine("void DoNothing() called");
}
public void DoNothingTyped<T>()
{
Console.WriteLine("void DoNothing<T>() called");
}
}
This worked great. Both the standard and non-generic method can be called.
Attempt 5
I took the custom built collection class and interface and added a generic modifier to the class and interface:
public interface Ip3IClassTypedArrayList<T> : System.Collections.IList
{
void DoNothing();
void DoNothingTyped<T>();
}
public class Ip3ClassTypedArrayList<T> : ArrayList, Ip3IClassTypedArrayList<T>
{
public Ip3ClassTypedArrayList() : base()
{
}
public Ip3ClassTypedArrayList(ICollection collection) : base(collection)
{
}
public Ip3ClassTypedArrayList(int capacity) : base(capacity)
{
}
public void DoNothing()
{
Console.WriteLine("void DoNothing() called");
}
public void DoNothingTyped<T>()
{
Console.WriteLine("void DoNothing() called");
}
}
And changed the repository.xml accordingly:
<CollectionDescriptor FieldName="_cartItems" RelatedClassName="Ip3.Tdsf.Model.ECommerce.CartItem" ListClassName="Ip3.Tdsf.Model.Common.Collections.Ip3ClassTypedArrayList`1[[Ip3.Tdsf.Model.ECommerce.CartItem, Ip3.Tdsf.Model]],Ip3.Tdsf.Model" ListInterfaceName="Ip3.Tdsf.Model.Common.Collections.Ip3IClassTypedArrayList`1[[Ip3.Tdsf.Model.ECommerce.CartItem, Ip3.Tdsf.Model]],Ip3.Tdsf.Model" CascadeDelete="true">
As soon as either method is called (yes, even the non-generic DoNothing method) an exception similar to attempt 2 occurs.
Ip3.Tdsf.ApplicationFacades.DataAccess.Tests.GenericsDataAccessTests.TestGetAllCartsGenerics : System.Reflection.TargetInvocationException : Exception has been thrown by the target of an invocation.
----> System.BadImageFormatException : An attempt was made to load a program with an incorrect format.
Server stack trace:
at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, SignatureStruct sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
at Ojb.Net.Core.Proxy.LazyLoadingObjectProxy.Invoke(IMessage request) in C:\Src\Ip3.Tdsf\Ip3.Tdsf\core\Ojb.Net.Core\Proxy\LazyLoadingObjectProxy.cs:line 100
Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at Ip3.Tdsf.Model.ECommerce.Cart.get_CartItems() in c:\src\ip3.tdsf\ip3.tdsf\ip3.tdsf.model\ecommerce\cart.cs:line 119
at Ip3.Tdsf.ApplicationFacades.DataAccess.Tests.GenericsDataAccessTests.TestGetAllCartsGenerics() in c:\src\ip3.tdsf\ip3.tdsf\ip3.tdsf.applicationfacades\dataaccess\tests\genericsdataaccesstests.cs:line 73
--TargetInvocationException
at Ip3.Tdsf.Model.Common.Collections.Ip3IClassTypedArrayList`1.DoNothing()
at Ip3.Tdsf.Model.ECommerce.Cart.get_CartItems() in c:\src\ip3.tdsf\ip3.tdsf\ip3.tdsf.model\ecommerce\cart.cs:line 123
However, it is interesting to note that this class and interface work just fine if the custom methods are not called and the class and interface are treated like an ArrayList and IList respectively.
I’m not sure why remoting is causing this exception or what this exception means exactly. I have seen this type of error before and that instance was because NUnit was trying to run a generics test with the 1.1 version of the framework (we had to modify the .config file). I have tried to ensure that this isn’t the problem here by checking the Environment.Version property to ensure that I am running the most recent (CTP) version of the .Net framework.
If anyone knows of a solution to this problem, I would be most grateful for some help.
Conclusions
Generics support for OJB.Net can be split into two groups; Query support and Lazy Loaded Collection support.
(1) Query support is simple to enable with four method additions to the OJB.Net source. I would recommend making these changes to anyone using OJB.Net with the pre-release Whidbey versions of the .Net framework.
(2) Lazy Loaded Collection support is problematic and I would recommend that people not try this until support for Generics has been added at a lower level in OJB.Net. Instead, a helper method (static public T[] ToArray<T>(IList list)) can be used to provide runtime type checking and single line “collection to array” conversion.
Comments
- Anonymous
June 10, 2004
Cool. Keep posting changes. I would love to reduce the amount of code needed for implementing an ORM.