13

Our company is developing a new application, which has a somewhat large business data object at its core. We decided to try out Entity Framework with code first to abstract the database from the application, but things have gone awry. The business object is composed of approximately 60 classes, and in total around 600 properties; however, it is a tree structure and no crossover/backtracking pointers are present.

Our test was to add a single, uninitialized instance of the class to the database. Using DbContext.Add on our data structure took 8 minutes on my development machine. Is this the expected performance of an object this size? Is there a list of common problems that causes poor performance with Entity Framework? I feel I need some help with this.

Some more data points: There are 27 elements in the first level under the root of the business object. With 3 elements present (the rest commented out), the time to add is 4.5 seconds. With 5 elements present, it is 11.8 seconds. With 8 elements present, it is 1 minute 12.5 seconds. Obviously, the size of these elements varies significantly, but these does seem to indicate a systematic problem of some sort.

11
  • 3
    Have you looked at the SQL its generating in Sql Profiler? Sometimes a lot can be gleaned about what EF is trying to do by looking at the sql. In my opinion, saving 60 related classes to the db should be a sub 1-second operation for EF. Commented Aug 8, 2011 at 23:05
  • We hadn't tried that, because we knew it was only hitting the database at the end and all of the processing was taking place inside EF. The trace is very large... I'm not quite sure what I should be looking for. Commented Aug 8, 2011 at 23:41
  • 1
    Try setting dbContext.Configuration.AutoDetectChangesEnabled = false; when you create the context and measure again. Since you say Add is slow (and not SaveChanges, right?) I suspect that automatic change detection/snapshot creation makes things slow. Commented Aug 8, 2011 at 23:41
  • 1
    Yes, I did try that. These results are with AutoDetectChangesEnabled set to false. And yes, the Add was the slow portion, and SaveChanges finishes relatively quickly. Commented Aug 8, 2011 at 23:46
  • Oh, interesting! 8 minutes without change detection is crazy. I have no idea if this is normal (I hope it isn't), never tested with such a large object. I'm curious about answers to your question. Commented Aug 8, 2011 at 23:56

2 Answers 2

4

...Our test was to add a single, uninitialized instance of the class to the database. Using DbContext.Add...

Did you make sure that your Code-First model is created and loaded into memory before you've called Add? I mean the following: If you use a test code like this ...

using (var context = new MyContext())
{
    var myHugeBusinessObject = CreateItSomeHow();

    context.HugeBusinessObjects.Add(myHugeBusinessObject);

    context.SaveChanges();
}

... and it's the first time that you are using the context in your test application Add will actually spent some time to build the EF model in memory before it starts to add the object to the context.

You can separate these two steps simply by adding a dummy method before calling Add, for instance something like:

context.HugeBusinessObjects.Count();

I've built a test:

public class MyClass
{
    public int Id { get; set; }
    public string P1 { get; set; }
    // ... P2 to P49
    public string P50 { get; set; }
    public MyClass Child1 { get; set; }
    // ... Child1 to Child26
    public MyClass Child27 { get; set; }
}

With this I created an object:

var my = new MyClass();
MyClass child = my;
for (int i = 0; i < 100; i++)
{
    child.Child1 = new MyClass();
    child = child.Child1;
}
child = my;
for (int i = 0; i < 100; i++)
{
    child.Child2 = new MyClass();
    child = child.Child2;
}
// and so on up to Child27

So this object graph has 2700 child objects with 50 scalar properties each. Then I tested this code:

using (var context = new MyContext())
{
    var my = CreateWithTheCodeAbove();

    context.MyClassSet.Count();
    context.MyClassSet.Add(my);

    context.SaveChanges();
}

...Count() (= building the EF model) needs roughly 25 seconds. Add needs 1 second. (Changing 100 to 1000 in the loops above (having then 27000 objects in the graph) increases the time for Add almost linearly to 9-10 seconds.) This result is independent of setting AutoDetectChangesEnabled to true or false.)

The next interesting result: If I add 20 navigation properties more (Child28 to Child47) to MyClass the time spent with building the model (Count() in the example code) explodes to 140 seconds. The duration of Add only increases linearly with the additional properties.

So, my hypothesis is: You are not actually measuring the time to add your business object to the context but the time EF needs to build the EF model in memory. The time to build the model seems to grow exponentially with the complexity of the model: Number of navigation properties on a class and perhaps also the number of different involved classes.

To separate these steps test with some dummy call like suggested above. If you already have this separation... omg, forget this post.

Sign up to request clarification or add additional context in comments.

4 Comments

Your suggestion actually improved the performance a lot! The time required went down one of our other developer's machines from 5 minutes (the time from my computer was 8 minutes, as in my initial post) to 2.5 minutes, which is a huge difference! Unfortunately it's still not production-ready... For the time being we are starting to look at NHibernate, although still trying to keep EF in mind. I understand what you said about the lag actually coming from initializing the internal structure for EF, but I don't see why separating it as you said should make such a huge difference.
I'll try to make a simplified model of our code that can be used for testing (as Ladislav suggested above).
@dythim: Do you mean that the total time (Count() + Add()) went down to a half of the time when calling only Add() alone? If yes then that is something which I also wouldn't expect. I had expected that the time is the same but unevenly distributed: Count() (= model creation) is much slower than Add(). Did you check the time of each of the calls and not only the sum? The internal model structure is only created once per application instance. If the model creation is the performance problem your question turns to another one (how to improve model initialization performance).
Yes, the total time seemed to have gone down. It is possible that something in our model is causing this odd behavior. It turned out to not be related to inheritance (as another site indicated was a possibility). We're still running experiments to try to determine what is wrong, or if we should move to NHibernate.
3

So, figured out the problem. While proceeding with NHibernate, we came across a structural error which was some experimental code. The category was subscribing to the PropertyChanged events of its children, which caused NHibernate to crash.

Which is GOOD! That told us there was actually a problem. Entity Framework just ran forever without any indication that it was a problem we could do something about.

So anyway, we are going to continue to use NHibernate for the time being. We like the control we have over the database structure we get by using it.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.