This project has moved. For the latest updates, please go here.

Issue with InverseProperty collections (bug?)

Nov 24, 2013 at 9:24 PM
I'm working on a sort of personal research endeavor to create a highly virtualized computing environment, across machine, network, and even programming language boundaries. I believe that graph DBs have the structure which makes it practical to achieve this, so this is a great time to get started.

I have three Entity Framework datatypes.
    [Entity]
    public interface IFunction
    {
          INodeType DeclaringType { get; set; }
    }

    [Entity]
    public interface INodePropertyDefinition
    {
         INodeType DeclaringType { get; set;  }
    }

    [Entity]
    public interface INodeType
    {
        [InverseProperty("DeclaringType")]
        ICollection<INodePropertyDefinition> Properties { get; }

        [InverseProperty("DeclaringType")]
        ICollection<IFunction> Functions
    }

void Foo()
{
            INodeType type = Context.NodeTypes.Create();
            Context.NodeTypes.Add(type);
            type.Label = "MyType";

            IFunction func = Context.Functions.Create();
            Context.Functions.Add(func);
            func.DeclaringType = type;
            func.Label = "MyFunction";

            INodePropertyDefinition prop = Context.NodePropertyDefinitions.Create();
            Context.NodePropertyDefinitions.Add(prop);
            prop.Label = "MyProperty";
            prop.DeclaringType = type;

            Context.SaveChanges();

            type = Context.NodeTypes.Where(t => t.Label == "MyType").Single();
            var funcs = type.Properties.ToList();
            var props = type.Functions.ToList();

            string s = funcs[0].Label; //"MyProperty"
            s = funcs[1].Label;  //"MyFunction"
}
From my point of view, I believe that "funcs" and "props" should have only one item each, but here they have two items each. I briefly looked through the BrightstarDB code and it has something to do with the way the namespaces are set up with the properties, and they seem to ignore the entity type in which the property is defined.

I don't suspect there is an easy way to fix it but since this is important to my project I'm happy to help.
Nov 25, 2013 at 1:48 AM
There are a number of ways to approach this issue. I don't want to try to suggest the wrong thing since I still know little about how the DB and RDF actually is supposed to work.

One approach to deal with this is, when using the high-level EF API, to prefix the triple predicate property name with the Entity Framework model name. So if I have an interface "INode", with property "Label", the predicate would be http://brightstardb.com/namespaces/default/node.label (instead of http://brightstardb.com/namespaces/default/label).

Another approach would be to leave the naming convention as-is, and add some filtering wizardry to filter the types of data to meet the specifications. That may be too much of a detriment to the algorithmic efficiency to be suitable for a database system however.

Any solution provided may also introduce new issues down the road, particularly with Entity Inheritance (something that will be necessary for a portion of users). I modified my GraphContext.tt to support entity inheritance (a feature I would like to suggest for BrightstarDB but haven't yet found a good way to do so yet). I hope that a solution to this issue can be found in such a way that still makes entity inheritance practical in the future.
Nov 25, 2013 at 2:36 AM
It turns out that the DB system already seems to do a good job of dealing with the inheritance issue, as it just treats the underlying data the same as if the Entity Type inherited multiple interfaces, and I think that data layout will work fine in the case of inherited classes as well.

https://github.com/NuzzIndustries/BrightstarDB/commit/24120c6e4147205f9499d754789208daf3b3f2e5 (not the nicest looking code but in my case it fixed the issue).

Let me know what you think, thanks!
Coordinator
Nov 25, 2013 at 5:43 PM
The type mapping in the entity framework is deliberately left quite loose to enable a more flexible run-time environment. However, I can see your problem here :)

I definitely do not want to change the URI scheme for the auto-generated properties, but your second filtering solution (or something like it) is a possibility. The only problem with it is that it forces a full load of related data objects and only then filters them on the client side, that could be a pain if you have a type with 1 property and 100 functions and your code only cares about properties. What might be a better alternative would be to allow you to add a type filter on the InverseProperty attribute so that it could be passed through to the code that loads the data objects enabling a minimal set to be pulled back from the database.

However, the core problem is actually that you are getting the same generated URI for the two DeclaringType properties. There are a couple of possible alternative approaches that might work for you:

1) Be explicit about the property type for the two DeclaredType properties, and declare them as different URIs:
    [Entity]
    public interface IFunction
    {
          [PropertyType("http://nuzzindustries.com/computingframework/functionHasDeclaringType")]
          INodeType DeclaringType { get; set; }
    }

    [Entity]
    public interface INodePropertyDefinition
    {
         [PropertyType("http://nuzzindustries.com/computingframework/propertyHasDeclaringType")]
         INodeType DeclaringType { get; set;  }
    }
