Domanda

I have a model-first, entity framework design like this (version 4.4)

Entity ER diagram When I load it using code like this:

PriceSnapshotSummary snapshot = db.PriceSnapshotSummaries.FirstOrDefault(pss => pss.Id == snapshotId);

the snapshot has loaded everything (that is SnapshotPart, Quote, QuoteType), except the DataInfo. Now looking into the SQL this appears to be because Quote has no FK to DataInfo because of the 0..1 relationship. However, I would have expected that the navigation property 'DataInfo' on Quote would still go off to the database to fetch it.

My current work around is this:

foreach (var quote in snapshot.ComponentQuotes)
{
    var dataInfo = db.DataInfoes.FirstOrDefault(di => di.Quote.Id == quote.InstrumentQuote.Id);
    quote.InstrumentQuote.DataInfo = dataInfo;
}

Is there a better way to achieve this? I thought EF would automatically load the reference?

È stato utile?

Soluzione

This problem has to do with how the basic linq building blocks interact with Entity Framework.

take the following (pseudo)code:

IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
    addresses = db.Users.Addresses.Where(addr => addr.Number > 1000);
}

addresses.Select(addr => Console.WriteLine(addr.City.Name));

This looks OK, but will throw a runtime error, because of an interface called IQueryable.

IQueryable implements IEnumerable and adds info for an expression and a provider. This basically allows it to build and execute sql statements against a database and not have to load whole tables when fetching data and iterating over them like you would over an IEnumerable.

Because linq defers execution of the expression until it's used, it compiles the IQueryable expression into SQL and executes the database query only right before it's needed. This speeds up things a lot, and allows for expression chaining without going to the database every time a Where() or Select() is executed. The side effect is if the object is used outside the scope of db, then the sql statement is executed after db has been disposed of.

To force linq to execute, you can use ToList, like this:

IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
    addresses = db.Users.Addresses.Where(addr => addr.Number > 1000).ToList();
}

addresses.Select(addr => Console.WriteLine(addr.City.Name));

This will force linq to execute the expression against db and get all addresses with number greater than a thousand. this is all good if you need to access a field within the addresses table, but since we want to get the name of a city (a 1..1 relationship similar to yours), we'll hit another bump before it can run: lazy loading.

Entity framework lazy loads entities by default, so nothing is fetched from the database until needed. Again, this speeds things up considerably, since without it every call to the database could potentially bring the whole database into memory; but has the problem of depending on the context being available.

You could set EF to eager load (in your model, go to properties and set 'Lazy Loading Enabled' to False), but that would bring in a lot of info you probably don't use.

The best fix for this problem is to execute everything inside db's scope:

IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
    addresses = db.Users.Addresses.Where(addr => addr.Number > 1000);
    addresses.Select(addr => Console.WriteLine(addr.City.Name));
}

I know this is a really simple example but in the real world you can use a DI container like ninject to handle your dependencies and have your db available to you throughout execution of the app.

This leaves us with Include. Include will make IQueryable include all specified relation paths when building the sql statement:

IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
    addresses = db.Users.Addresses.Include("City").Where(addr => addr.Number > 1000).ToList;
}

addresses.Select(addr => Console.WriteLine(addr.City.Name));

This will work, and it's a nice compromise between having to load the whole database and having to refactor an entire project to support DI.

Another thing you can do, is map multiple tables to a single entity. In your case, since the relationship is 1-0..1, you shouldn't have a problem doing it.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top