0

I'm still learning EF and I'm trying to learn how to use a ViewModel and JOIN with multiple tables. Researching I think I found how to do it but it's not working.

I have two tables, CustomerCall and CallStatus. The customer call has a Status (int) field and the CallStatus will have the display name of the status. So I need to JOIN those together.

In my research it looks like I need a Navagation property in my ViewModel and then use the .Include of EF. So, I created a FK in SQL between CustomerCall.Status and CallStatus.Id.

Here are my class and View Model

[Table("CustomerCall")]
public partial class CustomerCall
{
    public int Id { get; set; }

    [StringLength(50)]
    public string CustomerName { get; set; }

    [StringLength(50)]
    public string Subject { get; set; }

    [Column(TypeName = "text")]
    public string Comment { get; set; }

    public DateTime? CallDate { get; set; }

    public int? Status { get; set; }

    public int? AssignedTo { get; set; }

    public DateTime? CreateDate { get; set; }
}

public partial class CallStatus
{
    public int Id { get; set; }

    [StringLength(25)]
    public string StatusName { get; set; }
}

public class CustomerCallVM
{
    public int Id { get; set; }

    [DisplayName("Customer Name")]
    public string CustomerName { get; set; }

    public string Subject { get; set; }

    public string Comment { get; set; }

    [DisplayName("Call Date")]
    public DateTime? CallDate { get; set; }

    public int? Status { get; set; }

    [DisplayName("Status")]
    public string StatusName { get; set; }

    public int? AssignedTo { get; set; }

    [DisplayName("Assigned To")]
    public string AssignedToName { get; set; }

    [DisplayName("Create Date")]
    public DateTime? CreateDate { get; set; }


    public CallStatus CallStatus { get; set; }

}

Here is the EF in my repository I'm trying to use but I get an error "A specified Include path is not valid. The EntityType 'CPPCustomerCall.ViewModels.CustomerCall' does not declare a navigation property with the name 'CallStatus'"

public CustomerCallVM SelectById(int? id)
{
    using (DataContext db = new DataContext())
    {
        db.Configuration.AutoDetectChangesEnabled = false;      //no changes needed so turn off for performance.

        CustomerCallVM customerCall = new CustomerCallVM();

        var call = db.CustomerCalls.Include("CallStatus").Where(c => c.Id == id).FirstOrDefault();


        return customerCall;
    }
}
Caverman
  • 3,371
  • 9
  • 59
  • 115

2 Answers2

2

I need a Navagation property in my ViewModel

No, that won't work. Entity Framework doesn't know anything about your viewmodel, and also doesn't know about the relation between CustomerCall and CallStatus. You need that navigation property in your entity model CustomerCall.

You also need a CallStatusVM.

Then you can query your database:

var customerCall = db.CustomerCalls.Include(c => c.CallStatus).Where(...).FirstOrDefault();

And then you can map it to your viewmodel:

var customerCallVM = new CustomerCallVM
{
    Id = customerCall.Id,
    CustomerName = customerCall.CustomerName,
    // ...
    CallStatus = new CallStatusVM
    {
        Id = customerCall.CallStatus.Id,
        StatusName = customerCall.CallStatus.StatusName,
    }
};

The latter can be made easier with AutoMapper.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • I think [this](http://blog.alanta.nl/2015/04/automapper-is-evil.html) sums up my own feelings about automapper. "Hand authored mapping code is faster, easier to understand and easier to maintain." <== this – spender May 04 '16 at 14:26
  • Thanks for the help. Not sure why I would need CallStatusVM. I thought the whole idea on the VM is to shape the class with the data I want. So my CustomerCallVM only has a StatusName because that's the only thing I'm going to display. – Caverman May 04 '16 at 14:33
  • I think this will put me in the right direction though. – Caverman May 04 '16 at 14:33
  • @Caverman alright, but that wasn't clear from your question. If you want `customerCallVM.StatusName = customerCall.CallStatus.StatusName`, then indeed you don't need a `CallStatusVM`. – CodeCaster May 04 '16 at 14:34
  • I took the CallStatus property out of my VM and put it in my CustomerCall entity. Then tried "var call = db.CustomerCalls.Include(s => s.CallStatus).Where(c => c.Id == id).FirstOrDefault;" but i get an error with the s.CallStatus saying "cannot convert lambda expression to type string because it's not a delegate type. Do I need to do anything special with my "CallStatus" property that I put in my CustomerCall entity class to let it know it's a navigation property? – Caverman May 04 '16 at 15:11
  • @Caveman that means you're missing `using System.Data.Entity` to import the `Include()` extension method that has a lambda parameter. See also http://stackoverflow.com/questions/19197481/cannot-convert-lambda-expression-to-type-string-because-it-is-not-a-delegate-t. – CodeCaster May 04 '16 at 15:12
  • My understanding of what's going on is that I'm putting a property in my CustomerCall entity of type CallStatus. Since there is a FK between CustomerCall.Status and CallStatus.Id in SQL the .Include of EF will do a JOIN on that field and include the CallStatus object in the CustomerCall entity? Hopefully I explained that correct. – Caverman May 04 '16 at 15:15
  • @Caveman yes, that's correct, that's how navigation properties work. – CodeCaster May 04 '16 at 15:15
0

The error you are receiving is telling you that you need a navigation property. The .Include() is eager loading. This loading needs to load the entity into something. That something is the navigation property.

The code below is what your CustomerCall class needs to look like with the addition of the Navigation Property.

[Table("CustomerCall")]
public partial class CustomerCall
{
    public int Id { get; set; }

    [StringLength(50)]
    public string CustomerName { get; set; }

    [StringLength(50)]
    public string Subject { get; set; }

    [Column(TypeName = "text")]
    public string Comment { get; set; }

    public DateTime? CallDate { get; set; }

    public int? Status { get; set; }

    public int? AssignedTo { get; set; }

    public DateTime? CreateDate { get; set; }

    //This is your navigation property
    [ForeignKey("Status")]
    public virtual CallStatus CallStatus { get; set; }
}
Matt Rowland
  • 4,575
  • 4
  • 25
  • 34