简体   繁体   中英

Entity Framework is sometimes not loading the contents of Child Objects/Collections

I've started to have a problem where child collections in Entity Framework are not being loaded properly with Lazy Loading.

The most prominent example of this is my Orders object - each Order has one or more Order Lines associated with it (ie. a list of which products have been ordered and how many). Sometimes, when the program is run, you can open up some orders and all the order lines (for every order) will be blank. Restart the program, and they might re-appear. it's pretty intermittent.

I have confirmed that there are no entries in the child collection through logging & debugging:

    private ObservableCollection<OrderLine> LoadOrderLines()
    {
        Log.Debug("Loading {0} order lines...", this.Model.OrderLines.Count);

        var result = new ObservableCollection<OrderLine>();

        foreach (var orderLine in this.Model.OrderLines)
        {
            result.Add(orderLine);
        }

        return result;
    }

Sometimes it will say "Loading 0 order lines..." and sometimes "Loading 4 order lines..." for the same order.

I can't use Eager Loading when I load the list of orders because I don't want to load all the order lines for all the orders when only a few of them might ever be opened - I need to keep the loading as fast as possible and only load things as they are needed, hence lazy loading.

It's not only the Orders object that it is happening on, it sometimes happens on other child collections too, but the effect is exactly the same.

Anybody have any idea why EF is doing this and what I can do to fix it? It's a huge problem - I can't have empty order lines in my program when they should be there!

Extra info that may or my not be of use:

This is a WPF MVVM application. The data layer, which is shared with the website, uses a Repository/Unit of Work pattern.

In the OrdersRepository:

    public IEnumerable<Order> GetOrders(DateTime fromDate, DateTime toDate, IEnumerable<string> sources, bool? paidStatus, bool? shippedStatus, bool? cancelledStatus, bool? pendingStatus)
    {
        if (sources == null)
        {
            sources = this.context.OrderSources.Select(s => s.SourceId);
        }

        return
            this.context.Orders.Where(
                o =>
                o.OrderDate >= fromDate
                && o.OrderDate < toDate
                && sources.Contains(o.SourceId)
                && (!paidStatus.HasValue || ((o.ReceiptId != null) == paidStatus.Value))
                && (!shippedStatus.HasValue || ((o.ShippedDate != null) == shippedStatus.Value))
                && (!pendingStatus.HasValue || (o.IsPending == pendingStatus.Value))
                && (!cancelledStatus.HasValue || (o.Cancelled == cancelledStatus.Value))).OrderByDescending(
                    o => o.OrderDate);
    }

The OrdersViewModel then loads the orders, creates an orderViewModel for each one and puts them in an ObservableCollection:

var ordersList = this.unitOfWork.OrdersRepository.GetOrders(this.filter).ToList();

        foreach (var order in ordersList)
        {
            var viewModel = this.viewModelProvider.GetViewModel<OrderViewModel, Order>(order);

            this.orders.Add(viewModel);
        }

Lazy loading is for loading the related entities automatically when you acces the navigation property. But, in this case, you're not doing it automatically, but manually.

To do so, you can disable lazy loading, and use explicit loading, like this:

context.Entry(order).Collection(o => o.orderLines).Load();

(Besides, using this technique, you can apply filters).

Your problem with lazy loading can be a consequence of a long lived DbContext that caches the related entities at a given point in time, and reuses this cache later, without hitting the DB, so it's outdated. Ie one DbContext finds 3 order lines for an order and caches them. Something else, (outside the db context), adds 2 extra new order lines to this order. Then you access the order lines from the first db context and get the outdated 3 order lines, instead of the 5 that there are in the DB. There are several ways in wich you could, thoretically, reload/refresh the cached data on the DbContext, but you can get into trouble. You'd rather use the explicit loading as I suggested above. If you see the docs for Load method , you can read this:

Loads the collection of entities from the database. Note that entities that already exist in the context are not overwritten with values from the database.

However, the safest option is always to dispose the DbContext and create a new one. (Read the second sentence of the block above).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM