trinity-rdf icon indicating copy to clipboard operation
trinity-rdf copied to clipboard

Setter of Manually Mapped Property Is Not Propagated Unless Getter Is Accessed

Open raffaelfoidl opened this issue 1 year ago • 1 comments

Hello,

I have noticed some pretty unexpected behavior (one might even argue incorrect?) today. We have some manual property mappings (as you describe in your docs here) in our application to represent mappings between enum members and their corresponding RDF named individuals.

In principle, this works quite well, however the following scenario left me a little puzzled:

  • Precondition: Have some model instance in your triple store.
  • Load this instance, update a property that is manually mapped as described above (but without accessing the property getter beforehand or afterwards)
  • Result: It seems that the persisted value has precedence over the just assigned property value - subsequent property get accesses return the previously persisted value. If, however, you execute a Commit() on the resource, property get accesses after a subsequent GetResource<> will yield the correct (i.e., the updated) value (so it was written to the store), but ONLY if there is no get() between the set() and Commit().

I'm afraid this may sound a bit confusing and unclear, therefore I have attached a MWE where one can easily play around with the different outcomes (depending on present or absent get accesses in different successions). It is a modified version of your "First Steps" tutorial, with the newest trinity version and notable changes being:

A micro ontology containing a property that represents the "enum mapping"
<http://www.semanticweb.org/raffa/ontologies/2023/8/enum-test> rdf:type owl:Ontology .

:hasMood rdf:type owl:ObjectProperty ; :range :Mood .

:Mood rdf:type owl:Class ;
      owl:equivalentClass [ rdf:type owl:Class ;
                            owl:oneOf ( :Confused
                                        :Happy
                                        :Sad
                                      )
                          ] .

:Confused rdf:type owl:NamedIndividual , Mood .
:Happy rdf:type owl:NamedIndividual , Mood .
:Sad rdf:type owl:NamedIndividual , Mood .
    <ontology uri="http://www.semanticweb.org/raffa/ontologies/2023/8/enum-test" prefix="enumtest">
      <filesource location="Ontologies/enum-test.ttl"/>
    </ontology>
The custom property mapping
public enum EnMood {    Sad, Happy, Confused   }

[RdfClass(SCHEMA.Person)]
public class Person : Thing {
        private PropertyMapping<Resource> moodMapping = new PropertyMapping<Resource>(nameof(Mood), ENUMTEST.hasMood);

        public EnMood? Mood
        {
            get
            {
                var moodResource = GetValue(moodMapping);
                return moodResource?.Uri.AbsoluteUri switch
                {
                    null => null,
                    ENUMTEST.Sad => EnMood.Sad,
                    ENUMTEST.Happy => EnMood.Happy,
                    ENUMTEST.Confused => EnMood.Confused,
                    _ => throw new ArgumentOutOfRangeException(nameof(moodResource))
                };
            }
            set
            {
                var moodResource = value switch
                {
                    null => null,
                    EnMood.Sad => enumtest.Sad,
                    EnMood.Happy => enumtest.Happy,
                    EnMood.Confused => enumtest.Confused,
                    _ => throw new ArgumentOutOfRangeException(nameof(value), value, null)
                };
                SetValue(moodMapping, moodResource);
            }
        }
}
A small driver program that may be used in different combinations
using Semiodesk.Trinity;
using System;
using System.Reflection;
using FirstSteps.ObjectModel;

namespace FirstSteps
{
    class Program
    {
        private static IModel Model { get; set; }

        static void Main()
        {
            OntologyDiscovery.AddAssembly(Assembly.GetExecutingAssembly());
            MappingDiscovery.RegisterCallingAssembly();

            var store = StoreFactory.CreateStore("provider=dotnetrdf");
            // store.Log = Console.WriteLine;
            Model = store.GetModel(new Uri("http://example.com/model"));
            var johnUri = new Uri("http://example.com/person#john");

            // pretend there's already a User instance in the triple store
            var john = Model.CreateResource<Person>(johnUri);
            john.Mood = EnMood.Happy;
            john.FirstName = "John";
            john.Commit();

            //---------------------------------------------------------------

            // now fetch the User from the database and update it
            var loadedJohn = Model.GetResource<Person>(johnUri);

            // Console.WriteLine("1: " + loadedJohn.Mood + " (expected: Happy)"); // comment out to trigger unexpected results in 2, 3, 4
            // Console.WriteLine("1: " + loadedJohn.FirstName + " (expected: John)"); // expected result in any case
            loadedJohn.Mood = EnMood.Confused;
            loadedJohn.FirstName = "Alice";

            Console.WriteLine("2: " + loadedJohn.Mood + " (expected: Confused)"); // comment out as well to trigger unexpected results only in 3 (and also 4 if the Commit() is commented out)
            Console.WriteLine("2: " + loadedJohn.FirstName + " (expected: Alice)"); // expected result in any case

            loadedJohn.Commit();
            Console.WriteLine("3: " + loadedJohn.Mood + " (expected: Confused)");
            Console.WriteLine("3: " + loadedJohn.FirstName + " (expected: Alice)");

            var loadedJohn2 = Model.GetResource<Person>(johnUri);
            Console.WriteLine("4: " + loadedJohn2.Mood + " (expected: Confused)");
            Console.WriteLine("4: " + loadedJohn2.FirstName + " (expected: Alice)");
        }
    }
}

Keep in mind that there appear no problems with properties that have a "default" mapping, i.e., the tests with the string property FirstName result in the expected behavior in any constellation/setup. You could remove them all.

Furthermore, to test different permutations, simply alternate between commenting in/out the Console.WriteLine calls that access the Person.Mood getter. For your convenience, I have also attached the solution to this issue so that everyone may try it out quickly.

MWE.zip

What is your assessment of this situation? Are we doing anything wrong here or is this, in fact, a bug in trinity?

I very much appreciate your help and king regards, Raffael

raffaelfoidl avatar Sep 20 '23 15:09 raffaelfoidl