EF4 Mapping one-to-many on existing database without navigation - entity-framework

I'm using ModelBuilder to map an existing database to POCOs. I have courses, students, and meetings. Here are the tables
CREATE TABLE Courses (
CourseID int,
Name string)
CREATE TABLE Students(
StudentID int,
Name string)
CREATE TABLE Courses_Students (
CourseID int,
StudentID int)
CREATE TABLE Meetings (
MeetingID int,
CourseID int,
MeetDate datetime)
And the POCOs
public class Course {
public int CourseID { get; set; }
public string Name { get; set; }
public virtual ICollection<CourseMeeting> Meetings { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
public class Student {
public int StudentID { get; set; }
public string Name { get; set; }
}
public class Meeting {
public int MeetingID { get; set; }
public int CourseID { get; set; }
public DateTime MeetDate { get; set; }
}
The table mapping works great:
modelBuilder.Entity<Course>().MapSingleType().ToTable("Courses");
modelBuilder.Entity<Student>().MapSingleType().ToTable("Students");
modelBuilder.Entity<Meeting>().MapSingleType().ToTable("Meetings");
And the many-to-many mapping with a join table and without a navigation property works (i.e. there is no Students.Courses property specified on WithMany())
modelBuilder.Entity<Course>()
.HasMany(c => c.Students)
.WithMany()
.Map(StoreTableName.FromString("Courses_Students"),
(c, s) => new { CourseID = c.CourseID, StudentID = s.StudentID});
But I'm having trouble mapping the other relationship that doesn't have a join table. This obviously isn't right:
modelBuilder.Entity<Course>().HasMany(c => c.Meetings).WithMany();
Because it wants a join table: Invalid object name 'dbo.Course_Meetings'. I can add a Course property to the Meeting object and then use
modelBuilder.Entity<Course>()
.HasMany(c => c.Meetings)
.WithOptional(m => m.Course)
.HasConstraint((c, m) => c.CoursID == me.CourseID);
But I'd like to do this without the navigation property. Is this possible with EF4 and an existing database?

It's assuming it needs the join table (and thus looking for it) because you haven't mapped the property in the original declaration.
Try manually mapping the properties on the actual table like this..
public class Meeting {
public int MeetingID { get; set; }
public int CourseID { get; set; }
public DateTime MeetDate { get; set; }
public Course { get; set; }
}
and then configure it as follows:
modelBuilder.Entity<Meeting>(m => new {
MeetingId = m.Meeting,
MeetDate = m.MeetDate,
CourseId = m.Course.Id
})
.HasRequired(m => m.Course)
.WithMany()

Related

Non-Standard Composite Relationship in EF Core

I have a 3rd party database I'm trying to integrate with EF Core and it uses strange composite columns instead of foreign keys. A relationship is defined by a "Parent_Table_Name" and "Parent_Id". They're not keys, just an nchar(20) that contains the table name and an int with the ID in that table respectively. Is there any way I can establish navigation properties between tables if they don't have foreign keys?
The provider is Microsoft SQL Server. Here are CREATE statements that describe the situation.
CREATE TABLE [ParentA] (Table_Name nchar(20), Id int)
CREATE TABLE [ParentB] (Table_Name nchar(20), Id int)
CREATE TABLE [ChildC] (Table_Name nchar(20), Id int, Parent_Table_Name nchar(20), Parent_Table_Id int)
Here is an image of some sample data given the above tables.
These are known as a polymorphic associations (feature request #757). There is currently no first-class support for it, but you can work around it:
class ParentA
{
public string Table_Name { get; set; }
public int Id { get; set; }
public ICollection<ChildC> ChildCs { get; set; }
}
class ParentB
{
public string Table_Name { get; set; }
public int Id { get; set; }
public ICollection<ChildC> ChildCs { get; set; }
}
class ChildC
{
public string Table_Name { get; set; }
public int Id { get; set; }
public int? ParentA_Id { get; set; }
public ParentA ParentA { get; set; }
public int? ParentB_Id { get; set; }
public ParentB ParentB { get; set; }
}
var childrenWithParents = dbContext.ChildCs
// TODO: You could also create a view in the database
// or use ToSqlQuery on modelBuilder
.FromSql(#"
SELECT Table_Name,
Id,
CASE
WHEN Parent_Table_Name = 'ParentA'
THEN Parent_Table_Id
END AS ParentA_Id,
CASE
WHEN Parent_Table_Name = 'ParentB'
THEN Parent_Table_Id
END AS ParentB_Id
FROM ChildC")
.Include(c => c.ParentA)
.Include(c => c.ParentB)
.ToList();

EF Core Fluent API Null Foreign Key

I have the following tables : Products, Users and ProductApproval. Whenever user create a new product, the ProductApproval will have a new record with the product ID and a null ApprovedByUserId because it is not approve yet. User can have many ProductApproval but Product only can have one ProductApproval.
The structure is like this:
User.cs
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public virtual ICollection<ProductApproval> ProductApprovals { get; set; }
public User()
{
ProductApprovals = new Collection<ProductApproval>();
}
}
Product.cs
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ProductApproval ProductApproval { get; set; }
}
ProductApproval.cs
public class ProductApproval
{
public int Id { get; set; }
public virtual Product Product { get; set; }
public int? ProductId { get; set; }
public virtual User User { get ;set; }
public int? ApprovedByUserId { get; set; }
}
DataContext.cs
modelBuilder.Entity<User>()
.HasMany(u => u.ProductApprovals)
.WithOne(pa => pa.User)
.HasForeignKey(pa => pa.ApprovedByUserId)
.IsRequired(false);
modelBuilder.Entity<Product>()
.HasOne(p => p.ProductApproval)
.WithOne(pa => pa.Product)
.HasForeignKey<ProductApproval>(pa => pa.ProductId);
I already declared the foreign key ApprovedByUserId as nullable, but i still get the following error when i insert a record into productapprovals :
MySqlException: Cannot add or update a child row: a foreign key constraint fails (mysite.productapprovals, CONSTRAINT FK_ProductApprovals_Users_ApprovedByUserId FOREIGN KEY (ApprovedByUserId) REFERENCES users (Id) ON DELETE RESTRICT)
Please advice is there any place i did wrong

EF CF how to create one table that has three foreign keys

I have in my model Student that have a collection of all his subjects and every subject have collection of Educational matches.
public class Subject
{
public int SubjectID { get; set; }
public string SubjectName {get; set; }
public ICollection<Student> { get; set; }
}
public class EducationalMatches
{
public int EducationalMatchesID { get; set; }
public int Number { get; set; }
public string Name { get; set; }
}
public class Student
{
public int StudentID { get; set; }
public Icollection<AllStudentSubjects> AllStudentSubjects{ get; set; }
}
public class AllStudentSubject
{
public int AllStudentSubjectID { get; set; }
public Subject Subject { get; set; }
public ICollection<EducationalMatches> Educations { get; set; }
}
I'm expecting that in DB a table that looks like that will appear:
tableID
StudentID
SubjectID
EducationMatchesID
but no such table appears.
anyone have an idea?
Having a model is not enough, you need to override OnModelCreating method (it is empty by default). Plus EF wants to have 'reverse' property, for example, if Student has a collection of Subjects, Subject should have a collection of Students (for many-to-many relationship)
In your case for AllStudentSubject it should be like this (did not test)
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<AllStudentSubject>()
.HasKey(a => a.AllStudentSubjectID ) //Primary key, I prefer [Key] attribute, but this also works
.HasRequired(a => a.Student) //Property in AllStudentSubject
.WithMany(s => s.AllStudentSubjects) // Collection property in Student class
.HasForeignKey(a => a.StudentId)//Second property in AllStudentSubject
//For Student, you do not have to write this all again, just the primary key
modelBuilder.Entity<Student>()
.HasKey(a => a.StudentId ) //I like to move 'simple' declarations like this to the top
}
For other two entities you have to do the same.
Here`s a great article with all concepts explained

Many-to-many relationship on the same table using a junction table and a primary key in EF

I have the following tables:
Sub_Option: Sub_Option_ID as PK, Name
Sub_Option_To_Sub_Option: Sub_Option_To_Sub_Option_ID as PK, Sub_Option_ID_Primary, Sub_Option_ID_Secondary
I would like to be able to access all the secondary sub options associated with the primary sub option via EF and vice-versa. Directly using .Map won't work as the junction table Sub_Option_To_Sub_Option has a primary key.
public class Sub_Option
{
public int Sub_Option_ID { get; set; }
public string Name { get; set; }
}
corresponding to Table
CREATE TABLE Sub_Option(
Sub_Option_ID int,
Name varchar(255)
);
and Table
CREATE TABLE Sub_Option_To_Sub_Option(
Sub_Option_To_Sub_Option int PK,
Sub_Option_ID_Primary int,
Sub_Option_ID_Secondary int
);
This should work i think:
public class OptionToOption
{
[Key]
public int ID { get; set; }
[ForeignKey("PrimaryOption")]
public int PrimaryID { get; set; }
[ForeignKey("SecondaryOption")]
public int SecondaryID { get; set; }
public virtual Option PrimaryOption { get; set; }
public virtual Option SecondaryOption { get; set; }
}
public class Option
{
public Option()
{
OptionToOption = new HashSet<OptionToOption>();
}
[Key]
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<OptionToOption> OptionToOption { get; set; }
}
And in fluent api map like this (don't even think it's necessary to do this though):
modelBuilder.Entity<Option>()
.HasMany(e => e.OptionToOption)
.WithRequired(e => e.PrimaryOption)
.HasForeignKey(e => e.PrimaryID);
modelBuilder.Entity<Option>()
.HasMany(e => e.OptionToOption)
.WithRequired(e => e.SecondaryOption)
.HasForeignKey(e => e.SecondaryID);

Entity Framework's DbModel: How to map a one to many relationship using a connection table?

I'm trying to map via DbModel this relationship present on the database.
CREATE TABLE core.Institutes
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
OldID INT NULL
)
GO
CREATE TABLE core.InstitutePlaces
(
FKInstituteID INT NOT NULL PRIMARY KEY REFERENCES core.Institutes(ID),
FKPlaceID INT NOT NULL REFERENCES core.Places(ID)
)
GO
CREATE TABLE core.Places
(
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Name NVARCHAR(128) NOT NULL,
FKParentID INT NULL REFERENCES core.Places(ID),
OldID INT NULL
)
GO
on this model
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
public int Id { get; set; }
public string Name { get; set; }
public Place Place { get; set; }
}
we're using something like this to do the mapping
modelBuilder.Entity<Institutes.Institute>().HasOptional(i => i.Place);
but it doesn't work :(
This scenario is perfectly managed by the EDML file, so the problem is only about the mapping.
Something like this will give you (almost) the desired schema with one caveat: Code First does not create a 1:1 relationship in entity splitting scenarios which your desired schema (creating a 1:* association using a join table) is a special case of it.
public class Place
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public Place Parent { get; set; }
}
public class Institute
{
[DatabaseGenerated(DatabaseGenerationOption.None)]
public int Id { get; set; }
public string Name { get; set; }
public int? PlaceId { get; set; }
public Place Place { get; set; }
}
public class Context : DbContext
{
public DbSet<Place> Places { get; set; }
public DbSet<Institute> Institutes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Institute>().Map(mc =>
{
mc.Properties(p => new { p.Id, p.Name });
mc.ToTable("Institutes");
})
.Map(mc =>
{
mc.Properties(p => new { p.Id, p.PlaceId });
mc.ToTable("InstitutePlaces");
});
modelBuilder.Entity<Place>()
.HasOptional(p => p.Parent)
.WithMany()
.HasForeignKey(p => p.ParentId);
}
}
I had to switch off identity generation due to a bug that I explained here.