I have two classes:
public class Student
{
public long StudentId { get; set; }
public StudentDetails details { get; set; }
}
public class StudentDetails
{
public long StudentDetailsId { get; set; }
public Student student{ get; set; }
//other properties
}
One student contains one studentdetails. A studentdetails can not exist without a corresponding student.
With these mappings:
public StudentMapping()
{
this.ToTable("Student");
this.HasKey(x => x.StudentId);
this.HasRequired(x => x.details)
.WithRequiredDependent(x => x.student)
.WillCascadeOnDelete(true);
}
public StudentDetailsMapping()
{
this.ToTable("StudentDetails");
this.HasKey(x => x.StudentDetailsId);
this.HasRequired(x => x.student);
}
However, when I go to the database in SQL Management Studio, and do the following: DELETE FROM STUDENTS WHERE StudentId == 1, the Student row gets deleted, but the delete does not cascade to the studentdetails row. What's going wrong? I am trying to get the StudentDetails row to get deleted when I delete it's Student parent object.
Have you checked this SO and this article on MSDN?
As I can see, your model is not a real one-on-one relationship because the two entities you have here doesn't share the same primary key.
When you model a relationship like that you are, in fact, creating a one-to-many table structure in database: how can you prevent that StudentDetailsId will not be in use for another student? I mean, you can enforce it with a business rule, but strictly db speaking there no rule.
If you want to enforce cascade delete with EF in a one-to-one, you need to make something like this:
public class Student
{
public long StudentId { get; set; }
public StudentDetails details { get; set; }
}
public class StudentDetails
{
public long StudentId { get; set; }
public Student student{ get; set; }
//other properties
}
public StudentMapping()
{
this.ToTable("Student");
this.HasKey(x => x.StudentId);
this.HasRequired(x => x.details)
.WithRequiredDependent(x => x.student)
.WillCascadeOnDelete(true);
}
public StudentDetailsMapping()
{
this.ToTable("StudentDetails");
this.HasKey(x => x.StudentId);
this.HasRequired(x => x.student);
}
Hope it helps :)
Related
I have 2 completely different tables with thousand rows of data:
public class Student
{
public Guid StudentUserId { get; set; }
// Fields for students
}
public class Teacher
{
public Guid TeacherUserId { get; set; }
// Fields for teachers
}
public class Player
{
public long Id { get; set; }
// Extra Fields
public Guid PlayerUserId { get; set; }
public Teacher Teacher { get; set; }
public Student Student { get; set; }
// Fields for Player no difference between teacher and student, JUST PLAYER
}
In the Player class, there's a field of type Guid that can store StudentGuid or TeacherGuid.
So I have to connect both StudentUserId and TeacherUserId to PlayerUserId.
So I created a map like this:
public class PlayerMap : EntityTypeConfiguration<Player>
{
public PlayerMap()
{
HasKey(x => x.Id);
//...
HasRequired(x => x.Teacher)
.WithMany()
.HasForeignKey(x => x.PlayerUserId);
HasRequired(x => x.Student)
.WithMany()
.HasForeignKey(x => x.PlayerUserId);
}
}
But I get this error:
the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'.
if this relation is not correct, How can I handle this situation. I can't delete my data and I have to create 3rd table somehow keep previous data.
You cannot have a single foreign key column that refers to one table some times, and to another table at other times.
If you have a situation like this, you need to have two separate foreign key fields - one for student, one for teacher - and make sure at most one of them is filled with a value at any given time.
public class Player
{
public long Id { get; set; }
// Foreign Key for "Teacher"
public Guid? TeacherId { get; set; }
// Foreign Key for "Student"
public Guid? StudentId { get; set; }
public Teacher Teacher { get; set; }
public Student Student { get; set; }
// Fields for Player no difference between teacher and student, JUST PLAYER
}
public class PlayerMap : EntityTypeConfiguration<Player>
{
public PlayerMap()
{
HasKey(x => x.Id);
//...
HasOptional(x => x.Teacher)
.WithMany()
.HasForeignKey(x => x.TeacherId);
HasOptional(x => x.Student)
.WithMany()
.HasForeignKey(x => x.StudentId);
}
}
You cannot have one column as foreign key which is referring to two tables.
Though #Mark answer is answering your question. But you can have a different approach.
As you said that all the fields are same in both tables, so you can have only a single table, which will have a type field, it will tell you type whether its Student or Teacher. You can make Id and Type as composite primary key and refer in the other tables. So you can achieve whatever you want only using one column.
I have 2 entities with a many to many relationship:
[Table("Student", Schema = "School")]
public class Student
{
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
I've specified in the fluent API how to construct the join table as such:
public StudentMap(string schema)
{
//Where schema = "School"
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", schema);
m.MapLeftKey("Student_ID");
m.MapRightKey("Teacher_ID");
});
}
However, when I go to access the Teachers navigation object on the Student, it defaults to the EF convention as opposed to what I've designated for the join table. How would I go about specifying to the SchoolContext that we should be looking at School.StudentsTeachers table instead of dbo.StudentTeachers ?
The problem isn't in the designation of the join table, or the many to many relationship being generated. Those worked fine. The problem arises when attempting to use the Entities, I need a way to specify the relationship should use the Join table I specified, as opposed to the EF naming convention. I was able to resolve a similar issue with EF using its conventions over my table names by using the Table Attribute as shown above. I'm now looking for an equivalent answer except with regards to the many to many join table that exists, but doesn't have an explicit model
I think this should work:
[Table("Student", Schema = "School")]
public class Student
{
[Key,Column("Student_ID")]
public long ID { get; set; }
public virtual List<Teacher> Teachers { get; set; }
//...
}
[Table("Teacher", Schema = "School")]
public class Teacher
{
[Key,Column("Teacher_ID")]
public long ID { get; set; }
public virtual List<Student> Students { get; set; }
//...
}
public StudentMap()
{
HasMany(p => p.Teachers)
.WithMany(p => p.Students)
.Map(m =>
{
m.ToTable("StudentsTeachers", "School");
m.MapLeftKey(p => p.ID);
m.MapRightKey(p => p.ID);
});
}
I have a SQL database (which isn't normalized and doesn't have primary keys, only indexes which I cannot change the schema of) that has a many-to-many relationship.
Reservations can have many Customers and vice-versa. The Reservation table is keyed off ReservationID and TransactionLineNumber. The Customer table has IPCode as its key.
There is a lookup table that is used to create this relationship that has the three keys and nothing else.
Below is my DbContext configuration code:
modelBuilder.Entity<ReservationDetail>()
.HasMany(rd => rd.LinkedCustomers)
.WithMany(c => c.Reservations)
.Map(m =>
{
m.ToTable("r_transaction_detail_ip");
m.MapLeftKey("reservation_id", "transaction_line_number");
m.MapRightKey("ip_number");
});
Here are snippets of the classes for each, and their entity configurations:
Customer
public class Customer
{
public string IpNumber { get; set; }
public string CustomerName { get; set; }
public virtual ICollection<ReservationDetail> Reservations { get; set; }
}
ToTable("ip");
HasKey(c => c.IpNumber);
Property(c => c.IpNumber).HasColumnName("ipcode");
Property(c => c.CustomerName).HasColumnName("displayname");
Reservation
public class ReservationDetail
{
public string ReservationId { get; set; }
public short TransactionLineNumber { get; set; }
...
public virtual ICollection<Customer> LinkedCustomers { get; set; }
}
ToTable("r_transaction_detail");
HasKey(rd => new { rd.ReservationId, rd.TransactionLineNumber });
Property(rd => rd.ReservationId).HasColumnName("reservation_id");
Property(rd => rd.TransactionLineNumber).HasColumnName("transaction_line_number");
Now for the question/problem
At runtime, I am attempting to get a list of reservations, and each of the related customers on a reservation. Running the following example code:
(this is an IQueryable of ReservationDetail) reservationItems.First().LinkedCustomers.First().CustomerName;
throws an exception on the second First() call because LinkedCustomers has no items. Looking at the DB I am connecting to, there should be one entry in this list of Customers.
Any pointers on where my configuration may be bad?
I am trying to split a legacy user table into two entities using a one to one mapping but keep getting migration errors stating that my database is out of sync, even though everything (i think is mapped) and i am trying to make a one-to-one relationship.
This is an existing database (although i am using code first as migrations will become important down the line) but i have not added any changes to the database (although i am unsure what exactly the one-to-one table split expects), i keep getting this:
The model backing the 'Context' context has changed since the database was created. Consider using Code First Migrations to update the database
I can update the database (either manually or via Migrations) but have no idea what is actually out of sync as no new fields have been added and the names match up.
BaseEntity:
public abstract class BaseEntity<T>
{
[Key]
public T Id { get; set; }
public DateTime CreatedOn { get; set; }
}
Membership Model:
public class Membership : BaseEntity<Guid>
{
public string UserName { get; set; }
public bool Approved { get; set; }
public bool Locked { get; set; }
public Profile Profile { get; set; }
}
Profile Model:
public class Profile : BaseEntity<Guid>
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Telephone { get; set; }
public string Extension { get; set; }
public Membership Membership { get; set; }
}
Membership Mapping (this has the 1 to 1 Definition):
public class MembershipMap : EntityTypeConfiguration<Membership>
{
public MembershipMap()
{
//Primary Key
this.HasKey(t => t.Id);
//**Relationship Mappings
this.HasRequired(m => m.Profile)
.WithRequiredPrincipal(p => p.Membership);
//Properties & Column mapping
this.Property(m => m.Id)
.HasColumnName("PKID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(m => m.UserName)
.HasColumnName("Username")
.HasMaxLength(255);
this.Property(m => m.Approved)
.HasColumnName("IsApproved");
this.Property(m => m.Locked)
.HasColumnName("IsLocked");
this.Property(m => m.CreatedOn)
.HasColumnName("CreationDate");
this.ToTable("AppUser");
}
}
Profile Mapping:
public class ProfileMap : EntityTypeConfiguration<Profile>
{
public ProfileMap()
{
//Primary Key
this.HasKey(t => t.Id);
//Properties & Column mapping
this.Property(m => m.Id)
.HasColumnName("PKID")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(m => m.FirstName)
.HasColumnName("FirstName");
this.Property(m => m.LastName)
.HasColumnName("LastName");
this.Property(m => m.Email)
.HasColumnName("Email");
this.Property(m => m.Telephone)
.HasColumnName("Telephone");
this.Property(m => m.Extension)
.HasColumnName("Extension");
this.ToTable("AppUser");
}
}
Database Table
I know that not all fields are mapped, but i do not need them at this stage, surely that wouldn't be the issue would it?
Issue was not Code First Mappings but caused by me switching databases and some rouge migrations coming into play.
To reset migrations you can see the answer here from a follow up question:
Resetting Context for Entity Framework 5 so it thinks its working with a initialised Database - Code First
Kudos to EvilBHonda
I have the following entities
public abstract class Card
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
public virtual Product Product { get; set; }
public virtual Sprint Sprint { get; set; }
}
public class Story:Card
{
public virtual double Points { get; set; }
public virtual int Priority { get; set; }
}
public class Product
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Story> Stories { get; private set; }
public Product()
{
Stories = new List<Story>();
}
}
And the following mappings
public class CardMap:ClassMap<Card>
{
public CardMap()
{
Id(c => c.Id)
.Index("Card_Id");
Map(c => c.Name)
.Length(50)
.Not.Nullable();
Map(c => c.Description)
.Length(1024)
.Not.Nullable();
References(c=>c.Product)
.Not.Nullable();
References(c=>c.Sprint)
.Nullable();
}
}
public class StoryMap : SubclassMap<Story>
{
public StoryMap()
{
Map(s => s.Points);
Map(s => s.Priority);
}
}
public class ProductMap:ClassMap<Product>
{
public ProductMap()
{
Id(p => p.Id)
.Index("Product_Id");
Map(p => p.Name)
.Length(50)
.Not.Nullable();
HasMany(p => p.Stories)
.Inverse();
}
When I generate my Schema, the tables are created as follows
Card
---------
Id
Name
Description
Product_id
Sprint_id
Story
------------
Card_id
Points
Priority
Product_id
Sprint_id
What I would have expected would have been to see the columns Product_id and Sprint_id ONLY in the Card table, not the Story table.
What am I doing wrong or misunderstanding?
NB: Tested on the NH2 project only
Well, you are probably going to want to chew on a door once you read this, but the TLDR reason is because the Product_id and Spring_id columns in your Story table are not redundant - they exist for the HasMany(x => x.Stories) relations in your SpringMap and ProductMap. They just happen to be share the same naming convention as the CardMap References(x => x.Product and References(x => x.Sprint).
Validate this for yourself by commenting out ProductMap.cs:24-25 and SprintMap.cs:22 and rebuilding.
If the above does not make sense, let me know and I will try to explain in further detail.
So, it should work fine as is. If you want to clarify the columns, you could explicitly define the column names like so:
ProductMap.cs
HasMany(p => p.Stories)
.KeyColumn("ProductOwner_id")
.Inverse();
SprintMap.cs
HasMany(s => s.Stories)
.KeyColumn("SprintOwner_id")
;
CardMap.cs
References(c=>c.Product)
.Column("Product_id")
.Not.Nullable();
References(c=>c.Sprint)
.Column("Sprint_id")
.Nullable();
Here I am guessing that the 1:N relationships between a Story and a Product/Sprint are an "owner". You would want to rename it to whatever is appropriate semantically.
One other thing. I would have thought the last changes (the changes to CardMap.cs) would be unnecessary - but they seem to be for some reason, or the Sprint_id column becomes SprintOwner_id. I have no idea why this would happen - I would speculate that this is some sort of bidirectional relationship inferencing on fluent/nhibernates part gone awry, but I'd put very little money on that.
I see that the Story entity inherits from the Card entity you created, but you don't know why you have Product_Id and Sprint_Id properties in the Story table Schema, since they're virtual properties in the Card class.
I'm guessing that this happens because in NHibernate, all properties need to be virtual but only at first. They don't really stay virtual. The NHibernate framework overrides them, and probably because of this, this is happening to you.