4

I am new to entity framework and love the simplicity but am having some trouble with speed. I think I might be using the lazy loading incorrectly but having a hard time wrapping my head around it. I have separated my data model layer and business entity layer, and use a function to create the business entity from my data model. In this function I iterate over the different nested entities to create their corresponding models. Ok, enough rambling here is some code:

IM_ITEM.cs (Product data model)

public partial class IM_ITEM
{
    public IM_ITEM()
    {
        this.IM_INV = new HashSet<IM_INV>();
        this.IM_BARCOD = new HashSet<IM_BARCOD>();
        this.IM_GRID_DIM_1 = new HashSet<IM_GRID_DIM_1>();
        this.IM_GRID_DIM_2 = new HashSet<IM_GRID_DIM_2>();
        this.IM_GRID_DIM_3 = new HashSet<IM_GRID_DIM_3>();
        this.IM_PRC = new HashSet<IM_PRC>();
    }

    public string ITEM_NO { get; set; }
    public string DESCR { get; set; }
    // many more properties...

    public virtual ICollection<IM_INV> IM_INV { get; set; }
    public virtual ICollection<IM_BARCOD> IM_BARCOD { get; set; }
    public virtual ICollection<IM_GRID_DIM_1> IM_GRID_DIM_1 { get; set; }
    public virtual ICollection<IM_GRID_DIM_2> IM_GRID_DIM_2 { get; set; }
    public virtual ICollection<IM_GRID_DIM_3> IM_GRID_DIM_3 { get; set; }
    public virtual ICollection<IM_PRC> IM_PRC { get; set; }
}

Business entity creation method:

public static ProductEntity FromEfObject(IM_ITEM obj) {
    var product = new ProductEntity {
        ItemNumber = obj.ITEM_NO,
        StyleNumber = obj.VEND_ITEM_NO,
        Title = obj.DESCR_UPR,
        LongName = obj.ADDL_DESCR_1,
        ShortDescription = obj.DESCR,
        VendorCode = obj.ITEM_VEND_NO,
        Quarter = obj.ATTR_COD_2,
        Color = obj.PROF_ALPHA_2,
        Markdown = obj.PRC_1,
        Price = obj.REG_PRC ?? 0,
        Status = obj.STAT,
        DepartmentCode = obj.ATTR_COD_1,
        DepartmentDigit = obj.ATTR_COD_1.Substring(0, 1),
        MixAndMatch = obj.MIX_MATCH_COD,
        Inventory = new Inventory(obj.IM_INV),
        Sizes = new List<ProductSize>(),
        Widths = new List<ProductSize>(),
        Lengths = new List<ProductSize>(),
        Barcodes = new Dictionary<string, string>()
    };


    if (obj.IM_PRC.Any()) {
        var price = obj.IM_PRC.First();
        product.DnsPrice2 = price.PRC_2.GetValueOrDefault();
        product.DnsPrice3 = price.PRC_3.GetValueOrDefault();
    }

    foreach (var barcode in obj.IM_BARCOD) {
        product.Barcodes.Add(barcode.DIM_1_UPR, barcode.BARCOD);
    }

    foreach (var size in obj.IM_GRID_DIM_1) {
        product.Sizes.Add(ProductSize.FromEfObject(size));
    }

    foreach (var width in obj.IM_GRID_DIM_2) {
        product.Widths.Add(ProductSize.FromEfObject(width));
    }

    foreach (var length in obj.IM_GRID_DIM_3) {
        product.Lengths.Add(ProductSize.FromEfObject(length));
    }

    if (!product.Sizes.Any()) {
        product.Sizes.Add(new ProductSize());
    }

    if (!product.Widths.Any()) {
        product.Widths.Add(new ProductSize());
    }

    if (!product.Lengths.Any()) {
        product.Lengths.Add(new ProductSize());
    }

    return product;
}

And my method to retrieve the model:

public ProductEntity GetProductById(string itemNumber, int storeNumber) {
    var product = _unitOfWork
        .GetProductRepository(storeNumber)
        .GetQueryable()
        .FirstOrDefault(p => p.ITEM_NO == itemNumber);

    return product == null ? null : ProductEntity.FromEfObject(product);
}

And the GetQueryable method:

internal DbSet<TEntity> DbSet;
public GenericRepository(TContext context)
{
    Context = context;
    DbSet = context.Set<TEntity>();
}

