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

Accessing Entities added to a context but not yet saved

Mar 18, 2014 at 1:16 PM
I've come across something that at first seems peculiar, but is probably on purpose. I'm Adding entities to a set via Add function and not Create. Now, I have the need to then access the entities in the set to make sure I'm not adding duplicate entities on next Add. However, this is a bulk operation and thus I don't want to call SaveChanges after every add. This seems to be the only way to make entities accessible that have been added. Am I missing something or is there another way that I can access these uncommitted entities?

This actually bring up another question. Is there a way to override SaveChanges? If an entity is edited I want to be able to check that it's not modified in such a way that it matches an existing entity.

Perhaps I'm trying too hard to make b* work for me. Or perhaps I just don't understand it fully.
From what I understand I'm not able to use my own property to define the entity key as anything but a GUID? Also, from other topics I've read I realize it's not possible to use composite keys due to the underlying db definitions - which is why i'm trying to do all this custom equality checks.

Any insight or suggestions would be great.

Thanks
Coordinator
Mar 18, 2014 at 2:37 PM
The problem is that a query against the context is a query against the triple store, and until you call SaveChanges, locally added entities are not in the triple store. Probably the best way to address your requirement would be to track the added items up until you are ready to call SaveChanges.

There is a SavingChanges event that you can use to be notified when a SaveChanges is underway - you can examine and modify the entities before they are serialized to triples and sent to the store, but you cannot abort the save here other than by throwing an exception. Alternatively you can create a class derived from BrightstarDB.EntityFramework.BrightstarEntityContext and override the SaveChanges method in there.

If you want a different form of identity (not a GUID) it should just be a case of creating a property and adding the [Identifier] attribute to it (of course, this is another B*-specific attribute). How you set that property is completely up to you, but if it isn't set before you call SaveChanges, you will get a GUID. Perhaps you could have some custom logic in there to construct a string identifier based on the composite key you want for the entity. You could actually do this in an event handler atteched to the SavingChanges event I think.

I also wonder if you could look at using OptimisticLocking combined with generating your own IDs to do much of the heavy work for you. Optimistic locking will basically add a version number to the entity and will use that to fail the transaction if the entity is concurrently modified. I think if you do this and you generate an ID based on the composite key you would get the functionality you want because if an entity is modified such that its generated ID then matches the ID of an existing entity, when you attempt to save you should get a concurrent modification error. This isn't something I have tried but I think it might be worth giving it a shot.

Hope this helps! Let me know how you get on...
Mar 18, 2014 at 2:47 PM
Interesting idea. I really don't know much about triple store etc. It's my first attempt at using a non-relational db.

That being said, I'll have to look into the locking and try to understand how it works and where to begin. If I can override the Id as you suggest then that seems reasonable for composite keys. I'm curious if the [Identifier] attribute can be added to the concrete class of if it has to be on the interface. If it must be on the interface I'll have to find a way around it.

Now i did find a way to check adds before being saved since I actually implement my own entity set and override Add. I can use the TrackedObjects to look at those entities not saved. But, this still leaves me in the dark with modifications.

I'll see what I can figure out.
Coordinator
Mar 18, 2014 at 3:02 PM
We do provide some help for getting notified of changes. Check the docs at http://brightstardb.readthedocs.org/en/latest/Entity_Framework/#inotifypropertychanged-and-inotifycollectionchanged-support. There is also some stuff in the docs about how the optimistic locking works at http://brightstardb.readthedocs.org/en/latest/Entity_Framework/#optimistic-locking.
Mar 18, 2014 at 3:32 PM
Thanks. I think I make sense of it and am in the process of testing the idea. However, my roadblock is using the custom [Identifier]. So far i still get guids. Perhaps I've setup things incorrectly.

