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();
Related
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
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);
I have 3 Entities that have many combinations on my site.
I would to create the follow hierarchy:
Each User have collection of UserRoles.
Each UserRole have a fixed collection of PermissionRecords
Each PermissionRecord have a fild of PermissionRecordPrivileges that varies from user to user.
I would like to get the user's privileges (getting the permissionRecord and UserRole collection is quite trivial).
As I understand, I need to create a table that merges the following data:
UserId, PermissionRecordId, PermissionPrivilegesId (3 Foreign keys that create primary key)
How can I do this using EF 5 ( or earlier)?
The code:
public class BaseEntity
{
public int Id { get; set; }
}
public class User:BaseEntity
{
public virtual ICollection<UserRole> UserRoles{get;set;}
}
public class UserRole:BaseEntity
{
public ICollection<PermissionRecord> PermissionRecords { get; set; }
}
public class PermissionRecord : BaseEntity
{
public PermissionRecordPrivileges Privileges { get; set; }
}
public class PermissionRecordPrivileges : BaseEntity
{
public bool Create { get; set; }
public bool Read { get; set; }
public bool Update { get; set; }
public bool Delete { get; set; }
}
Your terminology "create a table" is a bit confusing. A table is a database object. I assume you mean a data structure client-side. To collect a User's privileges you can do:
var privileges = (from u in context.Users
from ur in u.UserRoles
from pr in ur.PermissionRecords
where u.UserId = id
select ur.Privileges).Distinct();
where id is a variable containing a User's id.
You have to create your entity model classes like below.
Note : Need to maintain Conventions(naming and singular/plural) when you use Entity framework Code First.Like below :
Entity Models
public class UserRole
{
public int Id { get; set; }
public virtual ICollection<PermissionRecord> PermissionRecords { get; set; }
}
public class PermissionRecord
{
public int Id { get; set; }
public virtual PermissionRecordPrivilege PermissionRecordPrivilege { get; set; }
}
public class PermissionRecordPrivilege
{
public int Id { get; set; }
public bool Create { get; set; }
public bool Read { get; set; }
public bool Update { get; set; }
public bool Delete { get; set; }
}
Your Tables Should like below :
Tables
PermissionRecordPrivileges
Id int NotNull
Create bit AllowNull
Read bit AllowNull
Update bit AllowNull
Delete bit AllowNull
PermissionRecords
Id int NotNull
PermissionRecordPrivilege_Id int NotNull
UserRole_Id int NotNull
UserRoles
Id int NotNull
I hope this will help to you.Good Luck.
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.
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()