Noob question about generated code with partial classes

Posts   
 
    
VBAHole22
User
Posts: 18
Joined: 12-Nov-2008
# Posted on: 05-May-2009 17:41:46   

I'm just getting the hang of using LLBLGen so this may be a silly question but here goes:

Say i have an entity called myEntity that i built off of an Oracle table. When i generate my code i get a property for each field in the db table, say f1, f2, f3. When i create an instance of this entity in code I can say


myEntity foo = new myEntity("myPkvalue");
string a = foo.f1;
string b = foo.f2;

That works all well and good for me. I want to extend this concept. myEntity is in a one to many relationship with a table called mySubEntity. I would like a property on myEntity that tells me if there are any children in the mySubEntity relationship. I don't care about getting the children records, i just want to know if there are any and i want that information to be stored on myEntity.

So what i did was create a partial class file to supplement myEntity like this:


  public partial class myEntity
    {
        public bool HasContacts
        {
            get
            {
                try
                {
                    DataTable dynamicList = new DataTable();
                    ResultsetFields fields = new ResultsetFields(1);
                    TypedListDAO dao = new TypedListDAO();

                    //filter by shape guid
                    IPredicateExpression filter = new PredicateExpression();
                    filter.AddWithAnd(new PredicateExpression(ShapeContactFields.Shpguid == this.Shpguid));

                    //define fields to return
                    fields.DefineField(ShapeContactFields.Shccontactid, 0);

                    dao.GetMultiAsDataTable(fields, dynamicList, 0, null, filter, null, true, null, null, 0, 0);

                    if (dynamicList.Rows.Count == 0)
                    {
                        return false;
                    }
                    return true;
                }
                catch (Exception ex)
                {
                    System.Diagnostics.Debug.WriteLine(ex.Message + ex.StackTrace);
                    return false;
                }
            }
        }


This also works well. I can obtain the value of HasContacts using the following test code:


myEntity _myEntity = null;
_myEntity = new myEntity("A9F2275C246F477897C536B15FAF6AFD");
bool test = _myEntity.HasContacts;

The thing I'm having a hard time understanding is why is my property, HasContacts, that i created in my partial class being treated a little differently than "native" properties that come right from the myEntity object itself?

When i run the following:


IEntityCore ec = _myEntity;
List<string> _fields = new List<string>();
_fields.AddRange(from IEntityField f in ec.Fields select f.Name);

I see all of the properties that correspond to fields in my db table but I don't see my HasContacts property. I would like for it to be in this set because this code is what I'm using to serialize that entity back to the browser.

It seems like my property is a second class citizen and not treated like the first class db fields. I'm open to modifying things here if i have gone about this the wrong way entirely. I have another method that pulls the child elements but i would like to know when i pull the parent element if there are any children to go look for. I would prefer not to have to turn around and send another request to determine if children exists.

I hope i explained this well. I'm new to this and i'm working off of alot of sample code.

any suggestions would be greatly appreciated.

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 05-May-2009 19:11:14   

Hi VBA -

Glad to see you've come a long way already. Good work on the Calculated field in the partial class. You are doing that correct. Although, you may want to consider using GetScalar() instead of GetMultiAsDataTable (because the latter actually returns all the entities, even though you may not use them. Could slow you down depending on how many records you have). Also, you could pass the value 1 to MaxRecordsToReturn parameter, which will only return 1 record.

You are also correct that your Calculated field is not listed as an EntityField object. This is intentional - because EntityFields are generated based on the actual database fields.

If I understand you correctly, you are basically trying to loop through all the fields in your entity (both DB fields & Calculated fields) and add them to a simple list. That will be no problem.

Here is how I tell Calculated Fields & DB Fields apart. Ready?

1.) Create a Partial Class for your HelperClasses EntityFields

This will allow you to use CustomerFields.HasContacts as a normal EntityField.


