New Posts New Posts RSS Feed: Table Splitting
  FAQ FAQ  Forum Search   Calendar   Register Register  Login Login

Table Splitting

 Post Reply Post Reply
Author
stephenmcd1 View Drop Down
DevForce MVP
DevForce MVP


Joined: 27-Oct-2009
Location: Los Angeles, CA
Posts: 166
Post Options Post Options   Quote stephenmcd1 Quote  Post ReplyReply Direct Link To This Post Topic: Table Splitting
    Posted: 06-Jun-2012 at 3:45pm
Does DevForce support setting up Table Splitting in our Entity Framework model?  Most things seem to work find with Table Splitting, but I've noticed a few odd behaviors that I think are bugs.

1. Referential Constraint seems to be reversed.

I followed the example of implementing table splitting on this page (linked to from this DRC page) which has you setup the Referential Contraint with the 'Principal' entity being the 'parent' and the 'Dependent' one being the child.  For example, I took a Dev Force sample project and split up the Customer entity.  I moved the address-related fields (Address, City, Region, PostalCode and Country) to a seperate CustomerAddress entity.  It looks like this in the designer:



To me, it seems like I should have the Customer entity be the principal and the CustomerAddress be the dependent.  For example, this is how I expected to setup the Referential Constraint:


But doing it that way seems to confuse DevForce.  For example, with the configuration above, the following code will break:
var em = new NorthwindIBEntities();
        
//Make a customer
var customer = em.CreateEntity<Customer>();
customer.EntityAspect.AddToManager();

//Generate a new Guid as the Id
customer.CustomerID = Guid.NewGuid();

//Sanity check
DebugFns.Assert(customer.CustomerID != Guid.Empty);

//Make an address
var customerAddress = em.CreateEntity<CustomerAddress>();
customerAddress.EntityAspect.AddToManager();

//Sanity check - the ID is still set
DebugFns.Assert(customer.CustomerID != Guid.Empty);
        
//Assign the address
customer.CustomerAddress = customerAddress;

//Fails!  The 'blank' Id from the CustomerAddress entity seems to have overwritten the 'real' ID on the Customer
DebugFns.Assert(customer.CustomerID != Guid.Empty);
I would have expected the Id that I set on the Customer to 'transfer' over to the CustomerAddress entity when I assigned the navigation property (which is the behavior I see in all other cases with DevForce which makes me think its a bug).

I can get around that problem by setting the CustomerID on the customerAddress entity before I assign the Navigation Property but that gets annoying (especially since in my real app, the code isn't nearly as simple as this....there are multipart keys and logic is spread all around).

Anyway, so I added some hacks to do manual copying of PK values before I assign the Navigation Property but then I ran into problems with deletes.  This code also seems like it should work but doesn't:
//Make a customer
var customer = em.CreateEntity<Customer>();
customer.EntityAspect.AddToManager();

//Generate a new Guid as the Id
customer.CustomerID = Guid.NewGuid();

//Sanity check
DebugFns.Assert(customer.CustomerID != Guid.Empty);

//Make an address
var customerAddress = em.CreateEntity<CustomerAddress>();
customerAddress.EntityAspect.AddToManager();

//Sanity check - the ID is still set
DebugFns.Assert(customer.CustomerID != Guid.Empty);

//HACK: Copy over the ID manually
customerAddress.CustomerID = customer.CustomerID;
        
//Assign the address
customer.CustomerAddress = customerAddress;

//Passes now
DebugFns.Assert(customer.CustomerID != Guid.Empty);

//Delete the customer
customer.EntityAspect.Delete();

//Both entities should now be detached (since they never got saved, they become detached instead of 'Deleted')
DebugFns.Assert(customer.EntityAspect.EntityState == EntityState.Detached);
        
//Fails: The CustomerAddress is still in an Added state
DebugFns.Assert(customerAddress.EntityAspect.EntityState == EntityState.Detached);
After running into those two problems, I thought that maybe DevForce just doesn't support table splitting.  But then I saw that your documentation mentions it so I took a second look.  I noticed that if I switch around the order of the Referential Constraint so that CustomerAddress is the principal and Customer is the dependent one, all the test code above works as expected (no hacks necessary)!

I started down the road of just reversing the principal/dependent entities hoping that would solve all my problems - even though it felt very wrong and unintuitive....but then I ran into problems because of that.  Now that Customer wasn't the principal, DevForce won't let me generate a temporary ID for it.  For example, if I try to do:
manager.GenerateId(myCustomer, Customer.PropertyMetadata.CustomerID)
it will give me an error saying:
Cannot call GenerateId on 'Customer.CustomerID'. GenerateId cannot be called on ForeignKey properties ( even if they are also part of a PrimaryKey).  Call GenerateId instead on the 'source' primary key
 So instead, I'd be forced to call GenerateID on the CustomerAddress.CustomerID property....but that is awkward and sometimes impossible (part of the reason I want to do table splitting is so that the client doesn't always need to have the CustomerAddress entity after all!)

