Custom EntityField2 Alias issue

Posts   
 
    
Benjamin23
User
Posts: 26
Joined: 15-Nov-2017
# Posted on: 27-Nov-2018 09:44:57   

In our code, we have an extension upon the EntityField2.

We added a custom JsonEntityField2 which inherits from the EntityField2 and adds some additional behavior. It's used to simplify the searching by JSON fields.

The problem we are having currently is with aliases. Meaning, everything works fine when we are filtering by one JSON field, but when filtering by the parent entities JSON field and it's related entities JSON, it's causing an ambiguous column issue which is fine. We examine the query and it has two queries that don't have their parent specified.

We tried adding an alias and including that in our JSON predicate building, but the cure is then generating the query with that alias and LLBLGen converts the alias to its other form (i.e. alias "A" is converted to "LPA_S1" and in our predicate, it's still "A")

Is there any way of retrieving that "LPA_S1" predicate? Or what would be a better way of adding this kind of behavior? Do you guys have some suggestions?

What we are trying to accomplish is to simplify the building of predicate expressions for nested JSON queries and as stated, this works perfectly when we have one JSON query, but in case of querying on the related entity, it's causing issues.

Our specs are:

LLBLGen Pro 5.3.3. Runtime version is 5.3.3.0 Database is PostgreSQL 10.4 and I'm using npgsql 3.2.5

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 27-Nov-2018 11:26:59   

With 'alias' you mean alias of the table/view that's targeted, not a field alias? A bit of code would help, as I don't know exactly what you're doing / what the results are so I can't point you to something that might be the problem.

In any case, an EntityField2's ObjectAlias (https://www.llblgen.com/Documentation/5.5/ReferenceManuals/LLBLGenProRTF/html/1F76C240.htm) property will be used to generate the table reference part of a field in the SQL query. The alias set there will then be converted to an internal alias using the ContainedObjectName and ActualContainedObjectName taken into account as well.

However as you inhert from EntityField2, none of that should really matter and it should work as-is (if you set the ObjectAlias field that is). But a bit of code might help.

Frans Bouma | Lead developer LLBLGen Pro
Benjamin23
User
Posts: 26
Joined: 15-Nov-2017
# Posted on: 27-Nov-2018 11:56:51   

The additional logic that we added is related to the SetExpression method.

We have helper code that constructs the JSON query and then passes it to the base SetExpression method.

In that helper, we are using the ObjectAlias property on the base EntityField2 class but it the moment we are using it, it still has the alias that we set to it (i.e. "A").

The main logic is in the GetJsonFieldExpression method and that's the source of the problem currently.

Here is our example class:


 public class JsonEntityField : EntityField2
    {
        protected const string sqlExpression = "({0}){{2}}";
        protected const string sqlExpressionCast = "({0})::{{2}}";
        protected string dbType = null;
        protected string jsonDbFieldName = null;
        protected Type systemType = null;
        private static Type stringType = typeof(string);
        private IExpression jsonFieldExpression = null;

        public JsonEntityField(string field)
            : this(String.Empty, field, String.Empty, stringType)
        {
        }

        public JsonEntityField(string jsonDbFieldName, string field)
            : this(jsonDbFieldName, field, String.Empty, stringType)
        {
        }

        public JsonEntityField(string jsonDbFieldName, string field, string dbType, Type systemType)
            : this(jsonDbFieldName, field, dbType, systemType, null, null)
        {
        }

        public JsonEntityField(string jsonDbFieldName, string field, string dbType, Type systemType, string fieldAlias, string objectAlias)
            : base(field, null, AggregateFunction.None, systemType, objectAlias, 0, 0, 0)
        {
            jsonFieldExpression = GetJsonFieldExpression(jsonDbFieldName, field, dbType);
            SetExpression(jsonFieldExpression);

            if (!string.IsNullOrWhiteSpace(fieldAlias))
            {
                this.SetFieldAlias(fieldAlias);
            }

            this.systemType = systemType;
            this.jsonDbFieldName = jsonDbFieldName;
            this.dbType = dbType;
        }

        protected virtual IExpression GetJsonFieldExpression(string jsonDbFieldName, string field, string dbType)
        {
            Func<string> compile = () =>
            {
                if (String.IsNullOrWhiteSpace(jsonDbFieldName))
                {
                    jsonDbFieldName = "Json";
                }

                if (String.IsNullOrWhiteSpace(field))
                {
                    throw new BaasicArgumentNullException("field");
                }

                string sqlExpressionResult = null;
                if (!String.IsNullOrWhiteSpace(dbType))
                {
                    sqlExpressionResult = sqlExpressionCast;
                }
                else
                {
                    dbType = null;
                    sqlExpressionResult = sqlExpression;
                }

                string result = null;
                string parsedField = field.Trim(' ').Trim('.');
                if (parsedField.Contains('.'))
                {
                    //where ("Json"->'Object'->'Object2'->>'SubObjField2')::int > 3
                    StringBuilder exp = new StringBuilder();
                    List<string> tokens = new List<string>(parsedField.Split('.'));
                    for (int i = 0; i < tokens.Count; i++)
                    {
                        if (!String.IsNullOrWhiteSpace(tokens[i]))
                        {
                            exp.Append(String.Format("-{0}'{1}'", i == tokens.Count - 1 ? ">>" : ">", tokens[i]));
                        }
                    }
                    sqlExpressionResult = String.Format(sqlExpressionResult, "\"{0}\"{1}");
                    result = String.Format(sqlExpressionResult, jsonDbFieldName, exp.ToString(), dbType);
                }
                else
                {
                    sqlExpressionResult = String.Format(sqlExpressionResult, "\"{0}\"->>'{1}'");
                    result = String.Format(sqlExpressionResult, jsonDbFieldName, field, dbType);
                }

                if (!string.IsNullOrWhiteSpace(this.ObjectAlias))
                {
                    result = $"\"{ObjectAlias}\".{result}";
                }
                if (!string.IsNullOrWhiteSpace(this.Alias) && !this.Alias.Equals(this.Name))
                {
                    result = $"{result} AS \"{Alias}\"";
                }

                return result;
            };
            return new LazyRawExpression(compile);
        }
    }

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39614
Joined: 17-Aug-2003
# Posted on: 27-Nov-2018 12:15:24   

I think it would be simpler if you implement IExpression in a separate class and add an instance of that to a normal field. Then generate the json string you need when that IExpression implementation is evaluated by the DQE. See for instance how that's done in the DbFunctionCall() expression or the Expression class in the sourcecode archive.

So i.o.w. you implement the ToQueryText() to produce the string you need at query time, and field names etc. are taken care of.

A field with an IExpression set is replaced with that IExpression. At query generation time the ToQueryText() method is called, and it has to produce the right values, but that's straightforward as it has the DbSpecificCreator instance to produce field names for field objects so you don't need to do much work simple_smile

Frans Bouma | Lead developer LLBLGen Pro