JSON Serialize a PredicateExpression

Posts   
 
    
Valemus avatar
Valemus
User
Posts: 37
Joined: 09-Jan-2018
# Posted on: 08-Jan-2019 17:36:05   

Hi! Is this possible? Or just an insane idea? I'm using version 5.3.5 with Adapters, and when I try to do something like this:

 PredicateExpression predicate = new PredicateExpression(CustomerFields.Name.Contains("A"));
 var serialized = JsonConvert.SerializeObject(predicate);

I get:

Newtonsoft.Json.JsonSerializationException: Error getting value from 'Field' on 'SD.LLBLGen.Pro.ORMSupportClasses.FieldLikePredicate'. ---> System.InvalidOperationException: This object was constructed using a non-selfservicing constructor. Can't retrieve an IEntityField after that.

disappointed ?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 09-Jan-2019 10:47:12   

Ah, I think what it is that causes this.

It simply serializes the whole object using properties, so it reads all properties. However predicates have a 'Field' property which is only valid for selfservicing predicates. (Backwards compatibility and all that). So this doesn't work.

I think you should use the xml route instead. So you should use: https://www.llblgen.com/Documentation/5.5/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/Adapter/Distributed%20systems/gencode_webservices.htm#json-support

var json = config.Formatters.JsonFormatter;  
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;  
json.SerializerSettings.ContractResolver = new DefaultContractResolver()   
        {   
            IgnoreSerializableInterface = true,   
            IgnoreSerializableAttribute = true   
        }; 

Frans Bouma | Lead developer LLBLGen Pro
Valemus avatar
Valemus
User
Posts: 37
Joined: 09-Jan-2018
# Posted on: 09-Jan-2019 15:00:53   

Otis wrote:

Ah, I think what it is that causes this.

It simply serializes the whole object using properties, so it reads all properties. However predicates have a 'Field' property which is only valid for selfservicing predicates. (Backwards compatibility and all that). So this doesn't work.

I think you should use the xml route instead. So you should use: https://www.llblgen.com/Documentation/5.5/LLBLGen%20Pro%20RTF/Using%20the%20generated%20code/Adapter/Distributed%20systems/gencode_webservices.htm#json-support

var json = config.Formatters.JsonFormatter;  
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;  
json.SerializerSettings.ContractResolver = new DefaultContractResolver()   
        {   
            IgnoreSerializableInterface = true,   
            IgnoreSerializableAttribute = true   
        }; 

Thank you Otis, but there's something I'm still missing...

I've changed my code to:


var settings = new JsonSerializerSettings()
            {
                ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore,
                PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
                ContractResolver = new DefaultContractResolver()
                {
                    IgnoreSerializableInterface = true,
                    IgnoreSerializableAttribute = true
                }
            };

PredicateExpression predicate = new PredicateExpression(CustomerFields.Name.Contains("A"));
var serialized = JsonConvert.SerializeObject(predicate, settings);
        

but I get the same error...

[update]

I've also tried this:

 public class ExludeFieldContractResolver : DefaultContractResolver
    {
        public static ExludeFieldContractResolver Instance { get; } = new ExludeFieldContractResolver();

        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty property = base.CreateProperty(member, memberSerialization);
            if (!property.Ignored &&  member.Name == "Field") 
            {
                property.Ignored = true;
            }
            return property;
        }
    }

and

 var settings = new JsonSerializerSettings()
            {
                ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore,
                PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects,
                ContractResolver = new ExludeFieldContractResolver()
                {
                    IgnoreSerializableInterface = true,
                    IgnoreSerializableAttribute = true
                }
            };

            PredicateExpression predicate = new PredicateExpression(CustomerFields.Name.Contains("A"));
            var serialized = JsonConvert.SerializeObject(predicate, settings);

And... ok, it serialize it and Field is ignored (awful, but it works)

But hey, this does not deserialize back frowning

Newtonsoft.Json.JsonSerializationException: Cannot create and populate list type SD.LLBLGen.Pro.ORMSupportClasses.PredicateExpression. Path '', line 1, position 1.
    in Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewList(JsonReader reader, JsonArrayContract contract, Boolean& createdFromNonDefaultCreator)
Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 10-Jan-2019 09:28:49   

I hoped it was sufficient, but apparently not. XML serialization is supported using a unit of work, or when you need to serialize it over a service interface (but not directly, there's no IXmlSerializable implementation on the predicate), but json isn't supported out of the box. The thing with serialization is that as soon as you implement it, it's likely already outdated, or not compatible or other issues occur with it and therefore we didn't implement 'yet another serialization' route for json.

In short: not supported.

Not sure what your plan is, but it's likely you can solve this more appropriate with an API method on your service which accepts a Like pattern instead of passing a predicate instance.

Frans Bouma | Lead developer LLBLGen Pro
Valemus avatar
Valemus
User
Posts: 37
Joined: 09-Jan-2018
# Posted on: 10-Jan-2019 10:05:06   

Otis wrote:

In short: not supported.

Not sure what your plan is, but it's likely you can solve this more appropriate with an API method on your service which accepts a Like pattern instead of passing a predicate instance.

Thanks Otis, I see. My plan is to dinamically create expressions and store them to a database to retrieve them later. Do you have any suggestion on how to achieve something like that without serialization?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 10-Jan-2019 10:42:09   

What's the end goal with the expressions? Isn't it better to generate them on the fly when they're needed?

Frans Bouma | Lead developer LLBLGen Pro
Valemus avatar
Valemus
User
Posts: 37
Joined: 09-Jan-2018
# Posted on: 10-Jan-2019 11:17:18   

Otis wrote:

What's the end goal with the expressions? Isn't it better to generate them on the fly when they're needed?

I just need to generate expressions and send them remotely.

The only alternative I see here is to recreate by myself a class that replicates almost exactly the structure of a PredicateExpression, serialize it, send it, remotely deserialize it and use it to rebuild a PredicateExpression.

However it's not a clean job, it replicates structures, causes overhead, a new layer of possible bugs etc.

So before going this way I'm just wondering if there's a better one.

Really am I the first one to need such a thing? flushed

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 10-Jan-2019 12:36:58   

The usual approach is to offer methods on the service which do what you want, instead of on the client creating predicates that are 1:1 bypassed to the service and executed as-is.

So instead of e.g. service.GetCustomer(IPredicate predicate) you do service.GetCustomer(string country) and build the predicate in that method using the country specified.

It's easier to limit the service's API this way to what you want to expose.

Another way is to use the XML pipeline.

You can serialize the predicate to XML using predicate.WriteXml(xmlwriter). It will write the xml string to the writer specified. You can deserialize the predicate's contents then again by using predicate.ReadXml(string)

To do that easily, use the XmlHelper class that's part of the ORMSupportClasses:

var xml = XmlHelper.SerializeObjectToXmlString(toSerialize, serializationFunc);

where 'serializationFunc' is e.g. (a, b)=>a.WriteXml(b) a is a predicate, b is an XmlWriter.

To get the object back, use:

var toSerializeDeserialized = XmlHelper.DeserializeObjectFromXmlString<FieldLikePredicate>(deSerializationFunc, xml);

where deSerializationFunc is (a, b) => ((IPredicate)a).ReadXml(b)

You can then pass the string to the service if you want or store in a DB.

Be aware that this doesn't version. Storing serialized data in a Database to be used later sounds great, but it might be the types change in the future and the data can't be deserialized to the object. With predicates and the like I don't expect that to happen tho, but it's a thing to take into account, also with e.g. keeping serialized entities in a database (some people do that... simple_smile )

Frans Bouma | Lead developer LLBLGen Pro
Valemus avatar
Valemus
User
Posts: 37
Joined: 09-Jan-2018
# Posted on: 11-Jan-2019 09:07:47   

Thank you Otis,

I'll use the Xml method. simple_smile

Thanks again.