Abstract / Generic Predicate Builder

Posts   
 
    
Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 02-May-2004 10:37:47   

I have been trying to wrap my brain around this for a few days now, and I have yet to come to terms with this delima.

My code is littered with long lines of code that builds predicates. Using the self servicing pattern, I dealt with plane jane predicate expressions, but since moving to the adapter way of doing things, I deal with RelationPredicateBuckets now.

In any case, I was wondering if anyone has figured out a generic / abstract way to write a method, that will accept some arguments and return a predicate that you can plop into a PredicatExpression.Add or .AddWithAnd or .AddWithOr method.

If I have 3 entities. That = 6 overloads for 1 compareValue predicate. All of which create a new FieldCompareValuePredicate. It just so happens that FieldCompareValuePredicate uses the EntityFieldFactory.Create method to return the field to create the predicate for. The overloaded CompareValue method knows to call a certain overlaod because of reflection and the type of enum passed in (I think).

Basically I would like to end up with something like this:


'CompareValueBuild is the method I am trying to work out
'TableA.CustomerName and TableB.FieldName are FieldIndexEnums

bucket.PredicateExpression.add(CompareValueBuilder(TableA.CustomerName, Equal, Value))
bucket.PredicateExpression.addwithand(CompareValueBuilder(TableB.FieldName,NotEqual,Value))

I am thinking that I will have to use some sort of reflection to check the arguments of the methods in the EntityFieldFactory to get a reference to the particular overload, and then execute it.

Any ideas are greatly appreciated, and thanks in advance.

Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 02-May-2005 05:58:01   

** WARNING: Lengthy Post w/ Source Code** Hopefully someone might find this useful. I wrote it primarily because I am lazy and I dont like trying to figure out which overloaded CompareValue method to use when creating a simple predicate.

I ended up with two classes that will be rolled into a template for a plugin that I am building, but the raw code is here.

The 2 classes are CompareValuePredicateBuilder and PredicateBuilderInfo. CompareValuePredicateBuilder returns instance an IPredicate using PredicateBuilderInfo. The CompareValuePredicateBuilder uses reflection to determine which overload of the PredicateFactory.CompareValue method to invoke based on the contents of PredicateBuilderInfo.

The primary reason I wrote the code as such is so that I could reduce the lines of code that need to be generated and to provide a very loose coupling when creating a predicate.

Here is a sample that shows how to use the 2 classes to fetch a collection with a RelationPredicateBucket that has predicate expressions build from my classes.

 public void TestWith2Predicates()
        {
            try
            {
                LookupTableHelperClass helper = new LookupTableHelperClass();
                
                IRelationPredicateBucket bucket = new RelationPredicateBucket();                

                PredicateBuilderInfo info = new PredicateBuilderInfo(
                    CompareValuePredicateBuilder.Aspnet_UsersInRolesEntityKey, 
                    "UserId", 
                    new System.Guid("8e121e39-1dbb-41e1-bbd0-e8a4c25c7f03"));   

                bucket.PredicateExpression.Add(CompareValuePredicateBuilder.GetPredicate(info));

                info.FieldName = "RoleId";
                info.FieldValue = new System.Guid("dde0195d-ef53-4a0a-8bdd-5134b6c757be");
                bucket.PredicateExpression.AddWithOr(CompareValuePredicateBuilder.GetPredicate(info));

                EntityCollection collection = helper.Aspnet_UsersInRolesLookup_WithRelation(
                    bucket);

                foreach (Aspnet_UsersInRolesEntity entity in collection)
                {
                    Console.WriteLine(entity.RoleId + ":" + entity.UserId );
                }
            }
            catch (Exception ex)
            {
                Fail(ex);
            }
        }

The important method is the GetPredicate method, which uses the EntityName, FieldName, and FieldValue to get the predicate.

Here is the code if you just want to copy and paste it. I will be rolling it into a plugin very soon.

