CriteriaBase & Generics

CriteriaBase & Generics

Old forum URL: forums.lhotka.net/forums/t/4694.aspx


jh72i posted on Tuesday, April 15, 2008

Just wondering why we don't have a generic'd Criteria. Don't really know what i'm talking about but just observing that we still need to pass Type info into the constructors of our Criterias that inherit from CriteriaBase.

tmg4340 replied on Tuesday, April 15, 2008

Well... from one perspective, it's a "six of one, half-a-dozen of the other" concept.  Would you rather pass in a type as a constructor parameter, or as a generic parameter?  It's the same thing.  CSLA needs the type to know what kind of business object to create in the DataPortal, so it doesn't have any actual bearing on CriteriaBase.  Plus, to use it in the DataPortal, you'd end up having to take the generic parameter and go the "typeof(T)" route to get what you wanted anyway.

There are backward-compatibility concerns as well.  I'm pretty sure that a generic-based CriteriaBase would have to be a separate class.  You certainly could do that, but again, that doesn't buy you a whole lot.

Just MHO...

- Scott

jh72i replied on Thursday, April 17, 2008

Thanks for that reply Scott. I 'get' what you're saying.

Kind of off but still on topic......I'm starting to wonder now whether criterias need to be tied to a specific object type at design time. Haven't put any real thought into this but it suddenly strikes me (after many years using clsa!!) that perhaps criteria needn't "belong" to the business classes..........like, say, most of my objects will have a criteria that allows retrieval by Id. I could simply provide one shared criteria class that would be passed into the factory methods (figuring the type of object to create then will have to be sorted out).

I'm also really starting to wonder if the criterias shouldn't be public and be passed into the factory methods themselves rather than have a parameter list that often need to be duplicated in the criteria constructor....example:

User me = User.LoadObject(myId);
so:
class User{
  public User LoadObject(int id)
  {
    return DataPortal.Fetch<User>(new Criteria(id));
  }

has to have: 
  public class Criteria{
      public Criteria(int id){....}

Now, I decide I want to provide a mechanism of searching by username...

User me = User.LoadObject(myUsername);
so:
class User{
  public User LoadObject(int id)
  {
    return DataPortal.Fetch<User>(new Criteria(id));
  }
  public User LoadObject(string username)
  {
    return DataPortal.Fetch<User>(new Criteria(username));
  }

has to have: 
  public class Criteria{
      public Criteria(int id){....}      
      public Criteria(string  username){....}

Basically, i have to add to both places. Now, we have many overloads in some of our classes to this gets ugly fast. So, I wondering if I shouldn't get rid of the User.LoadObject(...) overloads and replace with a single User.LoadObject(Criteria) and have the calls like:

User me = User.LoadObject(new Criteria(myId));
User meAgain = User.LoadObject(new Criteria(myUsername));

I'm thinking that this would also allow me to use a single static LoadObject method for all classes:

  public class BusinessBaseEx<T>: BusinessBase<T>...
   
{
     public T LoadObject(CriteriaBase criteria)
     {
       return DataPortal.Fetch<T>(criteria);
     }
   }