Namespace MyDataObjects.HelperClasses
    Partial Public Class CustomerFields

        '---------------------------------------------------------------------------
        'Calculated Fields
        '---------------------------------------------------------------------------
        Public Shared HasContacts As EntityField

        '---------------------------------------------------------------------------
        'Fields on Related Entities
        '---------------------------------------------------------------------------

        Shared Sub New()
            LLBLGenCommon.HelperClasses.AutomaticallyCreateEntityFields()
        End Sub

    End Class
End Namespace

  • Notice: LLBLGen generated EntityFields use Properties, so instead - I use shared Fields above for Calculated Fields.

2.) Instantiate an EntityField object for your Calculated Fields

In the Shared Sub New above, you need to do: HasContacts = GetEntityField(New Customer, "HasContacts"). I'm using Reflection to do this automatically because Reflection can tell the difference between a Property (DB Field) & a Field (Calculated Field).


    Public Shared Function GetEntityField(ByRef myEntity As IEntity, ByVal myFieldName As String) As EntityField
        Dim myFieldInfo As IFieldInfo = GetEntityFieldInfo(myEntity, myFieldName)
        Return New EntityField(myFieldInfo, Nothing)
    End Function

  • You don't need all the fancy stuff in this next function. I'm testing the field for ReadOnly and Nullable. Ultimately, all you need to do is create a New SD.LLBLGen.Pro.ORMSupportClasses.FieldInfo object

    Public Shared Function GetEntityFieldInfo(ByRef myEntity As IEntity, ByVal myFieldName As String) As IFieldInfo
        If Reflect.HasMember(myEntity, myFieldName) = False Then
            Throw New Exception("Cannot GetFieldInfo - Property or Field ('" & myFieldName & "') not found on Type ('" & CType(myEntity, Object).GetType.ToString & "').  Entity does not have this Calculated Property or this Field was not generated by LLBLGen as a Field on a related Entity!  Are you using Optional parameters?")
            Return Nothing
        End If

        'Get MemberInfo
        Dim myMemberInfo As MemberInfo = Reflect.GetMemberInfo(myEntity, myFieldName)

        'Get Data Type
        Dim myType As Type = myMemberInfo.GetType

        'Read Only?
        Dim myReadOnly As Boolean = False
        If TypeOf (myMemberInfo) Is PropertyInfo Then
            Dim myPropertyInfo As PropertyInfo = myMemberInfo
            myReadOnly = Not myPropertyInfo.CanWrite
        End If

        'Nullable?
        Dim myIsNullable As Boolean = False
        If myType.IsGenericType AndAlso myType.GetGenericTypeDefinition.Equals(GetType(System.Nullable)) Then
            myIsNullable = True
            'myType = System.Nullable.GetUnderlyingType(myType)
        End If

        Dim myFieldInfo As New SD.LLBLGen.Pro.ORMSupportClasses.FieldInfo(myFieldName, myEntity.LLBLGenProEntityName, myType, False, False, myReadOnly, myIsNullable, 0, 0, 0, 0)

        Return myFieldInfo
    End Function

3.) Get all Calculated Fields & tell DB Fields apart from Calculated Fields

Although I probably don't need it, I have an empty ICalculatedFields interface that I simply implement in my entity partial classes. That way I know which entities have Calculated fields before doing any Reflection.