Here's my interface and implementaion. Note that I never see get being called on the Id.
[Entity]
 public interface IApprover : IEntity
    {
        [Identifier]
        string Id { get;  }
        string EmployeeId { get;  }
        string ApproverGroupId { get; }
        string AuthLevel { get; }
    }

 public class Approver : MfrEntityObject, IApprover
    {
        public Approver(BrightstarEntityContext context, IDataObject dataObject)
            : base(context, dataObject)
        {
        }

        public Approver()
        {
        }

        public System.String Id
        {
            get
            {
                return EmployeeId + ApproverGroupId;
            }
        }

        public System.String EmployeeId
        {
            get { return GetRelatedProperty<System.String>("EmployeeId"); }
            set { SetRelatedProperty("EmployeeId", value); }
        }

        public System.String ApproverGroupId
        {
            get { return GetRelatedProperty<System.String>("ApproverGroupId"); }
            set { SetRelatedProperty("ApproverGroupId", value); }
        }

        public System.String AuthLevel
        {
            get { return GetRelatedProperty<System.String>("AuthLevel"); }
            set { SetRelatedProperty("AuthLevel", value); }
        }
Coordinator
Mar 18, 2014 at 4:28 PM
The Id property won't be invoked directly, instead in your implementation you need to call SetIdentity(string). This is the default implementation that the text template file generates:
public partial class FoafPerson : BrightstarEntityObject, IFoafPerson 
    {
        public FoafPerson(BrightstarEntityContext context, IDataObject dataObject) : base(context, dataObject) { }
        public FoafPerson() : base() { }

        public System.String Id { get {return GetIdentity(); } set { SetIdentity(value); } }
        // and other properties go here
}
Note that in the implementation these methods basically just do some translation of the identifier using the base URI and then actually get/set the Identity property of the underlying DataObject (if you get the source from GitHub you can trace this through in the BrightstarDB.EntityFramework.BrightstarEntityObject class). The value of that property has to be a valid URI (which is the mapping that GetIdentity/SetIdentity are doing). You could construct something like http://example.org/myapp/employee/{employeeid}/{approvergroupid}.

The problem will come if one of those properties are changed and you want to modify the identity. If it is a new entity that hasn't been persisted yet, you should be OK, but if it is an entity that has been loaded from the store, it is going to be backed by a DataObject and once that happens it is tied to the originial URI identifier for that DataObject. I think if that happens you best option is to clone the entity to a new one and delete the old one. Once again, none of this gets persisted until you call SaveChanges and when it goes through it will be in a single transaction, so either the whole swap will happen or it won't - you shouldn't end up in an undefined state as long as you are careful about exactly when SaveChanges gets called. BTW you can use the IsAttached property on your implementation class (inherited from BrightstarEntityObject) to check if then entity is attached to a DataObject.
Mar 18, 2014 at 4:49 PM
Ahh, right, so this is probably going to be a deal killer for this particular project. Modification of the identity, although may only occur infrequently, seems to require a lot more additional framework than it's worth for what I need to accomplish. Although I think I've managed to get pretty far in implementing additional requirement to squeeze b* into a working solution - I'm worried about the future and keeping things up to date. There's just a lot more going on than if I stick to a relational store.

I think the root of the problem is that the original datasource is relational, and not designed with RDF or any document store in mind really. As much as I want to make it work I think the complexity and scope is getting out of reach.

I will probably spend the day playing around with the above ideas just for completeness.

Thanks
Coordinator
Mar 19, 2014 at 7:51 AM
That's a shame. I think your analysis is correct, an RDF model tends to rely on the identifier for an entity remaining constant. While there are ways in OWL for expressing constraints that would behave like composite keys, its not in the core RDF model and its not something that we currently support.

Please keep an eye on the project though as I do have a task in the next milestone to add support for conditional updates that test for the non-existance of a triple - this would basically do the uniqueness constraint you require on a single property (so you could in theory create a "composite key" property separate from the ID and then specifiy that this needs to be unique on each update), that would mean you wouldn't have to deal with the ID at all (it could just remain a GUID assigned at creation time).

Thanks too for taking the time to provide feedback and for writing up what you did - it is really helpful stuff!
Mar 19, 2014 at 11:24 AM
No problem. I will be keeping a close eye on things. I think the project has some big potential as there are limited choices for embedded .net stores, especially document stores. Just right now I'm dealing with a lot of legacy SQL data in modern apps, making it difficult to use anything but relational stores.