Not Getting desired result for complex property when trying to set through custom EntityStaticMetaData

Posts   
 
    
Ashutosh
User
Posts: 15
Joined: 03-May-2024
# Posted on: 15-Nov-2025 18:49:32   

Setting EntityStaticMetaData custom

SetEntityCoreInfo("" + ObjectInfo.Name + "", ObjectInfo.IsInInheritanceChain
                    ? InheritanceHierarchyType.TargetPerEntity
                    : InheritanceHierarchyType.None, false, (int)ObjectInfo.EntityType, ObjectInfo.Type, typeof(EntityFactory), false);
                MethodInfo[] methods = typeof(EntityStaticMetaDataBase).GetMethods(BindingFlags.NonPublic | BindingFlags.Instance).Where(m => m.Name == "AddNavigatorMetaData").ToArray();
                Type currentEntityType = ObjectInfo.StaticType != null ? ObjectInfo.StaticType : ObjectInfo.Type;
                var propertyInfos = currentEntityType.GetProperties();
                foreach (var property in ObjectInfo.Properties.OfType<IReferenceProperty>())
                {
                    var propertyInfo = propertyInfos.Where(p => p.Name == property.Name).FirstOrDefault();
                    if (propertyInfo != null)
                    {
                        Type relatedEntityType = property.NetType;
                        if (property.Relation != null)
                        {
                           
                            if (property.Relation.Type == RelationType.OneToMany && property.Relation.RelationProperties.Count() > 0)
                            {
                                var relationProperties = new List<IRelationProperty>(property.Relation.RelationProperties);
                                foreach (IRelationProperty relationProperty in relationProperties)
                                {
                                    if (relationProperty.StartProperty != null && relationProperty.EndProperty != null)
                                    {
                                        // Filter by parameter types
                                        MethodInfo methodInfo = methods.Single(m =>
                                        m.GetParameters().Length == 7 &&
                                        m.GetParameters()[0].ParameterType == typeof(string) &&
                                        m.GetParameters()[1].ParameterType.IsGenericType && m.GetParameters()[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>) &&
                                        m.GetParameters()[2].ParameterType.IsGenericType && m.GetParameters()[2].ParameterType.GetGenericTypeDefinition() == typeof(Action<,>) &&
                                        m.GetParameters()[3].ParameterType.IsGenericType && m.GetParameters()[3].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>) &&
                                        m.GetParameters()[4].ParameterType == typeof(Func<IEntityRelation>) &&
                                        m.GetParameters()[5].ParameterType == typeof(Type) &&
                                        m.GetParameters()[6].ParameterType == typeof(int)
                                     );

                                        // Create an instance of the class
                                        // object entityStaticMetaDataInstance = Activator.CreateInstance(currentEntityType);
                                        var instance = new EntityStaticMetaDataBase();

                                        // Make the method generic with the dynamic types
                                        MethodInfo genericMethod = methodInfo.MakeGenericMethod(currentEntityType, relatedEntityType);

                                        string navigator = property.Name;
                                        var memberGetter = CreateGetterDelegate(currentEntityType, property.Name, relatedEntityType);
                                        var memberSetter = CreateMemberSetterDelegate(currentEntityType, relatedEntityType, property.Name);
                                        var propertyGetter = CreateGetterDelegate(currentEntityType, property.Name, relatedEntityType); // reuse
                                        Func<IEntityRelation> relationCreatorFunc = () => RelationFactory.CreateFor(property.Relation);// CreateRelationFactory(property.Relation.GetType());
                                        object typeOfRelatedEntity = relatedEntityType;
                                        object relatedEntityEnumTypeValue = property.Type.EntityType;

                                        //// Invoke the method dynamically
                                        genericMethod.Invoke(this, new object[]
                                        {
                                            navigator,
                                            memberGetter,
                                            memberSetter,
                                            propertyGetter,
                                            relationCreatorFunc,
                                            typeOfRelatedEntity,
                                            relatedEntityEnumTypeValue

                                        });
                                    }
                                }
                            }

                            else if ((property.Relation.Type == RelationType.ManyToMany || property.Relation.Type == RelationType.OneToMany) && property.Relation.SecondRelation != null)
                            {
                                MethodInfo methodInfo = methods.Single(m =>
                                {
                                    var p = m.GetParameters();
                                    return p.Length == 10 &&
                                            p[0].ParameterType == typeof(string) &&
                                            p[1].ParameterType.IsGenericType && p[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>) &&
                                            p[2].ParameterType.IsGenericType && p[2].ParameterType.GetGenericTypeDefinition() == typeof(Action<,>) &&
                                            p[3].ParameterType.IsGenericType && p[3].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>) &&
                                            p[4].ParameterType == typeof(Func<IEntityRelation>) &&
                                            p[5].ParameterType == typeof(Func<IEntityRelation>) &&
                                            p[6].ParameterType == typeof(string) &&
                                            p[7].ParameterType == typeof(string) &&
                                            p[8].ParameterType == typeof(Type) &&
                                            p[9].ParameterType == typeof(int);
                                });

                                // Make method generic
                                MethodInfo genericMethod = methodInfo.MakeGenericMethod(currentEntityType, relatedEntityType);

                                // Set up values
                                string navigator = property.Name;
                                var memberGetter = CreateGetterDelegate(currentEntityType, property.Name, relatedEntityType);
                                var memberSetter = CreateMemberSetterDelegate(currentEntityType, relatedEntityType, property.Name);
                                var propertyGetter = CreateGetterDelegate(currentEntityType, property.Name, relatedEntityType);
                                Func<IEntityRelation> relationCreatorFunc = () => RelationFactory.CreateFor(property.Relation.FirstRelation);
                                Func<IEntityRelation> secondRelationCreatorFunc = () => RelationFactory.CreateFor(property.Relation.SecondRelation);
                                string aliasStartEntity = property.Object.Name + "__";
                                string aliasIntermediateEntity = property.Relation.FirstRelation.EndObject.Name + "_";
                                object typeOfRelatedEntity = relatedEntityType;
                                object relatedEntityEnumTypeValue = property.Type.EntityType;

                                // Invoke
                                genericMethod.Invoke(this, new object[]
                                {
                                    navigator,
                                    memberGetter,
                                    memberSetter,
                                    propertyGetter,
                                    relationCreatorFunc,
                                    secondRelationCreatorFunc,
                                    aliasStartEntity,
                                    aliasIntermediateEntity,
                                    typeOfRelatedEntity,
                                    relatedEntityEnumTypeValue
                                });

                            }
                            else if (property.Relation.Type == RelationType.ManyToOne && property.Relation.RelationProperties.Count() > 0)
                            {
                                var relationProperties = new List<IRelationProperty>(property.Relation.RelationProperties);
                                foreach (IRelationProperty relationProperty in relationProperties)
                                {
                                    if (relationProperty.StartProperty != null && relationProperty.EndProperty != null)
                                    {
                                        MethodInfo methodInfo = methods.Single(m =>
                                        {
                                            var p = m.GetParameters();
                                            return p.Length == 12 &&
                                                    p[0].ParameterType == typeof(string) &&
                                                    p[1].ParameterType == typeof(string) &&
                                                    p[2].ParameterType.IsGenericType && p[2].ParameterType.GetGenericTypeDefinition() == typeof(Action<,>) &&
                                                    p[3].ParameterType.IsGenericType && p[3].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>) &&
                                                    p[4].ParameterType.IsGenericType && p[4].ParameterType.GetGenericTypeDefinition() == typeof(Action<,>) &&
                                                    p[5].ParameterType == typeof(IEntityRelation) &&
                                                    p[6].ParameterType == typeof(Func<IEntityRelation>) &&
                                                    p[7].ParameterType == typeof(string[]) &&
                                                    p[8].ParameterType == typeof(int[]) &&
                                                    p[9].ParameterType.IsGenericType && p[9].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>) &&
                                                    p[10].ParameterType == typeof(bool) &&
                                                    p[11].ParameterType == typeof(int);
                                        });

                                        MethodInfo genericMethod = methodInfo.MakeGenericMethod(currentEntityType, relatedEntityType);

                                        string navigator = property.Name;
                                        string navigatorInRelatedEntity = property.ReferencePropertyInRelatedObject != null ? property.ReferencePropertyInRelatedObject.ToString() : "";//dummy 
                                        var memberSetterFunc = CreateMemberSetterDelegate(currentEntityType, relatedEntityType, property.Name);
                                        var memberGetterFunc = CreateGetterDelegate(currentEntityType, property.Name, relatedEntityType);
                                        var propertySetterFunc = CreateMemberSetterDelegate(currentEntityType, relatedEntityType, property.Name);
                                        IEntityRelation staticRelationship = RelationFactory.CreateFor(property.Relation);
                                        Func<IEntityRelation> relationshipCreatorFunc = () => RelationFactory.CreateFor(property.Relation);
                                        string[] forfFieldNames = null;
                                        List<int> fieldarray = new List<int>();
                                        foreach (var field in property.Relation.RelationProperties) { fieldarray.Add(field.StartProperty.FieldIndex); }
                                        int[] fkFieldIndices = fieldarray.ToArray();
                                        object forfPropertyChangedHandlerFunc = null; //CreatePropertyChangedHandlerGetterDelegate(currentEntityType, property.ToString());//dummy
                                        bool containingIsOnPkSide = true;
                                        object relatedEntityEnumTypeValue = property.Type.EntityType;

                                        genericMethod.Invoke(this, new object[]
                                        {
                                        navigator,
                                        navigatorInRelatedEntity,
                                        memberSetterFunc,
                                        memberGetterFunc,
                                        propertySetterFunc,
                                        staticRelationship,
                                        relationshipCreatorFunc,
                                        forfFieldNames,
                                        fkFieldIndices,
                                        forfPropertyChangedHandlerFunc,
                                        containingIsOnPkSide,
                                        relatedEntityEnumTypeValue
                                        });
                                    }
                                }
                            }
                        }
                    }
                }
            }

            public static object CreateGetterDelegate(Type containingType, string propertyName, Type relatedType)
            {
                var param = LinqExpression.Parameter(containingType, "x");

                var propertyInfo = containingType
                  .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                  .FirstOrDefault(p => p.Name == propertyName)
                  ?? throw new InvalidOperationException(
                      $"Property '{propertyName}' not found on type {containingType.FullName}.");

                var propertyExpr = LinqExpression.Property(param, propertyInfo);

                bool isCollection =
                    typeof(IEntityCollectionCore).IsAssignableFrom(propertyInfo.PropertyType)
                    || (propertyInfo.PropertyType.IsGenericType &&
                        propertyInfo.PropertyType.GetGenericTypeDefinition() == typeof(IBusinessObjectCollection<>));

                Type returnType;
                LinqExpression converted;

                if (isCollection)
                {
                    // Return as IEntityCollectionCore to match LLBLGen expectations
                    returnType = typeof(IEntityCollectionCore);
                    converted = LinqExpression.Convert(propertyExpr, returnType);
                }
                else
                {
                    // Single entity: return actual relatedType (child entity)
                    returnType = relatedType;
                    converted = LinqExpression.Convert(propertyExpr, returnType);
                }

                var funcType = typeof(Func<,>).MakeGenericType(containingType, returnType);
                return LinqExpression.Lambda(funcType, converted, param).Compile();
            }

               public static Delegate CreateSetterDelegate(Type containingType, Type relatedType, string propertyName)
            {
                var containingParam = LinqExpression.Parameter(containingType, "a");
                var relatedParam = LinqExpression.Parameter(relatedType, "b");

                // Find SetRelatedEntityProperty(string, IEntityCore) on BusinessObject / IEntityCore
                var boType = typeof(BusinessObject);
                var methodInfo = boType.GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                                    .FirstOrDefault(m => m.Name == "SetRelatedEntityProperty"
                                                          && m.GetParameters().Length == 2
                                                          && m.GetParameters()[0].ParameterType == typeof(string)
                                                          && typeof(IEntityCore).IsAssignableFrom(m.GetParameters()[1].ParameterType));
                if (methodInfo == null)
                    throw new InvalidOperationException($"SetRelatedEntityProperty method not found on type '{boType.FullName}'.");

                // Call a.SetRelatedEntityProperty("PropertyName", (IEntityCore)b)
                var call = LinqExpression.Call(
                    LinqExpression.Convert(containingParam, boType), // ensure we invoke on BusinessObject
                    methodInfo,
                    LinqExpression.Constant(propertyName),
                    LinqExpression.Convert(relatedParam, typeof(IEntityCore))
                );

                var lambda = LinqExpression.Lambda(call, containingParam, relatedParam).Compile();

                return lambda;
            }
          
            public static Delegate CreateMemberSetterDelegate(Type containingType, Type relatedType, string propertyName)
            {
                var propertyInfo = containingType
                                    .GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                                    .FirstOrDefault(p => p.Name == propertyName)
                                    ?? throw new InvalidOperationException(
                                        $"Property '{propertyName}' not found on type {containingType.FullName}.");

                bool isCollection = typeof(IEntityCollectionCore).IsAssignableFrom(propertyInfo.PropertyType)
                                    || (propertyInfo.PropertyType.IsGenericType);

                if (!isCollection)
                {
                    // reuse CreateSetterDelegate for single-valued navs
                    return CreateSetterDelegate(containingType, relatedType, propertyName);
                }

                // For collection navigators, the setter should be Action<TContaining, TChild>
                var parentParam = LinqExpression.Parameter(containingType, "parent");
                var childParam = LinqExpression.Parameter(relatedType, "child");

                // SetRelatedEntityProperty on the child: child.SetRelatedEntityProperty("NavigatorName", parent);
                var entityCoreType = typeof(IEntityCore);
                var setRelatedOnChild = entityCoreType.GetMethod("SetRelatedEntityProperty",
                    BindingFlags.Instance | BindingFlags.Public);

                if (setRelatedOnChild == null)
                {
                    // fallback: search on BusinessObject
                    setRelatedOnChild = typeof(BusinessObject).GetMethod("SetRelatedEntityProperty",
                        BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
                }

                var call = LinqExpression.Call(
                    LinqExpression.Convert(childParam, setRelatedOnChild.DeclaringType),
                    setRelatedOnChild,
                    LinqExpression.Constant(propertyName),
                    LinqExpression.Convert(parentParam, typeof(IEntityCore)) // many implementations accept object for parent
                );

                var actionType = typeof(Action<,>).MakeGenericType(containingType, relatedType);
                return LinqExpression.Lambda(actionType, call, parentParam, childParam).Compile();
            }

What is the problem in this method I am getting error in unit test for complex property

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39988
Joined: 17-Aug-2003
# Posted on: 16-Nov-2025 08:12:20   

You posted a lengthy piece of code and I have to figure out where some vague issue is? simple_smile I'm sorry but you're not even describing what the 'desired result' is. Have you tried stepping through the code with a debugger? Enabled tracing https://www.llblgen.com/Documentation/5.12/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/gencode_troubleshootingdebugging.htm to see what queries are generated?

Frans Bouma | Lead developer LLBLGen Pro
Ashutosh
User
Posts: 15
Joined: 03-May-2024
# Posted on: 17-Nov-2025 06:26:26   

Otis wrote:

You posted a lengthy piece of code and I have to figure out where some vague issue is? simple_smile I'm sorry but you're not even describing what the 'desired result' is. Have you tried stepping through the code with a debugger? Enabled tracing https://www.llblgen.com/Documentation/5.12/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/gencode_troubleshootingdebugging.htm to see what queries are generated?

So, the Idea here to replicate LLBLGen's inbuild EntityStaticMetaData constructor's AddNavigatorMetaData method initialization. This code works fine for Single Navigator property but fails for Collection/Complex Property to initialize properly. I just want you to analysis and suggest how should we achieve this replication for custom code.

For Example we have a property with one-to-many relation with ParentProperty (ReferencePropertyInRelatedObject) such as

public virtual IBusinessObjectCollection<NewEntity> ChildProperty { get { return (IBusinessObjectCollection<NewEntity>)GetPropertyValue("ChildProperty"); } }

is not initializing when we try to set the value through EntityStaticMetaData's AddNavigatorMetaData method.

when we are accessing this property I am getting NullReference Exception (Object Not set to an instance of an object)

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39988
Joined: 17-Aug-2003
# Posted on: 17-Nov-2025 09:06:59   

Why would you want to replicate the AddNavigatorMetaData method? I'm not going to analyze pages of your code for a crash somewhere of which you don't specify where it happens

Frans Bouma | Lead developer LLBLGen Pro