** Code for the classes**

    /// <summary>
    /// This class uses reflection an a PredicateBuilderInfo class to create 
    /// a compare value predicate.  The predicate that is returned can 
    /// be used in a predicate expression
    /// </summary>
    public sealed class CompareValuePredicateBuilder
    {
        public const string Aspnet_UsersInRolesEntityKey = "Aspnet_UsersInRoles";

        private CompareValuePredicateBuilder()
        {
        }

        public static IPredicate GetPredicate(string entityName, 
            string entityFieldName, object fieldValue)
        {
            PredicateBuilderInfo builder = new PredicateBuilderInfo(entityName, 
                entityFieldName, fieldValue);

            return GetPredicate(builder);
        }
        
        public static IPredicate GetPredicate(PredicateBuilderInfo builder)
        {
                object predicate = null;
                Type factoryType = null;
                string factoryName = "{0}.FactoryClasses.PredicateFactory";
                Type[] methodArgs = null;
                MethodInfo compareValueMethod = null;
                object[] runtimeArgs = null;
                AssemblyName[] referencedAssemblies = null;

                factoryName = string.Format(factoryName, PredicateBuilderInfo.RootNamespace);

                // check the current assembly for the predicate factory
                Assembly currentAssembly = Assembly.GetExecutingAssembly();
                factoryType = currentAssembly.GetType(factoryName, false,true);

                // if the factoryType is still null after the first GetType then 
                // check the dependent assemblies
                if (factoryType == null)
                {
                    if (referencedAssemblies == null)
                    {
                        referencedAssemblies = currentAssembly.GetReferencedAssemblies();
                    }
                    
                    // iterate through the referenced assemblies
                    foreach (AssemblyName name in referencedAssemblies)
                    {
                        Assembly tempAssembly = Assembly.Load(name);
                        currentAssembly = tempAssembly;
                        factoryType = tempAssembly.GetType(factoryName, false, true);
                        if (factoryType != null)
                            break;
                    }
                }

                if (factoryType != null)
                {
                    string methodName = "CompareValue";     
                    string entityName = builder.EntityName;
                    Type fieldType = builder.FieldValue.GetType();

                    // create an arry of types to use when searching for the method
                    methodArgs = MethodArguments(currentAssembly, builder);

                    // find the method with the proper overload
                    compareValueMethod = factoryType.GetMethod(methodName, methodArgs);

                    // create the values to use in the method invocation 
                    runtimeArgs = RuntimeArguments(methodArgs[0],builder);
                        
                    // invoke the method
                    predicate = compareValueMethod.Invoke(factoryType, runtimeArgs);
                }
                return predicate as IPredicate;
        }

        /// <summary>
        /// Returns an array of types that will be used by the runtime when it is searching 
        /// for the proper CompareValue method to use
        /// </summary>
        /// <param name="dbGenericAssembly">The assembly containing the method</param>
        /// <param name="builder">The object containing the data required to build the predicate</param>
        /// <returns>An array of types</returns>
        private static Type[] MethodArguments(Assembly dbGenericAssembly, PredicateBuilderInfo builder)
        {           
            Type enumType = null;
            string[] args = new string[]{PredicateBuilderInfo.RootNamespace, builder.EntityName};

            string enumName = "{0}.{1}FieldIndex";
            enumName = String.Format(enumName, args);

            enumType = dbGenericAssembly.GetType(enumName,true,true);


            return new Type[]{enumType, 
                typeof(ComparisonOperator), builder.FieldValue.GetType()};
        }

        /// <summary>
        /// Returns an array of objects that will be used in the method invocation
        /// </summary>
        /// <param name="enumType">The entity field index</param>
        /// <param name="builder">The object containing the data to use to build the arguments</param>
        /// <returns>An array of objects</returns>
        private static object[] RuntimeArguments(Type enumType, PredicateBuilderInfo builder)
        {
            object fieldIndex = Enum.Parse(enumType, builder.FieldName, true);
            return new object[]{fieldIndex, 
                ComparisonOperator.Equal, builder.FieldValue};      
        }
    }

    /// <summary>
    /// This class contains all information used by the generic predicate builder framework.
    /// </summary>
    public class PredicateBuilderInfo 
    {
        public const string RootNamespace = "EDS.Plugins";

        /// <summary>
        /// Default ctor
        /// </summary>
        public PredicateBuilderInfo()
        {
        }

        /// <summary>
        /// Use this constructor to initialize this class with all of its 
        /// values populated
        /// </summary>
        /// <param name="rootNamespace">Root namespace of the database generic project</param>
        /// <param name="entityName">Name of the entity to use in the predicate </param>
        /// <param name="fieldName">Name of the field to query on</param>
        /// <param name="fieldValue">Value to be used in the query</param>
        public PredicateBuilderInfo(string entityName, string fieldName,
            object fieldValue)
        {
            this.m_entityName = entityName;
            this.m_fieldName = fieldName;
            this.m_fieldValue = fieldValue;
        }


        private string m_entityName;
        private string m_fieldName;
        private object m_fieldValue;

        /// <summary>
        /// Gets / sets the value to use for the field in the predicate 
        /// </summary>
        public object FieldValue
        {
            get
            {
                return m_fieldValue;
            }
            set
            {
                m_fieldValue = value;
            }
        }

        /// <summary>
        /// Gets / sets the name of the field to query on
        /// </summary>
        public string FieldName
        {
            get
            {
                return m_fieldName;
            }
            set
            {
                m_fieldName = value;
            }
        }
    
        /// <summary>
        /// Gets / sets the entity name to be used in the predicate expression
        /// </summary>
        public string EntityName
        {
            get
            {
                return m_entityName;
            }
            set
            {
                m_entityName = value;
            }
        }
    }

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 02-May-2005 10:05:05   

What I wonder is why you choose reflection over the fact that you can call into the EntityFieldsFactory using an EntityType enum and in there grab the field using the name as indexer, and use that field object to build a FieldCompareValuePredicate instance?

