Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How should I disable Entity Framework table reference(foreign) list from each objects?

I'm using Sqlite database and System.Data.SQLite 1.0.92 There is 2 table here:


Table Person:

PersonId

PersonName


Table Student:

StudentId

PersonId(reference table Person FK)

StudentNo


Now every time I get the Persons Collection in EF5:

using (var ctx = new myEntities)
{
  AllPersons = ctx.Persons.ToList();
}

There is also has AllPersons.student collection will include in the result;

But I don't need it. Of course that's just an example, There is a lot of big table has so many references, it always has performance problems here because of that.

So I'm trying to do not let it in my result. So I change it:

using (var ctx = new myEntities)
{
      ctx.Configuration.ProxyCreationEnabled = false;
      ctx.Configuration.LazyLoadingEnabled = false;
      AllPersons= ctx.Persons.ToList();
}

Now fine, because AllPersons.student collection will always be null

But now I found: If I get Person and Student together:

using (var ctx = new myEntities)
{
    ctx.Configuration.ProxyCreationEnabled = false;
    ctx.Configuration.LazyLoadingEnabled = false;
    AllPersons= ctx.Persons.ToList();
    AllStudents = ctx.Student.ToList();
}

Now the reference still include in.

So Is there anyway to don't let the reference include in any time in this situation? Thank you.


Update

For some friends request, I explain why I need it:

1: When I convert it to json it will be a dead loop. even I already use Json.net ReferenceLoopHandling, the json size very big to crash the server.(if no references, it's just a very small json)

2:Every time I get the client data and need to save, it will display exception about model state, until I set it to null.

Example:

using (myEntities ctx = new myEntities())
 {
 ctx.Configuration.LazyLoadingEnabled = false;
 ctx.Configuration.ProxyCreationEnabled = false;



  Person model= ThisIsAModel();

  model.students = null;  // This is a key, I need set the students collection references to null , otherwise it will throw exception

  ctx.Entry(model).State = EntityState.Modified;
  ctx.SaveChanges();

}

3: This is More important problem. I already get all data and cache on the server. But It will let the loading time very long when server start. (because the data and references are so many, that is the main problem), I don't know I'll meet what kind of problem again....

public List<Person> PersonsCache; // global cache
public List<Student> StudentsCache; // global cache
using (myEntities ctx = new myEntities())
 {
     ctx.Configuration.LazyLoadingEnabled = false;
     ctx.Configuration.ProxyCreationEnabled = false;
 // There is so many references and data, will let it very slow , when I first time get the all cache. even I only get the Person model, not other , just because some Collection has some references problem. It will very slow....

   PersonsCache = ctx.Persons.ToList();
   StudentsCache= ctx.Student.ToList();
}
like image 254
qakmak Avatar asked Jun 29 '15 04:06

qakmak


3 Answers

The Problem

As you said, when you load both of Parent and Child lists even when LazyLoading is disabled, and then look in parent.Childs you see child items has been loaded too.

var db = new YourDbContext();
db.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();
var childList= db.YourChildSet.ToList();

What happened? Why childs are included in a parent?

The childs under a parent entity, are those you loaded using db.YourChildSet.ToList(); Exactly themselves; In fact Entity Framework never loads childs for a parent again but because of relation between parent and child in edmx, they are listed there.


Is that affect Perforemance?

According to the fact that childs only load once, It has no impact on perforemance because of loading data.


But for serialization or something else's sake, How can I get rid of it?

you can use these solutions:

Solution 1:

Use 2 different instance of YourDbContext:

var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet.ToList();

var db2 = new YourDbContext();
db2.Configuration.LazyLoadingEnabled = false;
var childList= db.YourChildSet.ToList();
  • Now when you look in parent.Childs there is no Child in it.

Solution 2:

use Projection and shape your output to your will and use them.

var db1 = new YourDbContext();
db1.Configuration.LazyLoadingEnabled = false;
var parentList= db.YourParentSet
                  .Select(x=>new /*Model()*/{
                      Property1=x.Property1,
                      Property2=x.Property2, ...
                  }).ToList();
  • This way when serialization there is nothing annoying there.
  • Using a custom Model class is optional and in some cases is recommended.

Additional Resources

As a developer who use Entity Framework reading these resources is strongly recommended:

  • Performance Considerations for Entity Framework 4, 5, and 6
  • Connection Management
like image 159
Reza Aghaei Avatar answered Nov 13 '22 00:11

Reza Aghaei


I'll focus on your third problem because that seems to be your most urgent problem. Then I'll try to give some hints on the other two problems.

There are two Entity Framework features you should be aware of:

  1. When you load data into a context, Entity Framework will try to connect the objects wherever they're associated. This is called relationship fixup. You can't stop EF from doing that. So if you load Persons and Students separately, a Person's Students collection will contain students, even though you didn't Include() them.

  2. By default, a context caches all data it fetches from the database. Moreover, it stores meta data about the objects in its change tracker: copies of their individual properties and all associations. So by loading many objects the internal cache grows, but also the size of the meta data. And the ever-running relationship fixup process gets slower and slower (although it may help to postpone it by turning off automatic change detection). All in all, the context gets bloated and slow like a flabby rhino.

I understand you want to cache data in separate collections for each entity. Two simple modifications will make this much quicker:

  • Evade the inevitable relationship fixup by loading each collection by a separate context
  • Stop caching (in the context) and change tracking by getting the data with AsNoTracking.

Doing this, your code will look like this:

public List<Person> PersonsCache;
public List<Student> StudentsCache;

using (myEntities ctx = new myEntities())
{
     ctx.Configuration.ProxyCreationEnabled = false;
     PersonsCache = ctx.Persons
                       .AsNoTracking()
                       .ToList();
}

using (myEntities ctx = new myEntities())
{
     ctx.Configuration.ProxyCreationEnabled = false;
     StudentsCache= ctx.Student
                       .AsNoTracking()
                       .ToList();
}

The reason for turning off ProxyCreationEnabled is that you'll get light objects and that you'll never inadvertently trigger lazy loading afterwards (throwing an exception that the context is no longer available).

Now you'll have cached objects that are not inter-related and that get fetched as fast as it gets with EF. If this isn't fast enough you'll have to resort to other tools, like Dapper.

By the way, your very first code snippet and problem description...

using (var ctx = new myEntities)
{
  AllPersons = ctx.Persons.ToList();
}

There is also has AllPersons.student collection will include in the result;

...suggest that Entity Framework spontaneously performs eager loading (of students) without you Include-ing them. I have to assume that your code snippet is not complete. EF never, ever automatically executes eager loading. (Unless, maybe, you have some outlandish and buggy query provider).

As for the first problem, the serialization. You should be able to tackle that in a similar way as shown above. Just load the data you want to serialize in isolation and disable proxy creation. Or, as suggested by others, serialize view models or anonymous types exactly containing what you need there.

As for the second problem, the validation exception. I can only imagine this to happen if you initialize a students collection by default, empty, Student objects. These are bound to be invalid. If this is not the case, I suggest you ask a new question about this specific problem, showing ample detail about the involved classes and mappings. That shouldn't be dealt with in this question.

like image 2
Gert Arnold Avatar answered Nov 12 '22 22:11

Gert Arnold


Explicitly select what you want to return from the Database.

Use Select new. With the select new clause, you can create new objects of an anonymous type as the result of a query and don't let the reference include in. This syntax allows you to construct anonymous data structures. These are created as they are evaluated (lazily). Like this:

using (var ctx = new myEntities())
{
     var AllPersons = ctx.People.Select(c => new {c.PersonId, c.PersonName}).ToList();
}

And even you don't need to disable lazy loading anymore.

After running query above:

The Result

This query currently allocates an anonymous type using select new { }, which requires you to use var. If you want allocate a known type, add it to your select clause:

private IEnumerable<MyClass> AllPersons;//global variable

using (var ctx = new myEntities())
{
     AllPersons = ctx.People
         .Select(c => new MyClass { PersonId = c.PersonId, PersonName = c.PersonName }).ToList();
}

And:

public class MyClass
{
    public string PersonId { get; set; }
    public string PersonName { get; set; }
}
like image 1
Salah Akbari Avatar answered Nov 12 '22 22:11

Salah Akbari