I would like to get Exams and Test entities that have a UserTest entity with a UserId that is either equal to "0" or to a provided value. I had a number of suggestions but so far none have worked. One suggestion was to start by getting UserTest data and the other solution was to start by getting Exam data. Here's what I have when I used the UserTests as the source starting point.
I have the following LINQ:
var userTests = _uow.UserTests
.GetAll()
.Include(t => t.Test)
.Include(t => t.Test.Exam)
.Where(t => t.UserId == "0" || t.UserId == userId)
.ToList();
When I check _uow.UserTests
with the debugger it's a repository and when I check the dbcontext
's configuration.lazyloading
then it is set to false
.
Here's my classes:
public class Exam
{
public int ExamId { get; set; }
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Test> Tests { get; set; }
}
public class Test
{
public int TestId { get; set; }
public int ExamId { get; set; }
public string Title { get; set; }
public virtual ICollection<UserTest> UserTests { get; set; }
}
public class UserTest
{
public int UserTestId { get; set; }
public string UserId { get; set; }
public int TestId { get; set; }
public int QuestionsCount { get; set; }
}
When I looked at the output I saw something like this:
[{"userTestId":2,
"userId":"0",
"testId":12,
"test":{
"testId":12,"examId":1,
"exam":{
"examId":1,"subjectId":1,
"tests":[
{"testId":13,"examId":1,"title":"Sample Test1",
"userTests":[
{"userTestId":3,
"userId":"0",
Note that it gets a UserTest
object, then gets a test object and then an exam object. However the exam object contains a test collection and then it heads back down again and gets the different tests and the unit tests inside of those:
UserTest
> Test
> Exam
> Test
> UserTest
?
I have tried hard to ensure lazy loading is off and debug tell me it's set to false
. I am using EF6 and WebAPI but not sure if that makes a difference as I am debugging at the C# level.
If you try something like:
Would your code throw an exception because the Test property hasn't been loaded? I suspect the problem is your repository is using LINQ to SQL and not LINQ to Entities. You can't turn off Lazy Loading with LINQ to SQL. You would have to show how your repository works to fix the problem in that case.
Your query will load all
UserTest
s into the context whereUserId == "0" || UserId == userId
and you have eagerly loaded the relatedTest
and its relatedExams
.Now in the debugger you can see that the
Exams
are linked to someTests
in memory and are assuming that is because they have been lazy-loaded. Not true. They are in memory because you loaded all UserTests into the context whereUserId == "0" || UserId == userId
and you have eagerly loaded the relatedTest
. And they are linked to the navigation property because EF performs a "fix-up" based on foreign keys.The
Exam.Tests
navigation property will contain any entities loaded into the context with the correct foreign key, but will not necessarily contain allTests
linked to theExam
in the database unless you eagerly load it or turn on lazy loadingI believe that deferred execution causes nothing to happen unless something is actually read from
userTests
. Try to includevar userTestsAsList = userTests.ToList()
and check with the debugger ifuserTestsAsList
contains the desired sequence.You can't avoid that the inverse navigation properties are populated by EF, no matter if you load related entities with eager or lazy loading. This relationship fixup (as already explained by @Colin) is a feature you can't turn off.
You could solve the problem by nullifying the unwished inverse navigation properties after the query is finished:
However, in my opinion the flaw of your design is that you try to serialize entities instead of data transfer objects ("DTOs") that are specialized to the view where you want to send the data to. By using DTOs you can avoid the inverse navigation properties that you don't want altogether and maybe other entity properties that you don't need in your view. You would define three DTO classes, for example:
And then use a projection to load the data:
You could also "flatten" the object graph by defining only a single DTO class that contains all the properties you need for the view:
The projection would become simpler and look like this:
By using DTOs you do not only avoid the problems of inverse navigation properties but also follow good security practices to "white-list" the exposed property values from your database explicitly. Imagine you would add a test access
Password
property to theTest
entity. With your code that serializes eagerly loaded full entities with all properties the password would get serialized as well and run over the wire. You don't have to change any code for this to happen and in the worst case you wouldn't be aware that you are exposing passwords in a HTTP request. On the other hand when you are defining DTOs a new entity property would only be serialized with your Json data if you add this property explicitly to the DTO class.Is there any reson you are using "virtual" for your collections? If you're using "include", I would recommend getting rid of the "virtual"
As far as I can read your POCO-Relationships and your query, your Repo is returning what you asked for. But did you know you asked for this?
Your Entity of
<Exam>
is being treated as a Grand-Child when it seems to be a Grand-Parent (in fact, a graph root) having children of<Test>
who have children / grand-children of type<UserTest>
.As you are eager loading (and serializing?), of course your
<Exam>
should eager load its<Test>
Collection, which should load their<UserTest>
Collections.By working your way down the graph, you are causing a full circle.
Did you mean to have the opposite relationships?
I'm making many assumptions about your data. This relationships simply makes more sense as real-world entities and by your usage. That Users have Tests and Exams rather than the reverse. If so, this relationship should work with your linq query.