The reason I'm using Reflection to get the HelperFieldsType is because these methods are all in a common library, used across all my projects. So it knows nothing about the data objects you're using. Reflection allows me to obtain the Type of the HelperClasses for EntityFields (ie. HelperClasses.CustomerFields).


    'Automatically determine which fields are calculated fields (using the .HelperClasses EntityFields object)
    Public Shared Function GetCalculatedFields(ByRef myEntity As IEntity) As List(Of IEntityField)
        If Not TypeOf myEntity Is ICalculatedFields Then
            Throw New ArgumentException("Cannot call this method!  Passed Entity does not implement ICalculatedFields!")
        End If

        Dim myHelperFieldsType As Type = Reflect.GetHelperFieldsType(myEntity)
        Dim myHelperFieldsObject As Object = Activator.CreateInstance(myHelperFieldsType)

        Dim myHelperFields As List(Of IEntityField) = GetAllReadableValues(Of IEntityField)(myHelperFieldsObject)

        Dim myCalculatedFields As New List(Of IEntityField)

        Dim isCalculatedField As Boolean
        For Each myHelperField As IEntityField In myHelperFields
            'Find Fields that are in HelperFields that are not in EntityFields
            isCalculatedField = Not ContainsEntityField(myHelperField, myEntity.Fields)
            If isCalculatedField = True Then myCalculatedFields.Add(myHelperField)
        Next

        Return myCalculatedFields
    End Function


    Public Shared Function GetAllReadableValues(ByVal myObject As Object) As ArrayList
        Dim myType As Type = myObject.GetType
        Dim myValues As New ArrayList
        Dim myValue As Object

        'Fields
        Dim myFields() As Reflection.FieldInfo = myType.GetFields()
        For Each myField As Reflection.FieldInfo In myFields
            myValue = myType.InvokeMember(myField.Name, BindingFlags.GetField, Nothing, myObject, Nothing)
            myValues.Add(myValue)
        Next

        'Get/Set Properties
        Dim myProperties() As PropertyInfo = myType.GetProperties(BindingFlags.GetProperty)
        For Each myProperty As PropertyInfo In myProperties
            myValue = myType.InvokeMember(myProperty.Name, BindingFlags.GetProperty, Nothing, myObject, Nothing)
            myValues.Add(myValue)
        Next

        'Methods
        Dim myMethods() As MethodInfo = myType.GetMethods()
        For Each myMethod As MethodInfo In myMethods
            Dim myParameters() As ParameterInfo = myMethod.GetParameters()
            If myParameters.Length = 0 OrElse myParameters(0).IsOptional = True Then
                myValue = myType.InvokeMember(myMethod.Name, BindingFlags.InvokeMethod, Nothing, myObject, Nothing)
                myValues.Add(myValue)
            End If
        Next

        Return myValues
    End Function

Whew! Hope this helps!

Ryan D. Hatch

MTrinder
User
Posts: 1461
Joined: 08-Oct-2008
# Posted on: 05-May-2009 20:43:59   

What he said...!

More good info from Ryan there - thank you :-)

Matt

VBAHole22
User
Posts: 18
Joined: 12-Nov-2008
# Posted on: 05-May-2009 22:01:13   

Cool. I'm getting close here. Of course there has to be a language barrier. Below is my C# perversion of your code.


namespace GenBizLogic.HelperClasses
{
    public partial class ShapeAttributeFields
    {
        public static EntityField HasContacts;

        static ShapeAttributeFields()
        {
            //HelperClasses.AutomaticallyCreateEntityFields();

            HasContacts = GetEntityField(new ShapeAttributesEntity(), "HasContacts");
        }

        public static EntityField GetEntityField(IEntity myEntity, string myFieldName)
        {
            IFieldInfo myFieldInfo = GetEntityFieldInfo(myEntity, myFieldName);
            return new EntityField(myFieldInfo, null);
        }

        public static IFieldInfo GetEntityFieldInfo(IEntity myEntity, string myFieldName)
        {
            Type myType = myEntity.GetType();
            System.Reflection.FieldInfo fi = myType.GetField(myFieldName);

            if (fi == null)
            {
                throw new Exception("Cannot GetFieldInfo - Property or Field. Entity does not have this Calculated Property or this Field was not generated by LLBLGen as a Field on a related Entity! Are you using Optional parameters?");
                //return null;
            }

            SD.LLBLGen.Pro.ORMSupportClasses.FieldInfo myFieldInfo = new SD.LLBLGen.Pro.ORMSupportClasses.FieldInfo(myFieldName, myEntity.LLBLGenProEntityName, myType, false, false, true, false, 0, 0, 0, 0);
            return myFieldInfo;
        }
    }
}

