HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Lazy Load for multiple entities at a time

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
timelazyformultipleloadentities

Problem

We have a system with a non standard database solution. All trips to the DB are rather expensive. We cannot use entity framework.

Currently our lazy loading is on an entity by entity basis. So if I have a Customer and access their Orders object it only loads the Orders for that customer.

Something like so

//DAL
public List GetCustomersByIds(IEnumerable ids)
{
    var customers = db.GetCustomersByIds(ids);
    foreach(var customer in customers)
    {
        customer.Orders = new ResetLazy>(() => 
            db.GetOrdersByCustomerId(c.Id));
    }
    return customers;
}


ResetLazy taken from here https://stackoverflow.com/a/6255398/102526

Ideally if we had a collection of Customers and accessed an Orders collection on one of the customers it would load all the Orders for all the Customers. (If not in a collection it would just load it's own orders)

//DAL
public List GetCustomersByIds(IEnumerable ids)
{
    var customers = db.GetCustomersByIds(ids);
    foreach(var customer in customers)
    {
        customer.Orders = new ResetLazy>(() => 
            GetOrdersByCustomersLazy(customers, customer.Id));
    }
    return customers;
}

protected List GetOrdersByCustomersLazy(
    List customers, 
    int customerId)
{
    var orders = db.GetOrdersByCustomerIds(
        customers.Select(customer => customer.Id).AsEnumerable());
    foreach(var customer in customers)
    {
        customer.Orders = new ResetLazy>(() => {
            customer.Orders = new ResetLazy>(() => 
                GetOrdersByCustomersLazy(customers, customer.Id));
            return orders.Where(order => order.CustomerId == customer.Id).ToList();
        });
    }
    return customers.FirstOrDefault(customer => customer.Id == customerId).Orders.Value;
}


This seems to work well, but it is too complex and not generic.

Solution

You code seems to not doing what it is supposed to do.

Assume the following implementation (using the ResetLazy)

Customer and Order

namespace Entities
{
    public class Customer
    {
        public int Id { get; set; }
        public ResetLazy> Orders {get;set;}
    }
    public class Order
    {
        public int Id { get; set; }
        public int CustomerId { get; set; }
    }
}


Simulated database

public class TheDatabase
{
    public TheDatabase()
    {
        customers.Add(new Entities.Customer { Id = 1 });
        orders.Add(new Entities.Order { Id = 1, CustomerId = 1 });
        orders.Add(new Entities.Order { Id = 2, CustomerId = 1 });
        orders.Add(new Entities.Order { Id = 3, CustomerId = 1 });

        customers.Add(new Entities.Customer { Id = 2 });

        customers.Add(new Entities.Customer { Id = 3 });
        orders.Add(new Entities.Order { Id = 4, CustomerId = 3 });
        orders.Add(new Entities.Order { Id = 5, CustomerId = 3 });
    }
    private List customers = new List();
    private List orders = new List();
    public List GetCustomersByIds(IEnumerable ids)
    {
        return customers.Where(c => ids.Contains(c.Id)).ToList();
    }
    public IEnumerable GetOrdersByCustomerIds(IEnumerable ids)
    {
        return orders.Where(c => IsContained(c.CustomerId, ids));
    }
    private bool IsContained(int id, IEnumerable ids)
    {
        foreach (int currentId in ids)
        {
            if (id == currentId) { return true; }
        }
        return false;
    }
}


And the class containing your methods

class Program
{

    static void Main(string[] args)
    {
        Program p = new Program();
        IEnumerable ids = new List() { 1, 2, 3 };
        List customers = p.GetCustomersByIds(ids);

        int customerId = 1;
        List orders = new List(customers.Where(c => c.Id == customerId).First().Orders.Value);
        System.Diagnostics.Debug.Assert(orders[0].CustomerId == customerId);
    }

    TheDatabase db;
    private Program()
    {
        db = new TheDatabase();
    }

    public List GetCustomersByIds(IEnumerable ids)
    {
        var customers = db.GetCustomersByIds(ids);
        foreach (var customer in customers)
        {
            customer.Orders = new ResetLazy>(() =>
                GetOrdersByCustomersLazy(customers, customer.Id));
        }
        return customers;
    }

    protected List GetOrdersByCustomersLazy(
        List customers,
        int customerId)
    {
        var orders = db.GetOrdersByCustomerIds(
            customers.Select(customer => customer.Id).AsEnumerable());
        foreach (var customer in customers)
        {
            customer.Orders = new ResetLazy>(() =>
            {
                customer.Orders = new ResetLazy>(() =>
                    GetOrdersByCustomersLazy(customers, customer.Id));
                return orders.Where(order => order.CustomerId == customer.Id).ToList();
            });
        }
        return customers.FirstOrDefault(customer => customer.Id == customerId).Orders.Value;
    }
}


The Debug.Assert() method signals the wrong id.

Fixing

By adding a setter to ResetLazy.Value like

set
{

    if (mode != LazyThreadSafetyMode.None)
    {
        lock (syncLock)
        {
            this.box = new Box(value);
        }
    }
    else
    {
        this.box = new Box(value);
    }
}


and changing the GetOrdersByCustomersLazy() method to

protected List GetOrdersByCustomersLazy(
    List customers,
    int customerId)
{
    var orders = db.GetOrdersByCustomerIds(
        customers.Select(customer => customer.Id).AsEnumerable());
    foreach (var customer in customers)
    {
        customer.Orders.Value = orders.Where(o => o.CustomerId == customer.Id).ToList();
    }
    return customers.FirstOrDefault(customer => customer.Id == customerId).Orders.Value;
}


The following runs as expected

Program p = new Program();
IEnumerable ids = new List() { 1, 2, 3 };
List customers = p.GetCustomersByIds(ids);

int customerId = 1;
List orders = new List(customers.Where(c => c.Id == customerId).First().Orders.Value);
System.Diagnostics.Debug.Assert(orders.Count==3 && orders[0].CustomerId == customerId);

customerId = 2;
orders = new List(customers.Where(c => c.Id == customerId).First().Orders.Value);
System.Diagnostics.Debug.Assert(orders.Count==0);

customerId = 3;
orders = new List(customers.Where(c => c.Id == customerId).First().Orders.Value);
System.Diagnostics.Debug.Assert(orders.Count == 2 && orders[0].CustomerId == customerId);


You shouldn't shorten names of variables. The advantage of typing less is reduced by the lack of readability.

A call to the linq Select() method returns an IEnumerable so a call to AsEnumerable() is not needed.

Code Snippets

namespace Entities
{
    public class Customer
    {
        public int Id { get; set; }
        public ResetLazy<List<Entities.Order>> Orders {get;set;}
    }
    public class Order
    {
        public int Id { get; set; }
        public int CustomerId { get; set; }
    }
}
public class TheDatabase
{
    public TheDatabase()
    {
        customers.Add(new Entities.Customer { Id = 1 });
        orders.Add(new Entities.Order { Id = 1, CustomerId = 1 });
        orders.Add(new Entities.Order { Id = 2, CustomerId = 1 });
        orders.Add(new Entities.Order { Id = 3, CustomerId = 1 });

        customers.Add(new Entities.Customer { Id = 2 });

        customers.Add(new Entities.Customer { Id = 3 });
        orders.Add(new Entities.Order { Id = 4, CustomerId = 3 });
        orders.Add(new Entities.Order { Id = 5, CustomerId = 3 });
    }
    private List<Entities.Customer> customers = new List<Entities.Customer>();
    private List<Entities.Order> orders = new List<Entities.Order>();
    public List<Entities.Customer> GetCustomersByIds(IEnumerable<int> ids)
    {
        return customers.Where(c => ids.Contains(c.Id)).ToList();
    }
    public IEnumerable<Entities.Order> GetOrdersByCustomerIds(IEnumerable<int> ids)
    {
        return orders.Where(c => IsContained(c.CustomerId, ids));
    }
    private bool IsContained(int id, IEnumerable<int> ids)
    {
        foreach (int currentId in ids)
        {
            if (id == currentId) { return true; }
        }
        return false;
    }
}
class Program
{

    static void Main(string[] args)
    {
        Program p = new Program();
        IEnumerable<int> ids = new List<int>() { 1, 2, 3 };
        List<Entities.Customer> customers = p.GetCustomersByIds(ids);

        int customerId = 1;
        List<Entities.Order> orders = new List<Entities.Order>(customers.Where(c => c.Id == customerId).First().Orders.Value);
        System.Diagnostics.Debug.Assert(orders[0].CustomerId == customerId);
    }

    TheDatabase db;
    private Program()
    {
        db = new TheDatabase();
    }

    public List<Entities.Customer> GetCustomersByIds(IEnumerable<int> ids)
    {
        var customers = db.GetCustomersByIds(ids);
        foreach (var customer in customers)
        {
            customer.Orders = new ResetLazy<List<Entities.Order>>(() =>
                GetOrdersByCustomersLazy(customers, customer.Id));
        }
        return customers;
    }

    protected List<Entities.Order> GetOrdersByCustomersLazy(
        List<Entities.Customer> customers,
        int customerId)
    {
        var orders = db.GetOrdersByCustomerIds(
            customers.Select(customer => customer.Id).AsEnumerable());
        foreach (var customer in customers)
        {
            customer.Orders = new ResetLazy<List<Entities.Order>>(() =>
            {
                customer.Orders = new ResetLazy<List<Entities.Order>>(() =>
                    GetOrdersByCustomersLazy(customers, customer.Id));
                return orders.Where(order => order.CustomerId == customer.Id).ToList();
            });
        }
        return customers.FirstOrDefault(customer => customer.Id == customerId).Orders.Value;
    }
}
set
{

    if (mode != LazyThreadSafetyMode.None)
    {
        lock (syncLock)
        {
            this.box = new Box(value);
        }
    }
    else
    {
        this.box = new Box(value);
    }
}
protected List<Entities.Order> GetOrdersByCustomersLazy(
    List<Entities.Customer> customers,
    int customerId)
{
    var orders = db.GetOrdersByCustomerIds(
        customers.Select(customer => customer.Id).AsEnumerable());
    foreach (var customer in customers)
    {
        customer.Orders.Value = orders.Where(o => o.CustomerId == customer.Id).ToList();
    }
    return customers.FirstOrDefault(customer => customer.Id == customerId).Orders.Value;
}

Context

StackExchange Code Review Q#79041, answer score: 2

Revisions (0)

No revisions yet.