  public class User: BusinessBaseEx<User>
   {
   }

Am I losing my mind for the sake of cleaner/less code or is there merit in this approach!?

Of course, inheritance from User gets complicated because T is now stuck as a User but this is a problem regardless - incidentally one that i'd love to know how people are dealing with.

 

tmg4340 replied on Thursday, April 17, 2008

Well... there's nothing that says you have to create specific criteria objects for every business object.  One of the side benefits of the "CriteriaBase" solution is that you can create one criteria object that is "ID based" and use it for every business object that follows the ID pattern.  Your factory method can instantiate the criteria class, providing the type to create there.  I've done that several times.  In fact, since I stopped directly tying a criteria object to a business object, I tend to put all my criteria objects into their own namespace now.

As for the parameter list of your factory methods, how you manage that is largely a matter of style IMO.  Under the system I outlined above, you can't directly pass in a criteria object - or, more appropriately, you shouldn't.  That forces the UI to provide the business object's factory method with the type of the business object to create - something the factory method should already know.  If your factory methods weren't tied directly to your business objects, then that's just fine (and, in fact, required).  But since the CSLA pattern is to place the factory methods on the object they create...

There is certainly nothing stopping you from creating UI-specific parameter objects that you pass into your factory methods.  I have done this for generic Find methods, where the user can pass in several criteria.  They do allow for parameter expansion, though I wouldn't go so far as to make every method take one.  After all, what's the point of creating a parameter object with one property, especially if you know that method signature is never going to change?

Regardless of the method chosen, I still have to map values to my criteria objects, either through a constructor or through properties.  But I think of criteria objects as an internal CSLA construct, and as such I don't expose them to the UI.  All my criteria objects are internal.  So it doesn't bother me.  And the DataMapper can help here.

As for your generic load method - you can certainly do that.  Again, I wouldn't, because I don't expose criteria objects directly to the UI.  You could still make it work with the UI-specific parameter objects, though that's a little more complicated to do in a generic sense.  Again, the DataMapper concepts can help there.  The one downside I see is that all your Fetch routines will have to cast the criteria object passed in.  That's not a huge deal, but part of the reason Rocky went to so much trouble upgrading the DataPortal's method invocation routines is so that we didn't have to do that anymore.  Adding another generic parameter to handle the criteria object type would solve that, but then you're back to exposing the criteria internals to the UI.  You can create a graph of UI-based parameter objects, but only if they have enough similarities.  It quickly can become work for the pattern's sake, rather than actually helping you out.

And you are correct - your generic methodolody breaks down in subclasses.  There isn't any solution/workaround that I know of - "T" has to be defined somewhere, and once it is defined, there's no way to break it.  Subclasses have to end up defining their own factory methods, which tends to get messy pretty quickly.  You have to replace the base ones in your subclass and throw exceptions (to keep the UI from calling the wrong one), but you also would want to use the base factory routines in your subclass factory routines to get the base-class data - otherwise, what's the point?  So you can end up writing a lot of code.  You can certainly factor out some code so it's still usable in subclasses.

HTH

- Scott

JoeFallon1 replied on Thursday, April 17, 2008

I moved all of my criteria classes to their own namespace years ago when I had the same AHA! moment you just had. <g>.

I have posted about it many times in the past and it has served me well over the years. By inheriting from CriteriaBase you have to pass in the Type of the BO the Criteria class will be associated with so it knows which instance to create.

At first I wanted to make some generalized Criteria classes that would work for "anything". But it wasn't always clear what Code1 and Code2 stood for so I created a few more where they make more sense. Like Criteria for Id values or soe common string values in my app.

Moving the Criteria classes out of the containing bOs and into their own namespace has been a great help and reduces a lot of duplicate code.

Joe

 

 

sergeyb replied on Thursday, April 17, 2008

This is just my opinion, but I like having criteria classes inside BO because I believe it promotes loose coupling and avoids accidental breaking of code when you modify a criteria class that is shared amongst business objects.

 

 

 

Sergey Barskiy

Senior Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: JoeFallon1 [mailto:cslanet@lhotka.net]
Sent: Thursday, April 17, 2008 11:05 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] CriteriaBase & Generics

 

I moved all of my criteria classes to their own namespace years ago when I had the same AHA moment you jst had. <g>.

I have posted about it many times in the past and it has served me well over the years. By inheriting from CriteriaBase you have to pass in the Type of the BO the Criteria class will be associated with so it knows which instance to create.

At first I wanted to make some generalized Criteria classes that would work for "anything". But it wasn't always clear what Code1 and Code2 stood for so I created a few more where they make more sense. Like Criteria for Id values or soe common string values in my app.

Moving the Criteria classes out of the containing bOs and into their own namespace has been a great help and reduces a lot of duplicate code.

Joe

 

 



JoeFallon1 replied on Thursday, April 17, 2008

Sergey,

I get your point. But here is the thing - I do not modify my generalized Criteria classes. If I need a 2nd parameter or something I create a new criteria class. The general classes handle 90% of my needs. And I have 10-20 specialized ones which are more specific. Some are so specific that they could be inside the BO, but that is a rare case and it is easier just to have them all in one place.

Joe

 

tmg4340 replied on Thursday, April 17, 2008

sergeyb:
This is just my opinion, but I like having criteria classes inside BO because I believe it promotes loose coupling and avoids accidental breaking of code when you modify a criteria class that is shared amongst business objects.

I can certainly understand that as well.  But I am in a similar situation as Joe - my "generic" criteria classes are used in 90%-95% of my code, and they never change.  If they did, and it caused a problem, that problem would very likely be ferreted out fairly quickly during testing.  Having a bunch of nested criteria classes that are all basically the same isn't really a big deal, but it's a ton of code to cut-and-paste, and I don't use code generation (which would essentially eliminate that issue) nearly as much as I probably should.

I haven't had a chance to look at it, but I know that CSLA 3.5 provides a number of "generic" criteria classes.  As I understand it, the idea was that we end up writing a bunch of criteria classes that all look the same except for data types.  So instead of doing that, Rocky provided a set of classes (built on generics!) that allow you to create criteria classes "on the fly".  It's certainly another avenue to explore.

HTH

- Scott

jh72i replied on Thursday, April 17, 2008

1. Using Criteria Constructor overloads rather than BO Factory overloads

I definitely wasn’t considering having the client/UI need to tell the BO's factory anything about type.

UI call would change only from:

  User u = User.LoadObject(132);
  User u = User.LoadObject(first, last, middle, age, …);

To:
   User u = User.LoadObject(new User.Criteria(123));
   User u = User.LoadObject(new User.Criteria(first, last, middle, age, …));

The criteria in this case still being an embedded class of the User. Just that this would then become the common pattern - rather than overloading the LoadObject method I’d only overload the Criteria constructor.
If the criteria needed to be protected in terms of, say, requiring that only certain combinations of properties be set via a constructor I’d simply make the properties readonly and only settable via the constructor:

   User u = User.LoadObject(new User.Criteria(firstname, lastname, domain));

Rather than allowing:

   User.Criteria c = new User.Criteria();
   c.FirstName = “xxxx”; -- not allowed
   User u = User.LoadObject(c);

And could reserve some constructors for internal use. As for storing the criterias completely outside of the objects I’m not sure of a good pattern with this approach.

2. Using base Factory overloads instead of duplicating functionality

But…………what I’d really like to achieve is have a base class accept a criteria and do the work without having to duplicate essentially the same factory methods on each object. So taking the code from above I’d be saying the User inherits from BOBase<T> but only BOBase implements the LoadObject method. This is easily achieved because BOBase<T> knows all about the type of the object….

UI/Client:

User u = User.LoadObject(new User.Criteria(123));

User:

---- no LoadObject implementation but does have criteria so type is correct

BOBase:

public static T LoadObject(CriteriaBaseEx criteria)

{

  return (T)DataPortal.Fetch(criteria);

}

 

protected static T Fetch(CriteriaBaseEx criteria)

{

       T obj = (T)DataPortal.Fetch(criteria);

       // example of common task when creating all objects

       obj._criteria = criteria;

      

       return obj;

}

 

Now, this is a simplistic effort but in my world I want to do a lot more general object management stuff whenever any object is created - avoiding duplicating in each class being my main motivation. And, just for you extra clever folks much of what I want to do is after the object has been created and populated.

3. Reusable Criteria objects

Another point I mentioned that you all picked up on was the reusability of certain well known criterias. For me who uses identities on 99% of all records fetching by id is a common action for my objects. So the idea is to create a FetchById criteria and just use it in every object.
As pointed out by you the type of this criteria becomes a problem in the dataportal code. If I deviate from the pattern for a second and create a
CriteriaFilteredById criteria that has a factory of its own (called Build – as in building a criteria rather than loading or creating it) rather than constructor and implement a BOBase factory that accepts an Id (mmm, I know from about I said I want only Criterias but…)…

UI/Client:

User u = User.LoadObject(123);

User:

---- no LoadObject implementation and no criteria of its own

---- *** back later ***

BOBase:

public static T LoadObject(int id)

{

       return LoadObject(CriteriaFilteredById.Build<T>(id));

}

 

CriteriaFilterById:

[Serializable()]

public class CriteriaFilteredById : CriteriaBaseEx

{

       private int _id;


       public static CriteriaFilteredById Build<T>(int id)

       {

              return new CriteriaFilteredById(typeof(T), id);

       }

       public CriteriaFilteredById(Type type, int id)

              : base(type)

       {

              _id = id;

       }

}

 

*** back to User:

protected override void DataPortal_OnFetch(AdoHelper dataHelper, object criteria)

{

       // get the values we need to query the data                   CriteriaFilteredById crit = (CriteriaFilteredById)criteria;

…dataHelper.GetIntParameter("pUserID", crit.Id, false));

}

 

 

4. What about inheritance?

So now I want to inherit from User and extend it to create a SuperUser. How flexible is my code? Not very is the answer but then this is a general problem faced by any class inheriting from a concrete type.  But, all is not lost – I think this approach still provides enough to make it useful.

UI/Client:

SuperUser u = SuperUser.LoadObject(123);

SuperUser:

Cannot go straight to the BOBase unfortunately without having the UI/client involved in the typing and cannot cast User to SuperUser so…(but this is a problem anyway regardless).

public static new SuperUser LoadObject(int userId)

{

       return SuperUser.LoadObject<SuperUser>(userId);

}

BOBase:

I implement another LoadObject and another Fetch and actually refactor the original Fetch to ensure no code duplication…

 

protected static TT LoadObject<TT>(int id)

{

       if (!typeof(T).IsAssignableFrom(typeof(TT)))

              throw new ArgumentException(string.Format("{0} must be derived from type {1}", typeof(TT), typeof(T)));

 

       return (TT)Fetch<TT>(CriteriaFilteredById.Build<TT>(id));

}

 

protected static TT Fetch<TT>(CriteriaBaseEx criteria)

{

       TT obj = DataPortal.Fetch<TT>(criteria);

       // example of common task when creating all objects

       (obj as T)._criteria = criteria;

      

       return obj;

}

 

protected static T Fetch(CriteriaBaseEx criteria)

{

       return Fetch<T>(criteria);

}

 

 

 

So there are 4 ideas here – not all directly related but all depending on eachother. I’m not a major fan of generics so far but I’m pretty new to them. I think it’s because the hype was so much that I didn’t think I’d be so stuck with my old object inheritance graphs that I was very happy with. I’d love to hear any thought on all these rantings.

 

Copyright (c) Marimer LLC