that way you need just 1 instance to reflect (if you don't have a reference to the factory) and you can cache that.

(heh, you replied to your own thread exactly one year later. I was wondering why the first message didn't show up in my rss reader but then I saw it was a year old!)

Frans Bouma | Lead developer LLBLGen Pro
Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 02-May-2005 11:40:26   

I am not familiar with the method that you are referring to, will you show me a sample. I will be the first to admit the the LLBLGen framework is so deep that there are many things I dont know about it. Which is a good thing.

The one year thing is pretty odd as well.

Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 02-May-2005 12:04:01   

Ok, I think I see what youre saying. Use the EntityFieldsFactory to get a collection of fields from the factory. Then use the indexer of the fields collection to find the EntityField2 object via the "FieldName". Then pass along the EntityField2.FieldIndex .

Is that what you are referring to instead of using reflection to find the field index?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39927
Joined: 17-Aug-2003
# Posted on: 02-May-2005 13:51:23   

Devildog74 wrote:

Ok, I think I see what youre saying. Use the EntityFieldsFactory to get a collection of fields from the factory. Then use the indexer of the fields collection to find the EntityField2 object via the "FieldName". Then pass along the EntityField2.FieldIndex .

Is that what you are referring to instead of using reflection to find the field index?

Yes simple_smile

It IS a bit overkill perhaps, as it creates all the fields and you might only need one field. The routine which produces entityfields uses field indexes, which is a bit of a problem in this situation, as what you need is an instance of a given fieldindex enum type. To get that reflection is unavoidable (either direct, or indirect through .NET)

Though using reflection will be slower than the extra fields created.

Frans Bouma | Lead developer LLBLGen Pro
psandler
User
Posts: 540
Joined: 22-Feb-2005
# Posted on: 02-May-2005 15:55:21   

Devildog74 wrote:

Ok, I think I see what youre saying. Use the EntityFieldsFactory to get a collection of fields from the factory. Then use the indexer of the fields collection to find the EntityField2 object via the "FieldName". Then pass along the EntityField2.FieldIndex .

This is exactly the method I use to abstract predicates (worked it out with help from Frans), and it works beautifully.

I saw from another post (which I can't find at the moment) that your architecture has some similiarities to ours. I believe you use an abstracted Business Layer, and this is a current requirement of the project I'm on. If you feel like comparing a few notes, drop me an email at psandler70 at yahoo.

Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 02-May-2005 15:59:46   

Indeed, the reflection bit is somewhat slow on the first pass.

I do mostly web programming. A lot of the code that I write consists of fetching data into a list or combo, and then filtering other combos and lists when the user selects a value in the original list, i.e. Fetch the Category List -> User Selects a Category -> Filter and Fill the Product List based on the selected CategoryId.

Then I typically end up using the selected CategoryId and selected ProductId in other places. The Category and Product tables are what I consider to be "Lookup" tables, so I am attempting to provide a quicker, more encapsulated way to fill the lists.

The addon to the foundation of the code written here will be to move this code into a template set and plugin so that LLBLGen Pro users can use the plugin to generate a "One-Stop-Shop" helper class that provides quick access to fill lists and combos.

In your opinion, does this seem like it could be a useful plugin?

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 02-May-2005 18:01:55   

I would love to see some kind of tool to aid in the building of predicates in general.

DevilDog, is that you lurking on the EA forum? I went to post about multiple properties and you had already started a thread, I believe. (Actually I think some variation of that thread has been done many times).

sunglasses

Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 03-May-2005 01:38:32   

hehe,yeah thats me. for a second I couldnt decide if you were referring to enterprise architecht or electronic arts. im on both. hehe.

i picked up ea so that i could help the design process modeling process. i have been getting into the agile development with iconix lately, which seems to be working very well.

plus the features of ea for requirements, test cases, and useage scenarios is really cool.

I am havng a tough time getting the ea designer to import a 512kb cs file though, but for the most part i am happy with it.

JimFoye avatar
JimFoye
User
Posts: 656
Joined: 22-Jun-2004
# Posted on: 03-May-2005 02:36:30   

Devildog74 wrote:

i have been getting into the agile development with iconix lately, which seems to be working very well.

Interesting. What's the book you would recommend for that?

Devildog74 wrote:

plus the features of ea for requirements, test cases, and useage scenarios is really cool.

I'll have to look closer at that. I've found I can do quick and dirty component and deployment diagrams pretty easily. And, it actually serves as a pretty slick ER diagramming tool (make sure you hide feature stereotypes so you don't see <column> all over the place).

But EA also has some irritating problems, as you've noticed. And a few bugs, though they seem good about fixing those.

Devildog74
User
Posts: 719
Joined: 04-Feb-2004
# Posted on: 03-May-2005 16:38:40   

agile development with iconix process by APress does a fairly good job of illustrating their take on agile development.