You can reduce your typing if you add a NamespaceDeclaration assembly property to your project - see http://brightstardb.readthedocs.org/en/latest/Entity_Framework/#annotations

2) If your properties were the other way around, I think this problem would go away, because the generated property URIs would be different for the two different usages:
[Entity]
    public interface IFunction
    {
          [InverseProperty("Functions")]
          INodeType DeclaringType { get; set; }
    }

    [Entity]
    public interface INodePropertyDefinition
    {
         [InverseProperty("Properties")]
         INodeType DeclaringType { get; set; }
    }

    [Entity]
    public interface INodeType
    {
        ICollection<INodePropertyDefinition> Properties { get; }

        ICollection<IFunction> Functions {get; }
    }
Marked as answer by Nuzz604 on 12/18/2013 at 1:13 AM
Nov 27, 2013 at 7:13 AM
Awesome, I think that helps a lot. I would like to add these URIs via reflection and not by using attributes, as I am aiming to keep the maintenance footprint of the model classes to a minimum.

Can there be a feature to overwrite existing property hints via Context.Mapping, or some other way to specify the URIs during app initialization? Right now I can do this with reflection but I try to keep that sort of thing to a minimum.

Thank you for all the help.
Coordinator
Nov 29, 2013 at 7:40 AM
There actually is a hook for this "under the covers". It shouldn't be too hard to expose the mapping provider or provide some way to override it. I'll take a look today.
Nov 29, 2013 at 8:37 AM
I already got it set up over here, but incase anyone is interested in some code, which is not polished or complete in any way, but should give you a good understanding of the workaround I did here:
public static void SpecifyURIMappings(EntityMappingStore mappings)
        {
            var f = mappings.GetType().GetField("_propertyHints", BindingFlags.NonPublic | BindingFlags.Instance);
            var pHints = f.GetValue(mappings) as Dictionary<PropertyInfo, PropertyHint>;

            //Load Types
            foreach (var type in AllCLRTypes) //AllCLRTypes = Collection of entity interfaces
            {
                foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!pHints.ContainsKey(property))
                        continue;

                    if (!type.Name.StartsWith("I"))
                        throw new InvalidOperationException("Expected interface name starting with I");
                    
                    var hint = pHints[property];
                    if (hint.MappingType == PropertyMappingType.InverseArc)
                        continue; //do inverse properties in second pass

                    var uri = string.Format("http://www.nuzzgraph.com/Entities/{0}/Properties/{1}", type.Name.Substring(1), property.Name);
                    pHints[property] = new PropertyHint(hint.MappingType, uri);
                }
            }

            //Second pass for inverse properties
            foreach (var type in AllCLRTypes)
            {
                foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
                {
                    if (!pHints.ContainsKey(property))
                        continue;

                    if (!type.Name.StartsWith("I"))
                        throw new InvalidOperationException("Expected interface name starting with I");

                    var hint = pHints[property];
                    if (hint.MappingType != PropertyMappingType.InverseArc)
                        continue; //not an inverse property

                    var att = property.GetCustomAttributes(typeof(InversePropertyAttribute), false).Single() as InversePropertyAttribute;
                    string pName = att.InversePropertyName;

                    //Load the inverse property info, and set the URI accordingly
                    System.Type inverseType = null;
                    if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(ICollection<>))
                        inverseType = property.PropertyType.GetGenericArguments()[0];
                    else
                        inverseType = property.PropertyType;

                    var inverseProperty = inverseType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name == pName).Single();

                    var uri = string.Format("http://www.nuzzgraph.com/Entities/{0}/Properties/{1}", inverseType.Name.Substring(1), inverseProperty.Name);
                    pHints[property] = new PropertyHint(hint.MappingType, uri);
                }
            }
        }
Nov 29, 2013 at 8:39 AM
As you can see, pretty verbose. A simpler solution would be very appreciated of course! But no need to go far out of your way for it.
Coordinator
Nov 29, 2013 at 9:58 AM
Cool! Glad you got something to work. I'm going to have a think about a way to make altering the default behaviours of the entity mapping stuff. I could just add a plain override option but that would require you to implement the whole EntityMappingStore interface, which would be overkill in a situation like this where you just want to change the default URI naming for properties...
Feb 8, 2016 at 11:17 PM
I believe I am experiencing the same and will take a look tomorrow at implementing above suggestions.
But in case my issue is different I am posting my code.

I expected thing1.Aggregates to have 0 but it has 2, itself and thing1.
[Entity]
    public interface IThing
    {
        string Identifier { get; set; }

        string Kind { get; set; }

        [InverseProperty("Thing")]
        ICollection<IRole> Aggregates { get; set; }
    }

    [Entity]
    public interface IRole
    {
        string Name { get; set; }

        IThing Thing { get; set; }
    }
            var thing1 = KPEntityContext.Instance.Things.Create();
            thing1.Identifier = "US0200PLC1111";
            thing1.Kind = "dual weighing system component";

            var thing2 = KPEntityContext.Instance.Things.Create();
            thing2.Identifier = "US0200PLC1000";
            thing2.Kind = "dual weighing system";
            
            var role = KPEntityContext.Instance.Roles.Create();
            role.Thing = thing1;
            role.Name = "bulk weight";

            thing2.Aggregates.Add(role);
Feb 9, 2016 at 1:38 AM
Adding PropertyType fixed the issue.
I still don't really understand why, but whatever.
    [Entity]
    public interface IThing
    {
        string Identifier { get; set; }

        string Kind { get; set; }

        [InverseProperty("Thing")]
        [PropertyType("kp:thingHasParts")]
        ICollection<IRole> Aggregates { get; set; }
    }
Feb 15, 2016 at 2:51 AM
Edited Feb 15, 2016 at 2:57 AM
techquila wrote:
1) Be explicit about the property type for the two DeclaredType properties, and declare them as different URIs:
So with the below code, the inverse properties do not work, meaning the relationships return more items than they should as in my original post.

Any suggestions?
    [Entity("kp:thing")]
    public interface IThing : IConnectedEntity
    {
        [JsonProperty("kind")]
        [PropertyType("kp:kind")]
        string Kind { get; set; }

        [JsonProperty("aggregates")]
        [PropertyType("kp:hasParts")]
        [InversePropertyType("kp:partOf")]
        ICollection<IAggregateRole> Aggregates { get; set; }

        [JsonProperty("features")]
        [PropertyType("kp:hasFeatures")]
        [InversePropertyType("kp:ownedBy")]
        ICollection<IFeature> Features { get; set; }
    }

    [Entity("kp:aggregateRole")]
    public interface IAggregateRole : IEntity
    {
        [JsonProperty("role")]
        [PropertyType("kp:name")]
        string Name { get; set; }

        [JsonProperty("thing")]
        [PropertyType("kp:partOf")]
        //[InversePropertyType("kp:hasParts")]
        IThing Thing { get; set; }
    }

    [Entity("kp:feature")]
    public interface IFeature : IConnectedEntity
    {
        [JsonProperty("owner")]
        [PropertyType("kp:ownedBy")]
        //[InversePropertyType("kp:hasFeatures")]
        IThing Owner { get; set; }
    }
Feb 16, 2016 at 2:45 PM
Soooo... trying different things and this combination works for me and I am able to query both collections from IThing.
    [Entity("kp:thing")]
    public interface IThing : IConnectedEntity
    {
        [JsonProperty("kind")]
        [PropertyType("kp:kind")]
        string Kind { get; set; }

        [JsonProperty("aggregates")]
        [PropertyType("kp:hasParts")]
        //[InversePropertyType("kp:partOf")]
        ICollection<IAggregateRole> Aggregates { get; set; }

        [JsonProperty("features")]
        [PropertyType("kp:hasFeatures")]
        [InversePropertyType("kp:ownedBy")]
        ICollection<IFeature> InternalFeatures { get; set; }
    }

    [Entity("kp:feature")]
    public interface IFeature : IConnectedEntity
    {
        [JsonProperty("owner")]
        [PropertyType("kp:ownedBy")]
        //[InversePropertyType("kp:hasFeatures")]
        IThing Owner { get; set; }
    }

    [Entity("kp:aggregateRole")]
    public interface IAggregateRole : IEntity
    {
        [JsonProperty("role")]
        [PropertyType("kp:name")]
        string Name { get; set; }

        [JsonProperty("thing")]
        [PropertyType("kp:partOf")]
        [InversePropertyType("kp:hasParts")]
        IThing Thing { get; set; }
    }
Coordinator
Feb 23, 2016 at 5:28 PM
Hi,

That is really interesting - off the top of my head I can't see exactly why one works and the other doesn't, but it does give me something to try out and try to reproduce the issue.

Handling inverses and keeping collections up-to-date makes my head hurt! :)

I've opened a bug for this on GitHub: https://github.com/BrightstarDB/BrightstarDB/issues/272

@fixitchris - thanks for the report! If you have any additional info that could help in tracking this done let me know, either on this thread or on the GitHub issue.

Cheers

Kal