EF - Navigation property is null, even after reloading entity, but works when program is restarted
Asked Answered
B

1

5

EDIT

I've done some testing and found out, that the Item navigation property only works when the context is disposed/a new context is created.

        DataContext context = new DataContext();
        Order ord = context.Orders.FirstOrDefault();
        ord.OrderItem.Add(new OrderItem() { ItemId = 8, Quantity = 2, DateCreated = DateTime.Now });
        // At this point, both Order and Item navigation property of the OrderItem are null

        context.SaveChanges();
        // After saving the changes, the Order navigation property of the OrderItem is no longer null, but points to the order. However, the Item navigation property is still null.

        ord = context.Orders.FirstOrDefault();
        // After retrieving the order from the context once again, Item is still null.

        context.Dispose();
        context = new DataContext();
        ord = context.Orders.FirstOrDefault();
        // After disposing of the context and creating a new one, then reloading the order, both Order and Item navigation props are not null anymore

Can someone explain this to me?

EDIT END

In my program, the user has a list of orders, to which he can add new orders. The user can also add order items to the order, but this isn't working properly atm since the OrderItem -> Item navigation property is null, even after saving and reloading the order.

OrderItem
---------
OrderId
ItemId
Quantity
DateCreated
---------
Item <- navigation property
Order

When the user has made changes to an order and presses the save button, the program gets the data from the view, updates the activeOrder and sends it to the orderModel, adding it as a new order or updating an existing one.

GetOrderDataFromView() ..

        ...
        // Check for existing item, update if match found, add new item if not
        foreach (ItemViewObject oi in orderItems)
        {
            var existingOrderItem = activeOrder.OrderItem.Where(o => o.ItemId == oi.ItemId).SingleOrDefault();

            if (existingOrderItem != null)
            {
                existingOrderItem.Quantity = oi.Quantity;
            }
            else
            {
                activeOrder.OrderItem.Add(new OrderItem()
                {
                    ItemId = oi.ItemId,
                    Quantity = oi.Quantity,
                    DateCreated = DateTime.Now
                });
            }

Then, in the orderModel class ...

    public void AddOrUpdate(Order order)
    {
        if (order.Id == 0)
        {
            context.Orders.Add(order);
        }

        context.SaveChanges();
    }

Then the order table is updated, triggering an event, which fires the OrderSelectionChanged() method. At this point, the order is reloaded by retrieving it from the orderModel (return context.Orders.Where...) ...

        // Get values from selected order and populate controls
        if (view.OrderTable.SelectedRows.Count != 0)
        {
            OrderViewObject ovm = (OrderViewObject)view.OrderTable.SelectedRows[0].DataBoundItem;
            activeOrder = orderModel.GetById(ovm.OrderId);

            PopulateOrderItemTableControl();

When the PopulateOrderItemTableControl() method is called, I start running into trouble ..

        foreach (OrderItem oi in activeOrder.OrderItem)
        {
            orderItems.Add(new ItemViewObject(oi));
        }

Because when creating a new ItemViewObject, I need to get the item of the orderItem, through it's navigation property. However, the orderItem.Item navigation property is null and I get an exception here ...

    public ItemViewObject(OrderItem orderItem)
    {
        dateCreated = orderItem.DateCreated;

        // Retrieve latest item details, with effective date older or equal to the creation date of the order item
        var details = orderItem.Item.ItemDetails.Where(i => i.DateEffective  <= dateCreated)
                                                        .OrderByDescending(i => i.DateEffective)
                                                        .FirstOrDefault();

I then have to restart the program, but after doing so, the order items load perfectly fine. So this only happens when adding a new order item to an order, saving the order, then reloading the order and trying to display its order items.

Also, this only happens with new items that have not been previously added to the order. So if I create a new item and add it to an order, click the save button, I will get a null exception and need to restart. After restarting, the item loads fine. If I then delete the order item, save the order, re-add the item and save it again, it does not crash.

Hope this makes sense to someone.

Cheers!

Note: I used model first.

Brede answered 26/2, 2014 at 12:58 Comment(2)
out of curiosity, if you load the entity by key both times, do you get the same behavior?Rosio
Yes, it made no difference.Brede
S
20

The behaviour is actually expected and has explanations:

DataContext context = new DataContext();
Order ord = context.Orders.FirstOrDefault();
ord.OrderItem.Add(new OrderItem() {
    ItemId = 8, Quantity = 2, DateCreated = DateTime.Now });
// At this point, both Order and Item navigation property of the OrderItem
// are null
// Explanation: That's clear because you don't set Order and Item property

context.SaveChanges();
// After saving the changes, the Order navigation property of the OrderItem
// is no longer null, but points to the order. However, the Item navigation
// property is still null.
// Explanation: SaveChanges internally fixes relationships with objects that are
// attached to the context. Order is attached, Item is not. That's why only
// the Order property is set

ord = context.Orders.FirstOrDefault();
// After retrieving the order from the context once again, Item is still null.
// Explanation: Because the Order is already attached a new query won't replace
// it. The entity remains the same as before.

context.Dispose();
context = new DataContext();
ord = context.Orders.FirstOrDefault();
// After disposing of the context and creating a new one, then reloading the
// order, both Order and Item navigation props are not null anymore
// Explanation: In a new context the Order will be loaded as a proxy. So, now
// lazy loading will work and load the Item property

The key problem here is that you create the new OrderItem with the new operator:

ord.OrderItem.Add(new OrderItem() {
    ItemId = 8, Quantity = 2, DateCreated = DateTime.Now });

That means that it isn't a dynamic proxy that is able to load navigation properties by lazy loading. In order to create a proxy manually you should use instead:

var newOrderItem = context.OrderItems.Create();
newOrderItem.ItemId = 8;
newOrderItem.Quantity = 2;
newOrderItem.DateCreated = DateTime.Now;

ord.OrderItem.Add(newOrderItem);

After context.SaveChanges() you should now be able to load the Item property via lazy loading (based on the foreign key value ItemId). "Reloading" the order with context.Orders.FirstOrDefault() isn't necessary and using a new context isn't necessary either.

Salaam answered 26/2, 2014 at 18:8 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.