I am trying to generated an entity framework code first model from an existing database (without changing the database schema). This database has been used in the past to generate edmx models and I am trying to achieve the equivalent model using Fluent Api or data annotations.
The relationship I have been unable to reproduce is 0..1 to many using a join table (not a nullable foreign key).
So it would look something like this:
TableA
{
ID (PrimaryKey)
TableB (0 or 1)
}
JoinTable
{
TableA_FK (PrimaryKey, ForeignKey),
TableB_FK (ForeignKey)
}
TableB
{
ID (PrimaryKey)
TableAs (Many)
}
Is this achievable in the code first style or will I have to generate an edmx model in order to use this database in EF without changing its schema?
Many thanks,
Phil
Here is an example without using a JoinTable class. The join table is configured through the fluent api.
class DataContext : DbContext
{
public DataContext(string connectionString)
: base(connectionString)
{ }
public DbSet<TableA> TableA { get; set; }
public DbSet<TableB> TableB { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<TableA>().ToTable("TableA");
modelBuilder.Entity<TableB>().ToTable("TableB");
modelBuilder.Entity<TableB>()
.HasMany(x => x.TableAs)
.WithMany()
.Map(m =>
{
m.ToTable("JoinTable");
m.MapLeftKey("TableA_FK");
m.MapRightKey("TableB_FK");
});
}
}
class TableA
{
public int ID { get; set; }
public TableB TableB { get; set; }
}
class TableB
{
public int ID { get; set; }
public ICollection<TableA> TableAs { get; set; }
}
This will generate the following migration script, which looks like the schema you have.
public override void Up()
{
CreateTable(
"dbo.TableA",
c => new
{
ID = c.Int(nullable: false, identity: true),
TableB_ID = c.Int(),
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.TableB", t => t.TableB_ID)
.Index(t => t.TableB_ID);
CreateTable(
"dbo.TableB",
c => new
{
ID = c.Int(nullable: false, identity: true),
})
.PrimaryKey(t => t.ID);
CreateTable(
"dbo.JoinTable",
c => new
{
TableA_FK = c.Int(nullable: false),
TableB_FK = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.TableA_FK, t.TableB_FK })
.ForeignKey("dbo.TableB", t => t.TableA_FK, cascadeDelete: true)
.ForeignKey("dbo.TableA", t => t.TableB_FK, cascadeDelete: true)
.Index(t => t.TableA_FK)
.Index(t => t.TableB_FK);
}
If I've understood correctly, the following code using only data annotations should create your model.
public class TableA
{
public int ID { get; set; }
public JoinTable JoinTable { get; set; }
}
public class TableB
{
public int ID { get; set; }
public List<JoinTable> JoinTables{ get; set; }
}
public class JoinTable
{
[Key, ForeignKey("TableA")]
public int TableA_FK { get; set; }
[ForeignKey("TableB")]
public int TableB_FK { get; set; }
public TableA TableA { get; set; }
public TableB TableB { get; set; }
}
Interestingly, EF does not perform a round trip back to the original, if you generate the code-first models from the database model that this code creates then EF simplifies the model and removes the join table and creates a nullable foreign key.
Let me know if this works.
I may be wrong, but I believe you're missing some concepts here...
Why you have a JoinTable if it's doesn't have any column besides its foreign keys? It doesn't make sense... IHMO a nullable foreign key in TableA would be correct way.
When you work with Code-First it means that everything in your database will be represented by CODE. There's no reason to have a table in your database but not in your code...
EDMX handles that relationship because it uses "Associations" https://msdn.microsoft.com/en-us/data/jj713299#Overview
...backing to the code-first, you can represent your database like this:
public class JoinTable
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int TableA_FK { get; set; }
public int TableB_FK { get; set; }
//a future property here
public virtual TableA TableA { get; set; }
public virtual TableB TableB { get; set; }
}
public partial class TableA
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TableAId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual JoinTable JoinTable { get; set; }
}
public partial class TableB
{
public TableB()
{
JoinTable = new HashSet<JoinTable>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int TableBId { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual ICollection<JoinTable> JoinTable { get; set; }
}
}
public partial class Model1 : DbContext
{
public Model1()
: base("name=Model1")
{
}
public virtual DbSet<JoinTable> JoinTable { get; set; }
public virtual DbSet<TableA> TableA { get; set; }
public virtual DbSet<TableB> TableB { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TableA>()
.HasOptional(e => e.JoinTable)
.WithRequired(e => e.TableA);
modelBuilder.Entity<TableB>()
.HasMany(e => e.JoinTable)
.WithRequired(e => e.TableB)
.HasForeignKey(e => e.TableB_FK)
.WillCascadeOnDelete(false);
}
}
Related
I am quite new to EF Core 6.0. We currently have a projet to upgrade, we cannot change the actual tables (use by another program) so we use Database fisrt approch.
So I need to add some Permission on user (the database are in french) We curently have an UsagerEW table (user table) and we add an Permission Table and an joint table PermissionUsagerEW for the Many2Many. After doing Scaffold-dbContect here is the result:
UsagerEW (primary key is Code_Int)
public partial class UsagerEW
{
public UsagerEW()
{
PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
RefreshToken = new HashSet<RefreshToken>();
}
public string Code { get; set; }
public string Nom { get; set; }
public string Email { get; set; }
public string ModeLogin { get; set; }
public string PasswordTemp { get; set; }
public DateTime? PasswordTempExp { get; set; }
public int code_int { get; set; }
public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }
}
Pemrssion and PermissionUsagerEW
public partial class Permission
{
public Permission()
{
PermissionUsagerEW = new HashSet<PermissionUsagerEW>();
}
public int id { get; set; }
public string code { get; set; }
public string description { get; set; }
public int? moduleId { get; set; }
public virtual Module module { get; set; }
public virtual ICollection<PermissionUsagerEW> PermissionUsagerEW { get; set; }
}
public partial class PermissionUsagerEW
{
public int id { get; set; }
public int permissionId { get; set; }
public int usagerCodeInt { get; set; }
public virtual Permission permission { get; set; }
public virtual UsagerEW usagerCodeIntNavigation { get; set; }
}
That compile and I can "navigate with include" from UsagerEW and get an list of PermissionUsagerEW for a specific UsagerEW.
Now like I am in EF COre 6.0 that supposed to support Many2Many
I add this nav propertie in the Permnission class
public virtual ICollection<UsagerEW> UsagerEW { get; set; }
and this in the UsagerEW class:
public virtual ICollection<Permission> Permission { get; set; }
But I got execution error either I just try to load some user wintout any include:
UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).SingleOrDefault();
System.InvalidOperationException: 'Cannot use table
'PermissionUsagerEW' for entity type 'PermissionUsagerEW
(Dictionary<string, object>)' since it is being used for entity type
'PermissionUsagerEW' and potentially other entity types, but there is
no linking relationship. Add a foreign key to 'PermissionUsagerEW
(Dictionary<string, object>)' on the primary key properties and
pointing to the primary key on another entity type mapped to
'PermissionUsagerEW'.'
The FK are detect by the scaffold:
modelBuilder.Entity<PermissionUsagerEW>(entity =>
{
entity.HasOne(d => d.permission)
.WithMany(p => p.PermissionUsagerEW)
.HasForeignKey(d => d.permissionId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_PermissionUsager_Permission");
entity.HasOne(d => d.usagerCodeIntNavigation)
.WithMany(p => p.PermissionUsagerEW)
.HasForeignKey(d => d.usagerCodeInt)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_PermissionUsager_Usager");
});
Any idea?
---EDIT 1
I change your code to reflect the scaffolded PermissionUsagerEW table:
//--UsagewrEW
modelBuilder.Entity<UsagerEW>()
.HasKey(u => u.code_int);
modelBuilder.Entity<UsagerEW>()
.HasMany(u => u.Permissions)
.WithMany(p => p.Users)
.UsingEntity<PermissionUsagerEW>(
p => p.HasOne(e => e.permission)
.WithMany()
.HasForeignKey(e => e.permissionId),
p => p.HasOne(p => p.usagerCodeIntNavigation)
.WithMany()
.HasForeignKey(e => e.usagerCodeInt)
);
modelBuilder.Entity<PermissionUsagerEW>()
.HasOne(p => p.usagerCodeIntNavigation)
.WithMany()
.HasForeignKey(p => p.usagerCodeInt);
When testing with
UsagerEW user = _EWContext.UsagerEW.Where(u=>u.Code == usagerId).Include(u => u.Permissions).SingleOrDefault();
Now I got this error:
Microsoft.Data.SqlClient.SqlException: 'Invalid column name
'UsagerEWcode_int'.'
I think EF tries to link something automatically. I do not have any UsagerEWcode_int in my solution.
EDIT2:
There is the SQL generated. Wierd column name and some repetition...
SELECT [u].[code_int], [u].[Administrateur], [u].[Code], [u].[Email], [u].[EmpContact], [u].[Inactif], [u].[KelvinConfig], [u].[LectureSeule], [u].[ModeLogin], [u].[Nom], [u].[ParamRole], [u].[Password], [u].[PasswordTemp], [u].[PasswordTempExp], [u].[RestreintCommContrat], [u].[RestreintProjet], [u].[Role], [u].[UsagerAD], [u].[doitChangerPW], [u].[estSuperviseur], [u].[idSuperviseur], [u].[infoSession], [u].[paramRole2], [u].[permsGrps], [t].[id], [t].[Permissionid], [t].[UsagerEWcode_int], [t].[permissionId0], [t].[usagerCodeInt], [t].[id0], [t].[code], [t].[description], [t].[moduleId]
FROM [UsagerEW] AS [u]
LEFT JOIN (
SELECT [p].[id], [p].[Permissionid], [p].[UsagerEWcode_int], [p].[permissionId] AS [permissionId0], [p].[usagerCodeInt], [p0].[id] AS [id0], [p0].[code], [p0].[description], [p0].[moduleId]
FROM [PermissionUsagerEW] AS [p]
INNER JOIN [Permission] AS [p0] ON [p].[permissionId] = [p0].[id]
) AS [t] ON [u].[code_int] = [t].[usagerCodeInt]
WHERE [u].[Code] = #__usagerId_0
ORDER BY [u].[code_int], [t].[id]
You can configure direct Many-to-Many relationships with an existing database, and you can have the linking entity in the model or exclude it. There are several examples in the docs. And you can leave the foreign key properties in the model, or you can replace them with shadow properties. But the Scaffolding code doesn't do any of this for you. It creates the simplest correct model for the database schema.
Also you usually should rename the entities and properties to align with .NET coding conventions.
Anyway something like this should work:
public partial class UsagerEW
{
public string Code { get; set; }
public string Nom { get; set; }
public string Email { get; set; }
public string ModeLogin { get; set; }
public string PasswordTemp { get; set; }
public DateTime? PasswordTempExp { get; set; }
public int code_int { get; set; }
public virtual ICollection<Permission> Permissions { get; } = new HashSet<Permission>();
}
public partial class Permission
{
public int Id { get; set; }
public string Code { get; set; }
public string Description { get; set; }
public int? ModuleId { get; set; }
//public virtual Module module { get; set; }
public virtual ICollection<UsagerEW> Users { get; } = new HashSet<UsagerEW>();
}
public partial class PermissionUsagerEW
{
public int Id { get; set; }
public int PermissionId { get; set; }
public int UsagerCodeInt { get; set; }
public virtual Permission Permission { get; set; }
public virtual UsagerEW User { get; set; }
}
public class Db : DbContext
{
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<UsagerEW>()
.HasKey(u => u.code_int);
builder.Entity<UsagerEW>()
.HasMany(u => u.Permissions)
.WithMany(p => p.Users)
.UsingEntity<PermissionUsagerEW>(
p => p.HasOne(e => e.Permission)
.WithMany()
.HasForeignKey(e => e.PermissionId),
p => p.HasOne(p => p.User)
.WithMany()
.HasForeignKey( e => e.UsagerCodeInt)
);
builder.Entity<PermissionUsagerEW>()
.HasOne(p => p.User)
.WithMany()
.HasForeignKey(p => p.UsagerCodeInt);
foreach (var prop in builder.Model.GetEntityTypes().SelectMany(e => e.GetProperties()))
{
prop.SetColumnName(char.ToLower(prop.Name[0]) + prop.Name.Substring(1));
}
base.OnModelCreating(builder);
}
But when you're working in a database-first workflow, there's a downside to deeply customizing the EF model: you loose the ability to regenerate the EF model from the database.
So you can use a "nice" customized EF model, or a "plain" scaffolded model. If you customize the model, you can no longer regenerate it, and need to alter it to match future database changes by hand.
You can apply some customizations, though, like the convention-based property-to-column and entity-to-table mappings in the example. But changing the generated "indirect many-to-many" to "direct many-to-many" will prevent you from regenerating the EF model through scaffolding.
My entity AppUser has an optional UserProfile, and UserProfile as a required AppUser. I would like to have a foreign key to each other.
public class AppUser
{
public int Id { get; set; }
public string Name { get; set; }
public UserProfile UserProfile { get; set; }
public int? UserProfileId { get; set; }
}
public class UserProfile
{
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public AppUser AppUser { get; set; }
public int AppUserId { get; set; }
}
I got this mapping:
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
This generate the following migration. I notice there is no foreign key from AppUser to UserProfile. Also the foreignkey in UserProfile is defined on UserProfile.Id ... I want it on UserProfile.AppUserId.
public override void Up()
{
CreateTable(
"dbo.AppUsers",
c => new
{
Id = c.Int(nullable: false, identity: true),
Name = c.String(),
UserProfileId = c.Int(),
})
.PrimaryKey(t => t.Id);
CreateTable(
"dbo.UserProfiles",
c => new
{
Id = c.Int(nullable: false),
SomeUserProfileValue = c.String(),
AppUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.Id)
.ForeignKey("dbo.AppUsers", t => t.Id)
.Index(t => t.Id);
}
So I tried to change the mapping configuration as follow
modelBuilder.Entity<AppUser>().HasOptional(x => x.UserProfile).WithRequired(x => x.AppUser)
.Map(c => c.MapKey("AppUserId"));
But now when I try to add the migration i get the error:
AppUserId: Name: Each property name in a type must be unique. Property name 'AppUserId' is already defined.
This seems to complain that I have a field AppUserId already defined in my model.
This is how we define our entities, we always include both the class and the id fields, gives more flexibility as to which to use under different circumstances.
So I'm a bit stuck here... is there any way to have this 1:1 bidirectional relation while having both class and the id fields defined in the model ?
And why there is no nullable foreign key generated in the AppUser table ?
I've generally found better results with DataAnnotations, myself. So:
public class AppUser
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int? UserProfileId { get; set; }
[ForeignKey = "UserProfileId"]
public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
[Key]
public int Id { get; set; }
public string SomeUserProfileValue { get; set; }
public int AppUserId { get; set; }
[ForeignKey = "AppUserId"]
public AppUser AppUser { get; set; }
}
public class Admin : EntityTypeConfiguration<Admin>
{
//[ForeignKey("Blog")] -- If I enable this, it compiles
public int AdminId { get; set; }
public string AdminName { get; set; }
public string AdminPicture { get; set; }
//[Required] -- Or If I enable this, it compiles
public virtual Blog Blog { get; set; }
}
public class Blog : EntityTypeConfiguration<Blog>
{
public int BlogId { get; set; }
public string BlogName { get; set; }
public string BlogUrl { get; set; }
public virtual Admin Admin { get; set; }
public Blog()
{
HasRequired(a => a.Admin).WithRequiredPrincipal(b=>b.Blog);
}
}
As long as I am defining HasRequired and WithRequiredPrincipal keys, why VS still creates below error.
Unable to determine the principal end of an association between the types 'Dummy.Models.Blog' and 'Dummy.Models.Admin'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Second thing is, even I enable [Required] or [ForeingKey] attr., in edmx designer, I only see 1 - 0..1 But I must see 1 - 1 (both end required)
1-1 relationship is not possible at database level, because you can't insert two rows at the same time. 1-1 is only possible at class validation level.
To make a 1-1 relationship, the primary key of the dependant entity must be the foreign key of the principal entity; that's the only way to make a 1-1 relationship. So, you have to make the following changes (considering that you are using EF Code First):
public class Admin
{
public int AdminId { get; set; }
public string AdminName { get; set; }
public string AdminPicture { get; set; }
public virtual Blog Blog { get; set; }
}
Blog should not have its own BlogId, because a blog belongs to an admin, and the admin can have only one blog (1-1 relationship). If you create a BlogId, with an AdminId FK, you would be making a 1-n relationship. Furthermore, do not mix the entity class with the mapping class, they should be different things. See the example below:
public class Blog
{
public int AdminId { get; set; } //PK AND FK
public string BlogName { get; set; }
public string BlogUrl { get; set; }
public virtual Admin Admin { get; set; }
}
Creating the relationship with a mapping class:
public class BlogMapping : EntityTypeConfiguration<Blog>
{
public BlogMapping()
{
HasKey(i => i.AdminId);
HasRequired(a => a.Admin)
.WithRequiredDependent(i => i.Blog);
}
}
Register the mapping inside the dbContext class:
public class MyDbContext : DbContext
{
public DbSet<Admin> Admins { get; set; }
public DbSet<Blog> Blogs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new BlogMapping());
}
}
This will generate the following migration:
CreateTable(
"dbo.Admins",
c => new
{
AdminId = c.Int(nullable: false, identity: true),
AdminName = c.String(),
AdminPicture = c.String(),
})
.PrimaryKey(t => t.AdminId);
CreateTable(
"dbo.Blogs",
c => new
{
AdminId = c.Int(nullable: false),
BlogName = c.String(),
BlogUrl = c.String(),
})
.PrimaryKey(t => t.AdminId)
.ForeignKey("dbo.Admins", t => t.AdminId)
.Index(t => t.AdminId);
Hope this helps!
I have 4 tables:
User table
public enum SEX { Male, Female }
public abstract class User
{
public int UserID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public SEX Sex { get; set; }
}
Doctor table inherites from User
[Table("Doctor")]
public class Doctor : User
{
public string Department { get; set; }
public string Occupation { get; set; }
public string CabinetNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
Patient table inherites from User
[Table("Patient")]
public class Patient : User
{
public int InsuranceNumber { get; set; }
public int CardNumber { get; set; }
public virtual List<Treat> Treats { get; set; }
}
public class Treat
{
public int TreatId { get; set; }
public int DoctorUserId { get; set; }
public int PatientUserId { get; set; }
public virtual Doctor Doctor { get; set; }
public virtual Patient Patient { get; set; }
}
public class HospitalContext: DbContext
{
public HospitalContext() : base("DBConnectionString") {
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<HospitalContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Doctor)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.DoctorUserId)
.WillCascadeOnDelete(true);
modelBuilder.Entity<Treat>()
.HasRequired(x => x.Patient)
.WithMany( x => x.Treats)
.HasForeignKey( x => x.PatientUserId)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
public DbSet<User> Users { get; set; }
public DbSet<Treat> Treats { get; set; }
}
I have found much answers here but no one from them works. I have spend a few hours trying to make it work. I know that Entity Framework must enable cascade delete when there is one-to-many relation, but it didn't
Entity Framework doesn't apply cascade deletion with TPT (Table Per Type) inheritance. You can solve this with Code Fist migrations:
CreateTable(
"dbo.Treats",
c => new
{
TreatId = c.Int(nullable: false, identity: true),
DoctorUserId = c.Int(nullable: false),
PatientUserId = c.Int(nullable: false),
})
.PrimaryKey(t => t.TreatId)
.ForeignKey("dbo.Doctor", t => t.DoctorUserId, cascadeDelete: true)
.ForeignKey("dbo.Patient", t => t.PatientUserId, cascadeDelete: true)
.Index(t => t.DoctorUserId)
.Index(t => t.PatientUserId);
The important part is cascadeDelete: true. You have to manually add it after migration code generation. After that you will have cascade deletion in your database:
FOREIGN KEY ([DoctorUserId]) REFERENCES [dbo].[Doctor] ([UserID]) ON DELETE CASCADE,
FOREIGN KEY ([PatientUserId]) REFERENCES [dbo].[Patient] ([UserID]) ON DELETE CASCADE
What are the step to convert/migrate a model property into a navigation property (create a new class and create a foreign key relationship, using EF Code First Migration.
In the example below, I want to convert the Student class property Country into a navigational property, without losing in data.
Current Model
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public string Country { get; set; }
}
Proposed Model
public class Student
{
public int ID { get; set; }
public string Name { get; set; }
public int CountryID { get; set; }
public virtual Country Country { get; set; }
}
public class Country
{
public int ID { get; set; }
public string Country { get; set; }
}
Add-Migration NavigationProperty
public override void Up()
{
CreateTable(
"dbo.Countries",
c => new
{
ID = c.Int(nullable: false, identity: true),
CountryName = c.String(),
})
.PrimaryKey(t => t.ID);
AddColumn("dbo.Students", "CountryID", c => c.Int(nullable: false));
CreateIndex("dbo.Students", "CountryID");
AddForeignKey("dbo.Students", "CountryID", "dbo.Countries", "ID", cascadeDelete: true);
DropColumn("dbo.Students", "Country");
}
Update-Database Error
System.Data.SqlClient.SqlException (0x80131904): The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Students_dbo.Countries_CountryID". The conflict occurred in database "aspnet-navprop-20141009041805", table "dbo.Countries", column 'ID'.