I'm assuming that AutomaticallyCreateEntityFields() is a method in your common library. Do I need that one?

I got up to step 3 with the conversion but i wasn't sure how I got from 2 to 3 in code. Where would i call GetCalculatedFields() and GetAllReadableValues()?

And thank you for the help, I'm learning slowly. I knew there had to be some way to get LLBLGEN to recognize my calced fields and i kind of knew there had to be a better way to determine if there were child recs without pulling them all - just couldn't see the light.

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 05-May-2009 22:19:00   

Hi VBA -

Good work.

No, you don't need AutomaticallyCreateEntityFields(). You've already done what it does already in Step 2 by calling GetEntityField. It's simply a shortcut function to automatically call GetEntityField, since I got tired of calling it by hand & retyping the field names.

You call Step 3 when you want to loop through your object's properties & add the Calculated Fields to your list that you're sending to the browser.

Here's an example, if you have myShapeAttributeEntity:


Dim myCalculatedValues As New ArrayList

For Each myField as IEntityField In GetCalculatedFields(myShapeAttributeEntity)
   Dim myCalculatedValue as Object = GetPropertyValue(myShapeAttributeEntity, myField.Name)
   myCalculatedValues.Add(myCalculatedValue)
Next

Other helper methods you'll need:


    Private Shared Function ContainsEntityField(ByRef myField As IEntityField, ByRef myFields As IEntityFields) As Boolean
        For Each myEntityField As IEntityField In myFields
            If myField.Name = myEntityField.Name Then Return True
        Next
        Return False
    End Function


    Public Shared Function GetPropertyValue(ByVal myObject As Object, ByVal PropertyName As String) As Object
        Dim myPropertyInfo As PropertyInfo = GetPropertyInfo(myObject, PropertyName)
        Return myPropertyInfo.GetValue(myObject, Nothing)
    End Function


    Public Shared Function GetPropertyInfo(ByVal myObject As Object, ByVal PropertyName As String) As PropertyInfo
        Return GetPropertyInfo(myObject.GetType, PropertyName)
    End Function


    Public Shared Function GetPropertyInfo(ByVal myType As Type, ByVal PropertyName As String) As PropertyInfo
        Try
            Dim emptyParameters As Type() = {}
            Dim myProperty As System.Reflection.PropertyInfo = myType.GetProperty(PropertyName, emptyParameters)
            Return myProperty
        Catch ex As Exception
            Throw New Exception("Could not get PropertyInfo for " & myType.Name & "." & PropertyName, ex)
        End Try
        Return Nothing
    End Function

Hope this helps!

Ryan

PS. Personally, I would move the GetEntityField & GetEntityFieldInfo out of the ShapeAttributeFields class & put them in a utility class, so you can call those functions from other places.

VBAHole22
User
Posts: 18
Joined: 12-Nov-2008
# Posted on: 06-May-2009 15:01:30   