#2 Setting Navigation Properties on a Deleted Entity doesn't fixup the IDs

This one confused me for a while but in the end, it's very easy to reproduce.  I'll build on the scenario given above with Customer and CustomerAddress.  For this example, I'll have the Referential Constraint "reversed" so that setting Navigation Properties works.  The difference in this case is that I delete the parent entity before assigning the navigation property.
//Make a customer
var customer = em.CreateEntity<Customer>();
customer.EntityAspect.AddToManager();

//Generate a new Guid as the Id
customer.CustomerID = Guid.NewGuid();

//Sanity check
DebugFns.Assert(customer.CustomerID != Guid.Empty);

//Make an address
var customerAddress = em.CreateEntity<CustomerAddress>();
customerAddress.EntityAspect.AddToManager();

//Need to AcceptChanges before doing the delete otherwise the entity will just get detached
customer.EntityAspect.AcceptChanges();

//Delete the customer.
customer.EntityAspect.Delete();

//The customer still has its ID even though it is deleted
DebugFns.Assert(customer.CustomerID != Guid.Empty);

//Assign the address
customer.CustomerAddress = customerAddress;

//Since I reversed the Referential Constraint, the Customer.CustomerID property didn't get cleared
DebugFns.Assert(customer.CustomerID != Guid.Empty);

//Fails!  The CustomerAddress.CustomerID is still empty....even though it got assigned to the navigation property!
DebugFns.Assert(customerAddress.CustomerID != Guid.Empty);
If I comment out the line that deletes the customer, this all works fine.  Also, if I try it on other entities that aren't table-split ones, it all works fine (for example, I tested adding an Order to a deleted Customer and the Order.CustomerID property still got updated correctly).

Hopefully that is all clear.  Thanks!
Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 07-Jun-2012 at 7:08pm
Hi Stephen,
 
I verified the first issue you stated and can confirm this is a bug. I'm filing a bug report on this regard.
 
Regarding the second issue, i think this is just an effect of you calling Delete on the "Dependent" entity (i.e. in the Association). If you call Delete on CustomerAddress, (the "Principal" entity in the given situation) both entities will be Detached.
 
As for the last issue. It also appear to be a bug and I'll be reporting it as well.
 
Regards,
   Silvio.
Back to Top
stephenmcd1 View Drop Down
DevForce MVP
DevForce MVP


Joined: 27-Oct-2009
Location: Los Angeles, CA
Posts: 166
Post Options Post Options   Quote stephenmcd1 Quote  Post ReplyReply Direct Link To This Post Posted: 12-Jun-2012 at 1:52pm

I'm glad those are bugs.  Thanks.

For the delete, I may have given a confusing example.  I do expect that deleting the principal entity would cascade to the dependent.  Whereas, deleting the dependent shouldn't necessarily cascade to the principal.  In the code that I showed where deleting Customer didn't cascade to CustomerAddress, that was early on in my efforts when Customer was still the principal.  Hopefully that is the behavior that you see (that the delete actually only cascades from dependent to principal) and hopefully you agree that one is a bug as well.  Hopefully I explained it better this time.

