I am trying to learn asp.net MVC, by converting a web forms app I have. It's a room booking app, where there is a customer table (tblCustomerBooking) which has a one to many relationship with tblRental - so one customer can book more than one room. The fields that match each other are tblCustomerBooking.customer_id -> tblRental.customer_ref
I'm trying to use code first - and building a model class - but I can't figure out how to link the two tables, so that when I query the dbContext, it will return a customer, with one or more rentals within the same model.
My table definitions are:
CREATE TABLE [dbo].[tblCustomerBooking](
[customer_id] [bigint] IDENTITY(1,1) NOT NULL,
[room_id] [bigint] NULL,
[customer_name] [varchar](110) NULL,
[customer_email] [varchar](50) NULL
CONSTRAINT [PK_tblCustomerBooking] PRIMARY KEY CLUSTERED
(
[customer_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
CREATE TABLE [dbo].[tblRental](
[rental_id] [bigint] IDENTITY(1,1) NOT NULL,
[room_id] [bigint] NOT NULL,
[check_in] [datetime] NOT NULL,
[check_out] [datetime] NOT NULL,
[customer_ref] [bigint] NULL,
[room_cost] [decimal](18, 2) NULL
CONSTRAINT [PK_tblRental_1] PRIMARY KEY CLUSTERED
([rental_id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
My attempt at building the model for this is:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Data.Entity;
namespace MvcApplication23.Models
{
public class tblRental
{
[Key()]
public int rental_id { get; set; }
public int room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public long customer_ref { get; set; }
[ForeignKey("customer_ref")]
public tblCustomerBooking Customer {get;set;}
public decimal room_cost { get; set; }
}
public class tblCustomerBooking
{
[Key()]
public long customer_id { get; set; }
public string customer_name { get; set; }
public string customer_email { get; set; }
public ICollection<tblRental> Rentals {get;set;}
}
public class RentalContext : DbContext
{
public DbSet<tblCustomerBooking> customers { get; set; }
public DbSet<tblRental> rentals { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
Controller:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using MvcApplication23.Models;
namespace MvcApplication23.api.Controllers
{
public class RentalController : ApiController
{
private RentalContext db = new RentalContext();
// GET /api/rental/5
public IQueryable<tblCustomerBooking> Get(int id)
{
return db.customers.Include("rentals").FirstOrDefault(c=>c.customer_id==id);
}
** I've updated the info above, with the actual table names that already existed in the database **
How to I link the two tables in the model? And then given a customer_id, how would I query the DbContext to return a customer, with any related entries in the tblRental table?
Thank you very much for any pointers,
Mark
To link two entities, provide a navigation property:
public class Rental
{
[Key]
public int rental_id { get; set; }
public int room_id { get; set; }
public DateTime check_in { get; set; }
public DateTime check_out { get; set; }
public int customer_ref { get; set; }
[ForeignKey("customer_ref")]
public virtual Customer Customer {get;set;}
public decimal room_cost { get; set; }
}
public class Customer
{
[Key]
public int customer_id { get; set; }
public string customer_name { get; set; }
public string customer_email { get; set; }
public virtual ICollection<Rental> Rentals {get;set;}
}
And to query your customer :
return this.DataContext.customers.Include("Rentals").FirstOrDefaul(c=>c.customer_id==customerId);
using System.ComponentModel.DataAnnotations.Schema;
public class Rental
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual int id { get; set; }
public virtual int room_id { get; set; }
public virtual DateTime check_in { get; set; }
public virtual DateTime check_out { get; set; }
public virtual int customer_id { get; set; }
public virtual decimal room_cost { get; set; }
#region Navigation Properties
[ForeignKey("customer_id")]
public virtual Customer Customer { get; set; }
#endregion
}
public class Customer
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int id { get; set; }
public string name { get; set; }
public string email { get; set; }
public virtual ICollection<Rental> Rentals {get;set;}
}
Related
I have an Organization entity table
public class Organization
{
public int OrganizationId { get; set; }
public string Name { get; set; }
public int OrganizationTypeId { get; set; }
public OrganizationType OrganizationType { get; set; }
public ICollection<OrganizationRelation> OrganizationRelations { get; set; }
}
then I have my relational table with a self-referencing parent column
public class OrganizationRelation
{
public int OrganizationRelationId { get; set; }
public int OrganizationId { get; set; }
public int? ParentOrganizationId { get; set; }
public Organization Organization { get; set; }
public Organization ParentOrganization { get; set; }
}
public class OrganizationRelationModelConfiguration : IEntityTypeConfiguration<OrganizationRelation>
{
public void Configure(EntityTypeBuilder<OrganizationRelation> builder)
{
builder.HasKey(c => c.OrganizationRelationId);
builder.Property(c => c.OrganizationRelationId).ValueGeneratedOnAdd();
builder.Property(c => c.OrganizationId).IsRequired();
builder.Property(c => c.ParentOrganizationId);
builder.HasOne(r => r.Organization).WithMany().HasForeignKey(fk => fk.OrganizationId);
builder.HasOne(r => r.ParentOrganization).WithMany().HasForeignKey(fk => fk.ParentOrganizationId);
builder.ToTable("OrganizationRelation", "dbo");
}
}
When I deploy my db with migration, I see this table created:
CREATE TABLE [mdo].[OrganizationRelation](
[OrganizationRelationId] [int] IDENTITY(1,1) NOT NULL,
[OrganizationId] [int] NOT NULL,
[ParentOrganizationId] [int] NULL,
[OrganizationId1] [int] NULL,
CONSTRAINT [PK_OrganizationRelation] PRIMARY KEY CLUSTERED
(
[OrganizationRelationId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
I'm using EF 5.0
I don't get it why is creating the column OrganizationId1
You didn't map OrganizationRelation.ParentOrganization to Organization.OrganizationRelations, so EF is adding an additional FK to OrganizationRelation for the second navigation property.
But that model seems overly complex. Why not just
public class Organization
{
public int OrganizationId { get; set; }
public string Name { get; set; }
public int OrganizationTypeId { get; set; }
public int? ParentOrganizationId { get; set; }
public Organization ParentOrganization { get; set; }
public ICollection<Organization> ChildOrganizations{ get; } = new HashSet<Organization>();
}
which creates
CREATE TABLE [Organization] (
[OrganizationId] int NOT NULL IDENTITY,
[Name] nvarchar(max) NULL,
[OrganizationTypeId] int NOT NULL,
[ParentOrganizationId] int NULL,
CONSTRAINT [PK_Organization] PRIMARY KEY ([OrganizationId]),
CONSTRAINT [FK_Organization_Organization_ParentOrganizationId] FOREIGN KEY ([ParentOrganizationId]) REFERENCES [Organization] ([OrganizationId]) ON DELETE NO ACTION
);
CREATE INDEX [IX_Organization_ParentOrganizationId] ON [Organization] ([ParentOrganizationId]);
?
To factor a foreign key out into a separate table, you just introduce a 1-1 dependent table, where the dependent table's FK and PK are the same as the main table's PK.
So to do the same thing with a separate entity would look like this:
public class Organization
{
public int OrganizationId { get; set; }
public string Name { get; set; }
public int OrganizationTypeId { get; set; }
public OrganizationRelation OrganizationRelation { get; set; }
public ICollection<OrganizationRelation> ChildOrganizations { get; } = new HashSet<OrganizationRelation>();
}
public class OrganizationRelation
{
public int OrganizationId { get; set; }
public int ParentOrganizationId { get; set; }
public Organization Organization { get; set; }
public Organization ParentOrganization { get; set; }
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<OrganizationRelation>().HasKey(c => c.OrganizationId);
builder.Entity<OrganizationRelation>()
.HasOne(r => r.Organization)
.WithOne(o => o.OrganizationRelation)
.HasForeignKey(nameof(OrganizationRelation), nameof(OrganizationRelation.OrganizationId));
builder.Entity<OrganizationRelation>()
.HasOne(r => r.ParentOrganization)
.WithMany(o => o.ChildOrganizations)
.HasForeignKey(r => r.ParentOrganizationId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<OrganizationRelation>().ToTable("OrganizationRelation", "dbo");
base.OnModelCreating(builder);
}
I have created 3 tables using Code First Approach. I get the following Model Validation Exception when i execute a Find on student table.
Student_courses_Target_Student_courses_Source: : The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical.
public class University
{
[Key]
public string Uni_ID { get; set; }
public virtual List<Course> Courses { get; set; }
}
public class Course
{
[Key]
[Column(Order = 1)]
public string Course_ID { get; set; }
[Key,ForeignKey("uni")]
[Column(Order = 2)]
public string Uni_ID { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual University uni { get; set; }
}
public class Student
{
[Key,ForeignKey("course"), Column(Order = 1)]
public string Course_ID { get; set; }
[ForeignKey("course"),Column(Order = 2)]
public string Uni_ID { get; set; }
[Key]
[Column(Order = 3)]
public string Student_ID { get; set; }
public virtual Course course { get; set; }
}
By my understanding , the exception means that i have not mapped my foreign keys in student table to the primary keys in course table. But i have done it . Is there an issue as to how the 'Uni_ID' occurs as Primary key in both University and Course Tables and perhaps i have gone wrong in referencing it as foreign key in the Student table ?
I've got the following model of my Request table:
public class Request
{
[Key]
[Column(Order = 0)]
public int Label_ID { get; set; }
[Key]
[Column(Order = 2)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public DateTime Date { get; set; }
public string Description { get; set; }
public string Memo { get; set; }
public DateTime DeadLine { get; set; }
**[ForeignKey("Label_ID, ID")]
[InverseProperty("Request")]
public int Parent_ID { get; set; }
public virtual Request Parent { get; set; }**
[ForeignKey("Label_ID")]
public virtual Label Label { get; set; }
[ForeignKey("RequestFrom")]
public int RequestFrom_UserID { get; set; }
public virtual ApplicationUser RequestFrom { get; set; }
[ForeignKey("RequestTo")]
public int RequestTo_UserID { get; set; }
public virtual ApplicationUser RequestTo { get; set; }
[ForeignKey("RequestAbout")]
public int? RequestAbout_UserID { get; set; }
public virtual ApplicationUser RequestAbout { get; set; }
public int? Project_ID { get; set; }
[ForeignKey("Label_ID, Project_ID")]
public virtual Project Project { get; set; }
}
}
The Parent_ID must be pointing to another Request by Label_ID and ID (composite key)
After creating a new add-migration I got the following error:
The ForeignKeyAttribute on property 'Parent_ID' on type 'iMaSys.Models.Request' is not valid. The navigation property 'Label_ID, ID' was not found on the dependent type 'iMaSys.Models.Request'. The Name value should be a valid navigation property name.
I know how to successfully point to other tables as you can see in RequestAbout_UserID. That migration was successfull. Only after adding Parent_ID I can't seem to solve this error.
Any help?
Best regards, Janno
You can use multiple ForeignKeyAttributes to refer to one parent, but you should also use the ColumnAttribute to indicate the column order of the key parts. But you've got an auto reference here, so there's a slight problem. The standard way to do this by data annotations would be:
public class Request
{
[Key]
[ForeignKey("Parent")]
[Column(Order = 0)]
public int Label_ID { get; set; }
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey("Parent")]
[Column(Order = 1)] // to refer to ID
public int Parent_ID { get; set; }
public virtual Request Parent { get; set; }
But this throws
The configured column orders for the table 'Requests' contains duplicates. Ensure the specified column order values are distinct.
You can fix this by changing a column index:
public class Request
{
[Key]
[ForeignKey("Parent")]
[Column(Order = 0)]
public int Label_ID { get; set; }
[Key]
[Column(Order = 2)] // Changed
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey("Parent")]
[Column(Order = 1)]
public int Parent_ID { get; set; }
public virtual Request Parent { get; set; }
And EF accepts it. The absolute column indexes don't matter, the relative order is what counts.
Still... I wouldn't like the somewhat obscure feature that column 1 refers to column 2. The code doesn't explain itself. I would prefer fluent mapping:
modelBuilder.Entity<Request>()
.HasRequired(p => p.Parent)
.WithMany()
.HasForeignKey(p => new { p.Label_ID, p.Parent_ID })
So, I am using Entity Framework Code First. This is the model:
public class StockMove
{
public int Id { get; set; }
public int ProductId { get; set; }
public virtual Product Product { get; set; }
}
public class StockMoveOut : StockMove
{
public int CustomerId { get; set; }
public virtual Customer Customer { get; set; }
public int OriginLocalId { get; set; }
public virtual Local OriginLocal { get; set; }
}
public class StockMoveIn : StockMove
{
public int SupplierId { get; set; }
public virtual Supplier Supplier { get; set; }
public int DestinationLocalId { get; set; }
public virtual Local DestinationLocal { get; set; }
}
public class StockMoveTransfer : StockMove
{
public int OriginLocalId { get; set; } //should be the same for StockMoveOut
public virtual Local OriginLocal { get; set; }
public int DestinationLocalId { get; set; } //should be the same for StockMoveIn
public virtual Local DestinationLocal { get; set; }
}
public class DataContext : DbContext
{
public DbSet<StockMove> StockMoves { get; set; }
}
I need to refer the fields (OriginLocalId, DestinationLocalId) in StockMoveTransfer to be the same for the StockMoveIn and StockMoveOut, but Entity Framework expect to new fields.
The SQL DDL generated is:
create table [dbo].[StockMoves] (
[Id] [int] not null identity,
[ProductId] [int] not null,
[CustomerId] [int] null,
[OriginLocalId] [int] null,
[SupplierId] [int] null,
[DestinationLocalId] [int] null,
[OriginLocalId1] [int] null, -- should not exists
[DestinationLocalId1] [int] null, -- should not exists
[Discriminator] [nvarchar](128) not null,
primary key ([Id])
);
Well, how can I configure the Entity Framework to point for the existing fields?
I'm using EF 5.0 (CodeFirst) with VS 2012 and am having trouble making a query using SqlQuery. The problem happens in the mapping between the property name of the entity and the name of the column in the database.
My entity (model):
[Table("Teste")]
public class TesteEntity
{
[Column("Teste_Id")]
public int Id { get; set; }
[Required]
public bool IsAdministrator { get; set; }
[Required]
public string Name { get; set; }
}
When I run the query, I get an error.
Rotine:
List<TesteEntity> list = dataContext.Database.SqlQuery<TesteEntity>("SELECT * FROM Teste").ToList();
Error:
The data reader is incompatible with the specified '....TesteEntity'. A member of the type, 'Id', does not have a corresponding column in the data reader with the same name.
Table structure in database:
CREATE TABLE [dbo].[Teste]( [Id] [int] IDENTITY(1,1) NOT NULL,
[IsAdministrator] [bit] NOT NULL, [Name] [nvarchar](max) COLLATE
Latin1_General_CI_AS NOT NULL, CONSTRAINT [PK_dbo.Teste] PRIMARY KEY
CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, IGNORE_DUP_KEY = OFF)
ON [PRIMARY] ) ON [PRIMARY]
Obviously when I take DataAnnotation [Column ("Teste_Id")] and recreate the table, everything works, but wanted to know if you have to do this query using DataAnnotation [Column ("Teste_Id")].
Thanks
Please, write this code as follow:
[Table("Teste")]
public class TesteEntity
{
[Column("TesteId")]
[Key]
public int Id { get; set; }
[Required]
public bool IsAdministrator { get; set; }
[Required]
public string Name { get; set; }
}
I suggest you to write your code How EF CF work fine. For your code it is as follow:
public class Teste
{
public int TesteId{ get; set; }
[Required]
public bool IsAdministrator { get; set; }
[Required]
public string Name { get; set; }
}