Getting still closer. Here is my latest C# conversion of your code:


        public IEntityCore Entity
        {
            get;
            private set;
        }

        List<string> _fields = new List<string>();

        public BizAttributes( IEntityCore entity )
        {
            entity.RequireArgument<IEntityCore>( "entity" ).NotNull<IEntityCore>();
            Entity = entity;
            _fields.AddRange(from IEntityField f in Entity.Fields select f.Name);
            //_fields.Add("HasContacts"); //KG Test

            List<object> myCalculatedValues = new List<object>();
            foreach(IEntityField myField in GetCalculatedFields(entity))
            {
                object myCalculatedValue = GetPropertyValue(entity, myField.Name);
                myCalculatedValues.Add(myCalculatedValue);
            }
        }


        public static List<IEntityField> GetCalculatedFields(IEntity myEntity)
        {


            Type myHelperFieldsType  = Reflect.GetHelperFieldsType(myEntity);
            object myHelperFieldsObject = Activator.CreateInstance(myHelperFieldsType);

            List<IEntityField> myHelperFields = GetAllReadableValues(Of IEntityField)(myHelperFieldsObject);

            List<IEntityField> myCalculatedFields = new List<IEntityField>();

            bool isCalculatedField;
            foreach (IEntityField myHelperField in myHelperFields)
            {
                //Find Fields that are in HelperFields that are not in EntityFields
                isCalculatedField = !ContainsEntityField(myHelperField, myEntity.Fields);
                if (isCalculatedField == true)
                {
                    myCalculatedFields.Add(myHelperField);
                }
            }

            return myCalculatedFields;
        }



        private static bool ContainsEntityField(IEntityField myField, IEntityFields myFields)
        {
            foreach (IEntityField myEntityField in myFields)
            {
                if (myField.Name == myEntityField.Name)
                {
                    return true;
                }
            }
            return false;
        }

        public static object GetPropertyValue(object myObject, string PropertyName)
        {
            PropertyInfo myPropertyInfo = GetPropertyInfo(myObject, PropertyName);
            return myPropertyInfo.GetValue(myObject, null);
        }

         public static PropertyInfo GetPropertyInfo(object myObject, string PropertyName)
         {
             return GetPropertyInfo(myObject.GetType(), PropertyName);
         }

         public static PropertyInfo GetPropertyInfo(Type myType, string PropertyName)
         {
             try
             {
                 Type emptyParameters = null;
                 PropertyInfo myProperty = myType.GetProperty(PropertyName, emptyParameters);
                 return myProperty;
             }
             catch (Exception ex)
             {
                 throw new Exception("Could not get PropertyInfo for " + myType.Name + "." + PropertyName, ex);
                 return null;
             }
         }

I think i still need method code for

Reflect.GetHelperFieldsType(myEntity)

and

GetAllReadableValues(Of IEntityField)(myHelperFieldsObject)

rdhatch
User
Posts: 198
Joined: 03-Nov-2007
# Posted on: 06-May-2009 15:14:57   

Here you go!


    Public Shared Function GetAllReadableValues(Of TType)(ByVal myObject As Object) As List(Of TType)
        Dim myArrayList As ArrayList = GetAllReadableValues(myObject)
        Dim myList As New List(Of TType)
        For Each myElement As Object In myArrayList
            If TypeOf myElement Is TType Then myList.Add(myElement)
        Next
        Return myList
    End Function


    Public Shared Function GetHelperFieldsType(ByVal myEntity As IEntity) As Type
        Return GetHelperFieldsType(CType(myEntity, Object).GetType)
    End Function

    'Converts EntityType or RelationsType to HelperFieldsType
    Shared Function GetHelperFieldsType(ByVal CallerType As Type) As Type
        Dim myHelperTypeName As String
        Dim myHelperType As Type
        Dim myNamespace As String
        If CallerType.Name.EndsWith("Fields") Then
            'HelperField class was passed in
            Return CallerType
        ElseIf CallerType.Name.EndsWith("Entity") Then
            myHelperTypeName = CallerType.Name.Replace("Entity", "Fields")
            myNamespace = "EntityClasses"
        ElseIf CallerType.Name.EndsWith("Relations") Then
            myHelperTypeName = CallerType.Name.Replace("Relations", "Fields")
            myNamespace = "RelationClasses"
        End If
        If myNamespace <> Nothing Then
            myHelperType = CallerType.Assembly.GetType(CallerType.Namespace.Replace(myNamespace, "HelperClasses." & myHelperTypeName))
        End If
        If myHelperType Is Nothing Then
            Throw New Exception("Cannot find matching HelperType for Type : " & CallerType.Name & ". This may be caused by incorrect case-sensitive naming of the HelperClass!")
        End If
        Return myHelperType
    End Function

Hope this helps!

Ryan D. Hatch