dbcontext loading related entities using select(), include() and where() not working - entity-framework

I have the following relationship between the entities.
Company 1 ---* Appointments *---1 Employee
I have the .net asp membership in a separate database. Whenever a user is created it can be assigned to companies, employees, or administrators roles.
in the Index action of my Company Controller, I check the logged in user's role. Based on the role, I make different linq query. For example, administrators can get list of all companies, companies can get list of company which has a username property (string) same as the User.Identity.Name. For both of administrators and companies role, it is working fine.
For the employees role, I want to load all the companies that are related to the current employee. I am having hard time to compose a linq query that does this job.
i tried
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower())).ToList();
to which i get this error
"The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
Parameter name: path"
Here are the source code,
CompanyController
[Authorize]
public class CompanyController : Controller
{
private MyDBContext db = new MyDBContext();
//
// GET: /Company/
public ViewResult Index()
{
var viewModel = new CompanyIndexViewModel();
if (Roles.IsUserInRole("administrators")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.ToList() };
}
else if (Roles.IsUserInRole("companies")) {
viewModel = new CompanyIndexViewModel { Companies = db.Companies.Where(c => c.Username.ToLower().Equals(this.User.Identity.Name.ToLower())).ToList() };
}
else if (Roles.IsUserInRole("employees")) {
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower())).ToList();
viewModel = new CompanyIndexViewModel { Companies = companies.ToList() };
}
return View(viewModel);
}
...
Models
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Company
{
public int CompanyId { get; set; }
[Required]
public string Username { get; set; }
[Display(Name="Company Name")]
[Required]
public string Name { get; set; }
[UIHint("PhoneNumber")]
public string Phone { get; set; }
[DataType(DataType.Url)]
public string Website { get; set; }
[DataType(DataType.EmailAddress)]
public string Email { get; set; }
public AddressInfo AddressInfo { get; set; }
public virtual ICollection<Contact> Contacts { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
public Company(){
this.AddressInfo = new AddressInfo();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Appointment
{
public int AppointmentId { get; set; }
[Required]
[UIHint("DateTime")]
[Display(Name="Appointment Date")]
public DateTime? DateOfAppointment { get; set; }
[Required]
public int CompanyId { get; set; }
[Required]
public int EmployeeId { get; set; }
[Required]
[UIHint("MultilineText")]
[Display(Name = "Appointment Summary")]
public string Description { get; set; }
[Display(Name="Allocated No of Hours")]
public decimal NoOfHoursWorked { get; set; }
public virtual Company Company { get; set; }
public virtual Employee Employee { get; set; }
public virtual ICollection<AppointmentLine> AppointmentLines { get; set; }
public Appointment() {
//this.AppointmentLines = new List<AppointmentLine>();
this.DateOfAppointment = DateTime.Now;
}
[NotMapped]
[Display(Name="Actual No of Hours")]
public decimal ActualHoursWorked {
get
{
decimal total = 0;
foreach (var jobline in this.AppointmentLines)
{
total = total + jobline.TimeSpent;
}
return total;
}
}
}
public class AppointmentLine
{
public int AppointmentLineId { get; set; }
[UIHint("MultilineText")]
[Required]
public string Description { get; set; }
[Display(Name="Time Spent")]
[DataType(DataType.Duration)]
public decimal TimeSpent { get; set; }
public int AppointmentId { get; set; }
public virtual Appointment Appointment { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace TorontoWorkforce.Models
{
public class Employee: TorontoWorkforce.Models.Person
{
public int EmployeeId { get; set; }
[Required]
public string Username { get; set; }
[Display(Name="Date Hired")]
public DateTime? DateHired { get; set; }
[Required]
public string Position { get; set; }
public virtual ICollection<Appointment> Appointments { get; set; }
public Employee() {
this.DateHired = DateTime.Now;
}
}
}

If you want to get companies which have appointment with selected employee you don't need to use Include. Include is for instructing EF to load all appointments related to the company (and it doesn't support filtering). Try this:
string userName = this.User.Identity.Name.ToLower();
var companies = db.Companies.Where(c => c.Appointments.Any(a =>
a.Employee.Username.ToLower() == userName)).ToList();

I think you just have an end parentheses in the wrong place. You need one more after "a => a.Employee" and one less after "this.User.Identity.Name.ToLower()));"
Try this code:
var companies = db.Companies.Include(c => c.Appointments.Select(a=>a.Employee)).Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower()).ToList();
Edit:
You should also be able to use the standard string include method:
var companies = db.Companies.Include("Appointments.Employee").Where(e=>e.Username.ToLower() == this.User.Identity.Name.ToLower()).ToList();

Related

Why does PersistentObjectSpace sometimes return a proxy and sometimes return an object?

The debugger shows me that in the following code
_taxRate =
PersistentObjectSpace.FindObject<TaxRate>(CriteriaOperator.Parse("[TaxCodeId] = ?", TaxCodeId));
var _product2 =
PersistentObjectSpace.FindObject<Product>(CriteriaOperator.Parse("[ItemId] = ?", ItemId));
_taxRate is a poco but _product2 is a proxy
The objects are
[Table("TaxCode")]
[DefaultProperty("TaxCode")]
[ImageName("BO_List")]
public class TaxRate : BasicBo
{
[Key] public short TaxCodeId { get; set; }
[Required]
[RuleRequiredField(DefaultContexts.Save)]
[StringLength(20, ErrorMessage = "The field cannot exceed 20 characters. ")]
public string TaxCode { get; set; }
[Required]
[RuleRequiredField(DefaultContexts.Save)]
public decimal Percentage { get; set; }
public override string ToString()
{
return TaxCode;
}
}
and
[Table("MyExtItem")]
[DefaultProperty("ProductCode")]
[NavigationItem("Config")]
public class Product : BasicBo
{
[Key]
public int ItemId { get; set; }
public string ItemName { get; set; }
[Column("Item Number")] public string ProductCode { get; set; }
[MaxLength(10)] public string UnitName { get; set; }
public int? ProductImageId { get; set; }
[ForeignKey("ProductImageId")] public virtual ProductImage ProductImage { get; set; }
[ForeignKey("ItemId")] public virtual ExtMaterialProperty ExtMaterial { get; set; }
}
This is expected behaviour when EF is configured to support lazy loading.
TaxRate holds no references to other entities so EF can return a concrete instance.
Product contains two references to other entities, ProductImage and ExtMaterial.
If I run the code:
var product = context.Products.Single(x => x.ItemId == itemId);
to get a product, EF uses a proxy in order to be prepared for when I try to access something like ProductImage.
var imageName = product.ProductImage.Name;
You can disable the proxies using Configuration.ProxyCreationEnabled on the DbContext. (EF6) This does mean that any references will need to be eager loaded or explicitly loaded, as Lazy Loading will not function without the proxies.

How to insert data into a table with composite key from Multiple Tables in ASP.NET MVC

Answer to this question is found at here
Having three tables:
Database diagram is here
Book class:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
public partial class Books
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Books()
{
UserBookComments = new HashSet<UserBookComments>();
}
[Key]
public int BookID { get; set; }
[Required]
[StringLength(255)]
public string Title { get; set; }
[Required]
[StringLength(255)]
public string Category { get; set; }
[Column(TypeName = "date")]
public DateTime PublishDate { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<UserBookComments> UserBookComments { get; set; }
}
User class:
public partial class Users
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Users()
{
UserBookComments = new HashSet<UserBookComments>();
}
[Key]
public int UserID { get; set; }
[Required]
[StringLength(255)]
public string UserName { get; set; }
[Required]
[StringLength(255)]
public string Password { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<UserBookComments> UserBookComments { get; set; }
}
And the UserBookComments class:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
public partial class UserBookComments
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserID { get; set; }
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int BookID { get; set; }
public int? Comments { get; set; }
public virtual Bookss Bookss { get; set; }
public virtual Users Users { get; set; }
}
The table "Books" is an already saved database. Each user can comment for each book and I want a view model that holds all the data from books with their comments.
The primary key on UserBookComment would be composite, on UserID and BookID.
I used EF Code First and my DBModel context class looks so:
using System.Data.Entity;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
public partial class DbModel : DbContext
{
public DbModel()
: base("name=DbModel")
{
}
public virtual DbSet<Books> Books { get; set; }
public virtual DbSet<UserBookComments> UserBookComments { get; set; }
public virtual DbSet<Users> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Books>()
.Property(e => e.Category)
.IsUnicode(false);
modelBuilder.Entity<Books>()
.HasMany(e => e.UserBookComments)
.WithRequired(e => e.Books)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Users>()
.HasMany(e => e.UserBookComments)
.WithRequired(e => e.Users)
.WillCascadeOnDelete(false);
}
}
I wonder how to save comments and display whole list of [title, category, publish date and comments] using a ViewModel class?
As you asked in the comments, I have provided the way to insert a record into the UserBookComment table by adding a method into the BookEntities class.
public partial class BooksEntities : DbContext
{
public virtual DbSet<Books> Books { get; set; }
public virtual DbSet<UserBookComment> UserBookComments { get; set; }
public virtual DbSet<Users> Users { get; set; }
public void AddComment(int userId, int bookId, string comment)
{
var userBookComment = new UserBookComment()
{
UserId = userId,
BookId = bookId,
Comment = comment
};
this.AddComment(userBookComment);
}
public void AddComment(UserBookComment userBookComment)
{
this.UserBookComment.Add(userBookComment);
this.UserBookComment.SaveChanges();
}
}
I assumed based on your provided information that your UserBookComment class looked like this
public class UserBookComment
{
public int UserId { get; set; }
public int BookId { get; set; }
public string Comment { get; set; }
}
Looks like you are using entity framework. To create a composite key, just at the Key attribute on both properties. To control the key order use the column atribute. Referencing should be fixed automaticly using the names in the model, or try googling on entity framework foreign key. I'm sorry I don't have the link right now.
For the viewmodel, just don't foget to use include in the statement.

how to update .edmx file without effecting all the other model classes?

how to update .edmx file without effecting all the other model classes?
when I update the edmx file it recreates all the other classes and this is a problem for me because I made some changes on the other classes so how can I modify it without affecting the other classes.
For example this is one of my classes
public partial class company
{
public company()
{
this.Departments = new HashSet<department>();
this.CustomersOfTheCompany = new HashSet<company_customers>();
this.CompaniesTheCompanyCustomerFor = new HashSet<company_customers>();
this.CustomerProjectDetails = new HashSet<cus_pro_connector>();
this.CompanyAsSupplier = new HashSet<company_suppliers>();
this.CompanyAsCustomer = new HashSet<company_suppliers>();
this.Tickets = new HashSet<oneTimeAuthenticationTicket>();
}
[Required(ErrorMessage = "Company name is required")]
[StringLength(200, MinimumLength = 3, ErrorMessage = "Length Of The Company Name Should Be More Than Three Letters")]
public string CompanyName { get; set; }
[Required]
[EmailAddress(ErrorMessage = "Invalid Email Address")]
public string Email { get; set; }
[Required]
public int Country { get; set; }
public int company_id { get; set; }
public string Logo { get; set; }
public string Description { get; set; }
private CompanyDTO _CDTO = new CompanyDTO();
public CompanyDTO CDTO { get { return this._CDTO; } set { this._CDTO = value; } }
public virtual ICollection<department> Departments { get; set; }
public virtual country CountryOfTheCompany { get; set; }
public virtual ICollection<company_customers> CustomersOfTheCompany { get; set; }
public virtual ICollection<company_customers> CompaniesTheCompanyCustomerFor { get; set; }
public virtual ICollection<cus_pro_connector> CustomerProjectDetails { get; set; }
public virtual ICollection<company_suppliers> CompanyAsSupplier { get; set; }
public virtual ICollection<company_suppliers> CompanyAsCustomer { get; set; }
public virtual ICollection<oneTimeAuthenticationTicket> Tickets { get; set; }
}
so when I modify the .edmx the class attributes will no longer be available.
It is not possible to retain edits to generated files when you re-generate them. If you need to apply attributes to generated code, there is a MetadataType mechanism that allows you to specify validation attributes in another partial class.
See this other answer or MSDN for further information on this.
I don't understand your example - But I will explain here:
Let's say you have the following Entity Models:
And the User.cs looks like this:
public partial class User
{
public User()
{
this.UserWindows = new HashSet<UserWindow>();
}
public int UserId { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public virtual ICollection<UserWindow> UserWindows { get; set; }
}
Add a new file and call it Extensions.cs, then create a New Partial Class in this File:
Then you can Add Properties to this Class:
public partial class User
{
public int NewUserId { get; set; }
}
And whenever you create a New User Object you will see your new Properties:
Similarly you can do this for UserWindow.cs
Extensions.cs:
public partial class User
{
public int NewUserId { get; set; }
}
// Similarly you can do
public partial class UserWindow
{
public string MyNewProperty { get; set; }
}
Then

Error in many-to-many relationship using Fluent API in Entity Framework 6 one model is self referencing

Category model is self referencing
public class Category
{
[Key]
public int CategoryID { get; set; }
public string Name { get; set; }
public int? ParentID { get; set; }
public Category Cat { get; set; }
public ICollection<Category> Categories { get; set; }
public ICollection<BusinessDetail> BDetails { get; set; }
}
and BusinessDetail is like
public class BusinessDetail
{
[Key]
public int ID { get; set; }
[Required]
[Display(Name="Business Name")]
public string BusinessName { get; set; }
public string Address { get; set; }
[Display(Name="Contact")]
public string contactDetail { get; set; }
// public int CategoryID { get; set; }
// public Category Category { get; set; }
public int ? LocationID { get; set; }
public Location Location { get; set; }
[Display(Name="Website Address")]
public string Website_Address { get; set; }
[Display(Name="Is Verified")]
public bool Is_verified { get; set; }
[Required]
[Display(Name="Added By")]
public string Added_By { get; set; }
[Required]
[Display(Name="Added Date")]
[DataType(DataType.DateTime)]
public DateTime Added_Date { get; set; }
[Display(Name="Is Featured")]
public bool Is_Featured { get; set; }
public string Latitude { get; set; }
public string VerifiedBy { get; set; }
public string Longitude { get; set; }
public ICollection<Category> Categories { get; set; }
}
When creating a many-to-many relationship using Fluent API
modelBuilder.Entity<BusinessDetail>()
.HasMany(c => c.Categories).WithMany(i => i.BDetails)
.Map(t => t.MapLeftKey("ID")
.MapRightKey("CategoryID")
.ToTable("BusinessCategories"));
I get this error
There are no primary or candidate keys in the referenced table
'dbo.BusinessDetails' that match the referencing column list in the
foreign key 'FK_dbo.BusinessCategories_dbo.BusinessDetails_ID'.
I need help on this error.
I will try to work out your exact example, but the code below works without any configuration:
EDIT:
I added in the code from OnModelCreating and changed the property names to those in your exampe, but it all keeps working. You do realize though, that the ParentId property is not seen as the foreign key for a parent Category, but that EF will create a Cat_CategoryId foreign key for you?
I advise to start from scratch using my code and work step by step towards the existing code.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Data.Entity;
public class CategoryContext : DbContext
{
public DbSet<Category> Categories { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//Fill in later.
}
}
public class Category
{
public Category()
{
Children = new List<Category>();
Details = new List<BussinesDetail>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Category Parent { get; set; }
public virtual ICollection<Category> Children { get; set; }
public virtual ICollection<BussinesDetail> Details { get; set; }
}
public class BussinesDetail
{
public int Id { get; set; }
public string BussinesName { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
static class Module1
{
public static void Main()
{
using (context = new CategoryContext()) {
var newCat = context.Categories.Add(new Category { Name = "CatOne" });
context.SaveChanges();
newCat = context.Categories.Single;
Console.WriteLine(newCat.Name);
Console.ReadLine();
}
}
}

Should data access operations be encapsulated in the ViewModel or Controller?

One of the main reasons I want to use a ViewModel instead of the model direct in the controller is that I notice the controller containing data access that, to me, seems to be the wrong place for this work. Shouldn't data access / read write be done in the ViewModel?
things like:
Data Access
Read / Write
Mapping
Select Lists
i.e.
EF created Model:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace TestIODSManagement.Models
{
using System;
using System.Collections.Generic;
public partial class ClientJob
{
public int Id { get; set; }
public int ClientId { get; set; }
public int JobTypeId { get; set; }
public string OlapAppName { get; set; }
public string EdgeAppName { get; set; }
public Nullable<int> DatabaseServer { get; set; }
public Nullable<int> ProcessingServer { get; set; }
public Nullable<int> QueryServer { get; set; }
public string DatabaseName { get; set; }
public int DomainId { get; set; }
public int CurrencyTypeId { get; set; }
public virtual Client Client { get; set; }
public virtual CurrencyType CurrencyType { get; set; }
public virtual Domain Domain { get; set; }
public virtual JobType JobType { get; set; }
public virtual Server DbServer { get; set; }
public virtual Server OpServer { get; set; }
public virtual Server OqServer { get; set; }
}
}
Possible ViewModel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using TestIODSManagement.Models;
namespace TestIODSManagement.ViewModels
{
public class ClientJobsViewModel
{
private DeploymentsEntities db = new DeploymentsEntities();
public int Id { get; set; }
public int ClientId { get; set; }
public int JobTypeId { get; set; }
public string OlapAppName { get; set; }
public string EdgeAppName { get; set; }
public Nullable<int> DatabaseServer { get; set; }
public Nullable<int> ProcessingServer { get; set; }
public Nullable<int> QueryServer { get; set; }
public string DatabaseName { get; set; }
public int DomainId { get; set; }
public int CurrencyTypeId { get; set; }
public virtual Client Client { get; set; }
public virtual CurrencyType CurrencyType { get; set; }
public virtual Domain Domain { get; set; }
public virtual JobType JobType { get; set; }
public virtual Server DbServer { get; set; }
public virtual Server OpServer { get; set; }
public virtual Server OqServer { get; set; }
private List<Client> _clients;
public IList<Client> Clients { get { return _clients.AsReadOnly(); } }
private List<Domain> _domains;
public IList<Domain> Domains { get { return _domains.AsReadOnly(); } }
private List<CurrencyType> _currencyTypes;
public IList<CurrencyType> CurrencyTypes { get { return _currencyTypes.AsReadOnly(); } }
private List<JobType> _jobTypes;
public IList<JobType> JobTypes { get { return _jobTypes.AsReadOnly(); } }
private List<Server> _databaseServers;
public IList<Server> DatabaseServers { get { return _databaseServers.AsReadOnly(); } }
private List<Server> _processingServers;
public IList<Server> ProcessingServers { get { return _processingServers.AsReadOnly(); } }
private List<Server> _queryServers;
public IList<Server> QueryServers { get { return _queryServers.AsReadOnly(); } }
public ClientJobsViewModel(Client client)
{
ForceClientList(client);
SetSelectLists();
}
public ClientJobsViewModel(int jobId)
{
ClientJob job = db.ClientJobs.Find(jobId);
Client thisClient = db.Clients.Find(job.ClientId);
ForceClientList(thisClient);
SetSelectLists();
}
private void ForceClientList(Client client)
{
_clients = db.Clients
.Where(c => c.Id == client.Id)
.ToList();
}
private void SetSelectLists()
{
_databaseServers = db.Servers
.Where(s => s.ServerPurpos.PurposeName == "DataWarehouse").ToList();
_processingServers = db.Servers
.Where(s => s.ServerPurpos.PurposeName == "Processing").ToList();
_queryServers = db.Servers
.Where(s => s.ServerPurpos.PurposeName == "Querying").ToList();
_domains = db.Domains.ToList();
_currencyTypes = db.CurrencyTypes.ToList();
_jobTypes = db.JobTypes.ToList();
}
}
}
You're right that the controller is not the right place, but neither is the view model. Ideally, your view model should only contain properties to encapsulate the data, and rarely any functionality. I would place all that logic in a dedicated data layer, or failing that if it's small, simple project, a class dedicated to data access.
Then, the controller should be the one to call into that layer, class or service and request that the view model be populated.
There is difference between ViewModel and Model. ViewModel is only custom class - crate which pass data between view and controller in strongly typed fashion. Model is "something" your controller calls to get data, business logic, etc. It can be service layer, business logic class, repository or whatever else. Data access is part of Model.