public virtual IQueryable<TEntity> GetQueryable()
{
    IQueryable<TEntity> query = DbSet;
    return query;
}

A little more info.. I used database first modeling to create my data model, and the database I am testing against doesn't have a ton of data. Also, I tried using .Include() in my GetProductById method to load (eagerly I believe) but the slowed it down even further.

Am I doing something fundamentally wrong? Or is using EF going to be slow for a query like this.

EDIT: To prevent lazy loading I updated my query to:

    public ProductEntity GetProductById(string itemNumber, int storeNumber) {
        var product = _unitOfWork
            .GetProductRepository(storeNumber)
            .GetQueryable()
            .Include(p => p.IM_INV.Select(i => i.IM_INV_CELL))
            .Include(p => p.IM_BARCOD)
            .Include(p => p.IM_GRID_DIM_1)
            .Include(p => p.IM_GRID_DIM_2)
            .Include(p => p.IM_GRID_DIM_3)
            .Include(p => p.IM_PRC)
            .FirstOrDefault(p => p.ITEM_NO == itemNumber);

        return product == null ? null : ProductEntity.FromEfObject(product);
    }

When tracing, this gives me just one big nasty query that takes longer than using the lazy loading http://pastebin.com/LT1vTETb

11
  • 3
    What is your implementation of _unitOfWork.GetProductRepository(storeNumber).GetQueryable() because if that part is wrong, it could be that EF queries your entire product table into memory before your FirstOrDefault is executed, and thus slowing it down. Commented Jun 28, 2016 at 14:44
  • Updated with that code.. thanks! Commented Jun 28, 2016 at 14:48
  • Is Sql Server your back end repository? If so use Sql Server Profiler to see what is actually executed on the server. If there are multiple statements (like suggested by @SynerCoder) then it could very well be a piece of code you are not showing us that would materialize the entire table before executing the FirstOrDefault. Either way use that to see what is executing. If that looks correct then start analyzing (tuning) the query, if it looks wrong start digging through the code. Commented Jun 28, 2016 at 14:51
  • Does the storeNumber affect the DbContext that is loaded, or is it part of a query? CQ, your implementation of GetQueryable is for a generic repository, but how does a product repository look. Commented Jun 28, 2016 at 14:52
  • You need to identify exactly where it's slow. Get a decent profiler (or add a bunch of Stopwatch calls) to identify where your app is spending the most time overall. Then tackle the slowest parts to get them as fast as possible. I see nothing glaringly obvious from what you're posted. Commented Jun 28, 2016 at 14:53

1 Answer 1

1

You can optimize your query to avoid lazy loading. In this case, when you load an object from the database, you know you're going to have to map almost the entire object graph to memory. You're going to need to look up all of those foreign keys later anyway - it's faster to do it all as one query rather than letting lazy loading do the work. See here for more information.

It should look something like this:

public ProductEntity GetProductById(string itemNumber, int storeNumber) {
    var product = _unitOfWork
        .GetProductRepository(storeNumber)
        .GetQueryable()
        .Include(p => p.IM_BARCOD)
        .Include(p => p.IM_GRID_DIM_1)
        .Include(p => p.IM_GRID_DIM_2)
        .Include(p => p.IM_GRID_DIM_3)
        .Include(p => p.IM_PRC)
        .FirstOrDefault(p => p.ITEM_NO == itemNumber);

    return product == null ? null : ProductEntity.FromEfObject(product);
}

Note that if these foreign keys have their own foreign keys (i.e. IM_BARCOD has a collection of IM_OtherType) which you also need to map to your ProductEntity model, you should also include those. You can do it in line like this:

.Include(p => p.IM_BARCOD.Select(b => b.IM_OTHERTYPE))
Sign up to request clarification or add additional context in comments.

4 Comments

In this case I would want to do .Where(p => p.ITEM_NO == itemNumber) before the includes, correct? I tested that and it went from ~8 seconds to about 20 seconds so I went back to debugging without the includes
No, you don't need the Where before. With EF, all of this code gets translated into a single SQL query - it's not actually executed in C#. The order of the statements doesn't matter since it turns into the same SQL query at the end.
Ok that's good to know.. I knew it compiled to one query but didn't know it was smart enough to order things.. Sort of side question but if I had several Where statements does it optimize the order for me?
Having multiple where statements is semantically the same as having multiple conditions with && in a single where. EF may not optimize this itself, but the Query Optimizer of your DBMS most likely will (MS SQL Server does, for example)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.