Thanks again!

Back to Top
stephenmcd1 View Drop Down
DevForce MVP
DevForce MVP


Joined: 27-Oct-2009
Location: Los Angeles, CA
Posts: 166
Post Options Post Options   Quote stephenmcd1 Quote  Post ReplyReply Direct Link To This Post Posted: 27-Nov-2012 at 3:14pm
Have any of these bugs been fixed yet?  We recently upgraded to 6.1.9.0 but the problems still seem to be there.
Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 29-Nov-2012 at 8:39am
Hi Stephen,

Unfortunatelly, these bugs have not been fixed yet.

I am bringing them up again to our engineers and will try to push them up in the priority list.

sbelini.
Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 03-Dec-2012 at 5:11pm
Stephen,

The first issue (Referential Constraint seems to be reversed) has been fixed and the fix will be in DevForce v6.1.10.0.

As for the second issue, could you provide a use case where setting Navigation Properties on a Deleted Entity would be necessary. (i.e. necessary in the sense that you'd need to manipulate Deleted entities)

Regards,
   sbelini.
Back to Top
stephenmcd1 View Drop Down
DevForce MVP
DevForce MVP


Joined: 27-Oct-2009
Location: Los Angeles, CA
Posts: 166
Post Options Post Options   Quote stephenmcd1 Quote  Post ReplyReply Direct Link To This Post Posted: 04-Dec-2012 at 10:46am
The big use case where I want to be able to use navigation properties for deleted items is when I'm deleting entities that are using table splitting (as in this case).  I'll use the example I gave in the first post - a Custom entity that is the 'main' entity and then CustomAddress that is basically optional data that I don't usually want to bring down to the client.  If I do simple code like this, it won't work:
entityManager.Customers.Single(c => c.CustomID == someId).EntitAspect.Delete();
entityManager.SaveChanges();
EF will complain that "Invalid data encountered. A required relationship is missing".  It seems EF needs the Customer and CustomerAddress entities to be be loaded and both marked as deleted.  I already have a bunch of pre-save logic in a custom EntityServerSaveInterceptor so I use that place to automatically fix this problem.  It goes something like this:
//Loop through any deleted Customer entities to fix them up
foreach(var customer in EntityManager.FindEntities<Customer>(EntityState.Deleted)
{
   //If the CustomerAddress wasn't loaded, we need to make a placeholder one to make EF happy
   if(customer.CustomerAddress == null)
   {
       //Make the placeholder and assign it to the navigation property.
       customer.CustomerAddress = EntityManager.CreateEntity<CustomerAddress>()

       //Before deleting this entity, we need to accept the changes...otherwise it will become detached
       customer.CustomerAddres.EntityAspect.AcceptChanges();
       
       //Now delete it
       customer.CustomerAddress.EntityAspect.Delete();
    }
}
In principle, that code should work but the navigation issue with deleted entities ends up causing problems.  For example, when I go to check if the CustomerAddress navigation property is filled in, it will never show up....even when the CustomerAddress entity is in the entity manager.  So I have to search the entity manager cache manually which isn't terribly hard but is awkward (and makes it hard to do this kind of logic generically).  Then also, when I assign the placeholder CustomerAddress to the navigation property, the CustomerAddress doesn't get its Primary Key fields updated with those on the customer - so then EF still complains because all it sees is a CustomerAddress with Primary Key of zero - which doesn't match the Customer.

There are other places in our code where it would be nice for navigation properties to work even for deleted entities.  There are other entities that do logic as they are about to be saved in the EntityServerSaveInterceptor and it is annoying that we can't just 'dot' around the properties to see if other things are loaded.
Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 04-Dec-2012 at 5:05pm
Hi Stephen
 
In DevForce 2010 v.6.1.10.0 you should be able to do:
 
 customer.ContactAddress.EntityAspect.Delete()
 
in your SaveInterceptor pre-save logic.
 
sbelini.
Back to Top
stephenmcd1 View Drop Down
DevForce MVP
DevForce MVP


Joined: 27-Oct-2009
Location: Los Angeles, CA
Posts: 166
Post Options Post Options   Quote stephenmcd1 Quote  Post ReplyReply Direct Link To This Post Posted: 05-Dec-2012 at 11:19am
The delete line is not what is causing problems.  It's the navigation properties.  Below is some code that maybe explains it better than I can in English.

Note: In my case, I have the principle/dependent roles swapped I think - because of the other issues in this thread.  But I don't think that changes the fact that there are problems with navigation properties.
       [TestMethod]
        public void TestDeletedEntityNavigations()
        {
            var em = new EntityManager();

            //Make the Customer and CustomerAddress - hard-coding the same key
            var customer = new Customer {CustomerID = 123};
            var customerAddress = new CustomerAddress {CustomerID = 123};

            //Add both entities to the entity manager
            em.AttachEntities(new Entity[] {customer, customerAddress});

            //Since they have the same PK, DevForce automatically updated the navigation properties so
            //  these two tests will pass.
            Assert.AreEqual(customer, customerAddress.Customer, "customerAddress.Customer is wrong");
            Assert.AreEqual(customerAddress, customer.CustomerAddress, "customer.CustomerAddress is wrong");

            //Now delete the two entities (in practice, we only need to delete the customer and that will
            //  cascade to the CustomerAddress as well - but we'll be explicit here for demonstration purposes)
            customer.EntityAspect.Delete();
            customerAddress.EntityAspect.Delete();

            //I'd still expect (and want) the navigation properties to still work.  This first check will work...
            Assert.AreEqual(customer, customerAddress.Customer, "customerAddress.Customer is wrong");

            //But then this one will fail.  customer.CustomerAddress is pointing to a NullEntity
            Assert.AreEqual(customerAddress, customer.CustomerAddress, "customer.CustomerAddress is wrong");
        }
Back to Top
sbelini View Drop Down
IdeaBlade
IdeaBlade
Avatar

Joined: 13-Aug-2010
Location: Oakland
Posts: 786
Post Options Post Options   Quote sbelini Quote  Post ReplyReply Direct Link To This Post Posted: 05-Dec-2012 at 12:05pm
Stephen,

Sorry if my response wasn't clear.
What I meant to say is that you don't need to create a dummy entity and delete it to satisfy EF. The check

customer.CustomerAddress == null

isn't necessary either.

You should be able to simply call

customer.ContactAddress.EntityAspect.Delete()

i.e. assuming you did not set LoadStrategy to DoNotLoad. Otherwise, you'd have to query the for the ContactAddress first.

sbelini.
Back to Top
stephenmcd1 View Drop Down
DevForce MVP
DevForce MVP


Joined: 27-Oct-2009
Location: Los Angeles, CA
Posts: 166
Post Options Post Options   Quote stephenmcd1 Quote  Post ReplyReply Direct Link To This Post Posted: 04-Jan-2013 at 11:09am

Sorry for the delay.  Been sidetracked by other projects.

In our case, we do have the LoadStrategy set to DoNotLoad.  Perhaps that's why we couldn't go the easy way.  Although, the reason that I'm using table splitting in this case is because the 'other half' of this entity is a large binary field - which could be many megabytes if not larger.  I'd really rather not have to query for it just to delete it.  

Having to query for the 'other half' of the item in order to delete it isn't really the end of the world.  However, I was mainly giving that example in response to your request of "As for the second issue, could you provide a use case where setting Navigation Properties on a Deleted Entity would be necessary. (i.e. necessary in the sense that you'd need to manipulate Deleted entities)".  This case was one place where we'd like to be able to use navigation properties on deleted items.  Sure, if we weren't using DoNotLoad LoadStrategy (which we can't really change at this point), it would get around this case - but it seems like a valid way to code.  Then the code I gave in the subsequent post was an even more basic case where I'd want/expect navigation properties to work for deleted items.

Hope that makes sense.


Back to Top
 Post Reply Post Reply

Forum Jump Forum Permissions View Drop Down