- Home
- LLBLGen Pro
- LLBLGen Pro Runtime Framework
Noob question about generated code with partial classes
Joined: 12-Nov-2008
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.
Joined: 03-Nov-2007
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
Joined: 12-Nov-2008
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.
Joined: 03-Nov-2007
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.
Joined: 12-Nov-2008
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)
Joined: 03-Nov-2007
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