2

Although I love what I'm learning, I'm finding it a struggle and need some help

I've been using these two tutorials which I think are awesome: http://weblogs.asp.net/scottgu/archive/2010/07/16/code-first-development-with-entity-framework-4.aspx http://msdn.microsoft.com/en-us/data/gg685467

Currently my main problem/confusion is:

I have a CodeFirst table/entity I don't know how to correctly get data from other tables/entities to show in my views:

public class Car {
    public int ID { get; set; }
    public string Name { get; set; }

    public int EngineID { get; set; }
    public virtual Engine { get; set; }
}

public class Engine {
    public int ID { get; set; }
    public string Name { get; set; }
    public string Manufacturer { get; set; }
    // (plus a whole lot of other things)
}

Now when I create a View for Cars (using the List type/option) I get a nice autogenerated list

@foreach (var item in Model) {
<tr>
    <td>@item.ID</td>
    <td>@item.Name</td>
    <td>@item.EngineID</td>
</tr>

Perfect... except EngineID is mostly worthless to the viewer, and I want to show Engine.Name instead

So I assumed I could use EF lazy loading:

<td>@item.Engine.Name</td>

Unfortunately when I tried that, it says my ObjectContext has been disposed so can't get any further data requiring a connection

Then I tried going to the controller and including the Engine.Name

var cars = (from c in db.Cars.Include("Engine.Name") select c;

Which tells me: Entities.Engine does not declare a navigation property with the name 'Name'

... ? Lies

Include("Engine") works fine, but all I want is the Name, and Include("Engine") is loading a large amount of things I don't want

Previously in a situation like this I have created a view in the DB for Car that includes EngineName as well. But with CodeFirst and my noobness I haven't found a way to do this

How should I be resolving this issue?

I thought perhaps I could create a Model pretty much identical to the Car entity, but add Engine.Name to it. This would be nice as I could then reuse it in multiple places, but I am at a loss on how to populate it etc

Wanting to learn TDD as well but the above is already frustrating me :p

Ps any other tutorial links or handy things to read will be greatly appreciated

mejobloggs
  • 7,937
  • 6
  • 32
  • 39

1 Answers1

2

It isn't lies as you are actually trying to include a property that's a 2nd level down withouth giving it a way to navigate. If you let EF generate your DB with this structure, it would likely have made a navigation table called something like Car_Engine and if you include the name without the object it HAS mapped, then it's not got a navigation property in your new object.

The simple way around this is to go:

(from c in db.Cars.Include("Engine") select new { c, EngineName = c.Engine.Name }

If you still get navigation property errors then you might need to make sure your are mapping to your schema correctly. This can be done with EntityTypeConfiguration classes using the fluent API - very powerful.

This of course won't help in strongly typing your car object to show in MVC. If you'd like to get around this, your gut feeling is right. It's pretty common to use viewmodels that are read only (by design, not necessarily set to readonly) classes that provide simple views of your data.

Personally I keep my model quite clean and then have another project with viewmodels and a presentation project to populate. I'd avoid using overlapping entities in your core model as it might lead to unpredictable behaviour in the data context and at least a peristance nightmare when updating multiple entities (ie who's responsible for updating the engine name?).

Using you viewmodels, you can have a class called CarSummaryView or something with only the data you want on it. This also solves the issue of being vulnerable to overposting or underposting on your site. It can be populated by the above query quite easily.

PS There's a bunch of advantages to using viewmodels beyond just not loading full heirarchies. One of the biggest is the intrinsic benefit it gives you with avoiding over and underposting scenarios.

There's loads of ways to implement viewmodels, but as a simple CarView example:

public class CarView
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string EngineName { get; set; }
}

This should be clearly seperated from your entity model. In a big project, you'd have a single viewmodels project that the presenter can return, but in a smaller one you just need them in the same layer as the service code.

To populate it directly from the query, you can do the following

List<CarView> cars = (from c in db.Cars.Include("Engine.Name") select new CarView() { ID = c.ID, Name = c.Name, EngineName = c.Engine.Name }).ToList();
Gats
  • 3,452
  • 19
  • 20
  • Would you be able to give me a quick example of the CarSummaryView viewmodel and how to populate it? Also, what is the e in EngineName = e.Name ? Don't mean to get you to do all the work, it's just things aren't connecting in my head and trying to understand – mejobloggs Mar 27 '11 at 06:47
  • Simple example supplied. The e was a mistake :) Edited that now. – Gats Mar 27 '11 at 11:36
  • That's great thanks. Also, turns out if you do select new CarView() like that you don't even need to use the .Include("Engine.Name") (except if lazy loading was turned off I guess?) – mejobloggs Mar 28 '11 at 06:01
  • Yep very true! I do often like to be explicit about it in case I end up removing the ToList and add manual population in a for loop later (ie like if i needed paging or something). I'm still getting the hang of where to put everything to be clean & scalable in EF4.1 myself. – Gats Mar 28 '11 at 15:07