I'm using EF Code First to query and create a database. One of my entities (relationship) has two navigation properties to the same entity (activity). My problem is that if I use EF to create the database schema it will create four foreign key columns and constraints instead of two.
Here are the relevant code parts:
activity class:
public class Activity {
public virtual ICollection<Relationship> Successors { get; set; }
public virtual ICollection<Relationship> Predecessors { get; set; }
}
relationship class:
public class Relationship {
public virtual Activity Activity1 { get; set; }
public int Activity1_ID { get; set; }
public virtual Activity Activity2 { get; set; }
public int Activity2_ID { get; set; }
}
Relationship mapping class:
this.HasRequired(t => t.Activity1)
.WithMany(t => t.Predecessors)
.HasForeignKey(m => m.Activity1_ID)
.WillCascadeOnDelete(false);
this.HasRequired(t => t.Activity2)
.WithMany(t => t.Successors)
.HasForeignKey(m => m.Activity2_ID)
.WillCascadeOnDelete(false);
Database structure:
Is there a way to prevent the creation of the last two columns?
This should create you only 2 foreign key columns.
public class Activity
{
public int Id { set; get; }
public virtual ICollection<Relationship> Successors { get; set; }
public virtual ICollection<Relationship> Predecessors { get; set; }
}
public class Relationship
{
public int Id { set; get; }
public virtual Activity Activity1 { get; set; }
public int Activity1_ID { get; set; }
public virtual Activity Activity2 { get; set; }
public int Activity2_ID { get; set; }
}
And the DbContext class where i am specifying the relationship/FK nature on my OnModelCreating.
public class MyDb: DbContext
{
public MyDb():base("EfDbContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Relationship>()
.HasRequired(f => f.Activity1)
.WithMany(f => f.Predecessors)
.HasForeignKey(g => g.Activity1_ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Relationship>().
HasRequired(f => f.Activity2)
.WithMany(f => f.Successors)
.HasForeignKey(g => g.Activity2_ID)
.WillCascadeOnDelete(false);
}
}
Related
I have this simple model and I'd want to configure relations between them
one Company has one logo
and one Company can have many files
public class Company
{
public int Id { get; set; }
public File Logo { get; set; }
public List<File> Attachments { get; set; } = new List<File>();
}
public class File
{
public int Id { get; set; }
public Company Company { get; set; }
public int CompanyId { get; set; }
(...)
}
public class CompanyConfiguration : IEntityTypeConfiguration<Company>
{
public void Configure(EntityTypeBuilder<Company> builder)
{
builder
.HasMany(x => x.Attachments)
.WithOne(x => x.Company)
.HasForeignKey(x => x.CompanyId);
builder
.HasOne(x => x.Logo)
.WithOne(x => x.Company)
.HasForeignKey(x => x.); // x. doesnt show me anything about "File" class. It looks like assembly
}
}
But since I probably neeed two FK
I changed it into:
public class File
{
public int Id { get; set; }
public Company Company { get; set; }
public int CompanyId { get; set; }
public Company CompanyOtherProperty { get; set; }
public int CompanyOtherPropertyId { get; set; }
}
but even if I insert FK's name as a string
public class CompanyConfiguration : IEntityTypeConfiguration<Company>
{
public void Configure(EntityTypeBuilder<Company> builder)
{
builder
.HasMany(x => x.Attachments)
.WithOne(x => x.Company)
.HasForeignKey(x => x.CompanyId);
builder
.HasOne(x => x.Logo)
.WithOne(x => x.CompanyOtherProperty)
.HasForeignKey("CompanyOtherPropertyId");
}
}
System.InvalidOperationException: 'You are configuring a relationship between 'Company' and 'File' but have specified a foreign key on 'CompanyOtherPropertyId'. The foreign key must be defined on a type that is part of the relationship.'
Given your needs, I would do this using inheritance since Logo and Attachements have common properties but not the same relations
public abstract class File
{
public int Id { get; set; }
.....
}
public class Logo : File
{
....
}
public class Attachement : File
{
public Company Company { get; set; }
public int CompanyId { get; set; }
}
public class Company
{
public int Id { get; set; }
public Logo Logo { get; set; }
public int LogoId { get; set; }
public ICollection<Attachement> Attachements { get; set; } = new List<Attachement>();
}
public DbSet<Company> Companies { get; set; }
public DbSet<Attachement> Attachements { get; set; }
public DbSet<Logo> Logos { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<File>().HasDiscriminator();
}
The design you have right now is error prone, I assume one file cannot be both Logo and Attachment which means one of the two Company navigation properties will always be null. You will have to do null checks everywhere in the system
You should have separate models for File and Logo
So I'm following this answer in trying to get two foreign keys to get to a single table and it works.
public class Team
{
public int TeamId { get; set;}
public string Name { get; set; }
public virtual ICollection<Match> HomeMatches { get; set; }
public virtual ICollection<Match> AwayMatches { get; set; }
}
public class Match
{
public int MatchId { get; set; }
public int HomeTeamId { get; set; }
public int GuestTeamId { get; set; }
public float HomePoints { get; set; }
public float GuestPoints { get; set; }
public DateTime Date { get; set; }
public virtual Team HomeTeam { get; set; }
public virtual Team GuestTeam { get; set; }
}
public class Context : DbContext
{
...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Match>()
.HasRequired(m => m.HomeTeam)
.WithMany(t => t.HomeMatches)
.HasForeignKey(m => m.HomeTeamId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Match>()
.HasRequired(m => m.GuestTeam)
.WithMany(t => t.AwayMatches)
.HasForeignKey(m => m.GuestTeamId)
.WillCascadeOnDelete(false);
}
}
However, in my solution I don't want Team to have HomeMatches and AwayMatches collections as it doesn't make sense to be able to navigate to Match from that entity.
Is it possible to have two foreign keys pointing to the same table when the entity for the parent table doesn't have collections for the child tables.
I would like my Team entity to be like below.
public class Team
{
public int TeamId { get; set;}
public string Name { get; set; }
// HomeMatches and AwayMatches collection is no longer here
}
How could I use the modelBuilder to articulate to EntityFramework that I want to HomeTeamID and GuestTeamID to be foreign keys of Team?
Just remove collections & leave empty parameters for .WithMany().
I am trying to make and optional one to many relationship using fluent API. But it doesn't seem to work as I get this error:
InBuildingNavigator.Data.Models.ConnectionPointRoute_Segment: : Multiplicity conflicts with the referential constraint in Role 'ConnectionPointRoute_Segment_Target' in relationship 'ConnectionPointRoute_Segment'. Because all of the properties in the Dependent Role are non-nullable, multiplicity of the Principal Role must be '1'.
This is the modelcreation :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ConnectionPointRoute>()
.HasKey(c => new {c.ConnectionPointId, c.RouteId, c.SegmentId});
modelBuilder.Entity<ConnectionPoint>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.ConnectionPoint)
.HasForeignKey(c => c.ConnectionPointId);
modelBuilder.Entity<Route>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.Route)
.HasForeignKey(c => c.RouteId);
modelBuilder.Entity<Segment>()
.HasMany(c => c.ConnectionPointRoutes)
.WithOptional(s => s.Segment)
.HasForeignKey(s => s.SegmentId);
}
and this is the model :
public class ConnectionPointRoute
{
public int ConnectionPointId { get; set; }
public int RouteId { get; set; }
public int? SegmentId { get; set; }
public int Position { get; set; }
public ConnectionPoint ConnectionPoint { get; set; }
public Route Route { get; set; }
public Segment Segment { get; set; }
}
public class Segment
{
public Segment()
{
ConnectionPointRoutes = new List<ConnectionPointRoute>();
}
public int SegmentId { get; set; }
public int ConnectionPointIdEnd { get; set; }
public string ConnectionName { get; set; }
public string ConnectionInformation { get; set; }
public string Image { get; set; }
public string Direction { get; set; }
public ICollection<ConnectionPointRoute> ConnectionPointRoutes { get; set; }
}
Any thoughts?
It's because you are trying to define a composite primary key on ConnectionPointRoute that includes SegmentId which is nullable. You cannot define a primary key on a nullable column.
I'm trying to do one-one relationship for MVC5 codefirst. I've looked this page and did exactly same things but I've got an error.
Here is my classes and context:
Product:
public class Product
{
public int ProductId { get; set; }
public string ProductName { get; set; }
public string ProductDescription { get; set; }
/// <summary>
/// Display order
/// </summary>
public int Order { get; set; }
public string TitleBackgroundColor { get; set; }
public virtual TblClass TblClass { get; set; }
public virtual ICollection<Order> Orders { get; set; }
public virtual ICollection<Price> Prices { get; set; }
public virtual ICollection<ProductFeature> ProductFeatures { get; set; }
}
TblClass:
public class TblClass
{
[Key, ForeignKey("Product")]
public int ProductId { get; set; }
public string ClassName { get; set; }
public virtual ICollection<Permission> Permissions { get; set; }
public virtual Product Product { get; set; }
public int ClassOrder { get; set; }
}
DBContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>()
.HasMany<UserProfile>(r => r.UserProfiles)
.WithMany(u => u.Roles)
.Map(m =>
{
m.ToTable("webpages_UsersInRoles");
m.MapLeftKey("RoleId");
m.MapRightKey("UserId");
});
modelBuilder.Entity<TblClass>()
.HasKey(c => c.ProductId);
modelBuilder.Entity<Product>()
.HasOptional(f => f.TblClass)
.WithRequired(s => s.Product)
.Map(t => t.MapKey("ProductId"));
}
And when I try to run 'update-database -verbose' I've got this error:
The navigation property 'Product' declared on type 'YazililarGaranti.Domain.Entities.TblClass' has been configured with conflicting foreign keys.
You do not have to use .Map(t => t.MapKey("ProductId") when making an 1:1 relationship. This should work:
public class Product
{
public int ProductId { get; set; }
//... other properties
public virtual TblClass TblClass { get; set; }
//... other properties
}
public class TblClass
{
//[Key, ForeignKey("Product")] <-- remove these attributes
public int ProductId { get; set; }
public string ClassName { get; set; }
//.. other properties
public virtual Product Product { get; set; }
public int ClassOrder { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//other mappings
modelBuilder.Entity<TblClass>()
.HasKey(c => c.ProductId);
modelBuilder.Entity<Product>()
.HasKey(c => c.ProductId); //consider to use database generated option
//modelBuilder.Entity<Product>().Property(t => t.ProductId)
//.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Product>()
.HasOptional(f => f.TblClass)
.WithRequired(s => s.Product);
}
Hope this helps!
I don't understand why EF creates a nullable TemplateTask_Id column in my TemplateTaskDependancies table. I thought using a modelbuilder configuration class would solve the problem, but I must be missing something.
My domain classes are as follows.
[Table("TemplateTaskDependancies")]
public class TemplateTaskDependancy : Dependancy<TemplateTask>,
IDependancy<TemplateTask>
{
[Column("TaskId")]
public int TaskId { get; set; }
[Column("NeededTaskId")]
public int NeededTaskId { get; set; }
[ForeignKey("TaskId")]
public override TemplateTask Task { get; set; }
[ForeignKey("NeededTaskId")]
public override TemplateTask NeededTask { get; set; }
}
public abstract class Dependancy<T> : LoggedEntity
where T : LoggedEntity
{
[Column("TaskId")]
public int TaskId { get; set; }
[Column("NeededTaskId")]
public int NeededTaskId { get; set; }
[ForeignKey("TaskId")]
public abstract T Task { get; set; }
[ForeignKey("NeededTaskId")]
public abstract T NeededTask { get; set; }
}
public interface IDependancy<T> where T : LoggedEntity
{
int Id { get; set; }
int TaskId { get; set; }
int NeededTaskId { get; set; }
T NeededTask { get; set; }
T Task { get; set; }
State { get; set; }
}
public abstract class LoggedEntity : IObjectWithState
{
public int Id { get; set; } // primary key
// todo with Julie Lerman's repository pattern
}
In my context I have
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions
.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Configurations
.Add(new TemplateTaskDependancyConfiguration());
}
public class TemplateTaskDependancyConfiguration :
EntityTypeConfiguration<TemplateTaskDependancy>
{
public TemplateTaskDependancyConfiguration()
{
HasRequired(x => x.NeededTask)
.WithMany(y=>y.NeededTasks)
.HasForeignKey(z=>z.NeededTaskId)
.WillCascadeOnDelete(false);
HasRequired(x => x.NeededTask)
.WithMany(y => y.Dependancies)
.HasForeignKey(z => z.TaskId)
.WillCascadeOnDelete(false);
HasRequired(x=>x.Task)
.WithMany(y=>y.NeededTasks)
.HasForeignKey(z=>z.NeededTaskId)
.WillCascadeOnDelete(false);
HasRequired(x => x.Task)
.WithMany(y => y.Dependancies)
.HasForeignKey(z => z.TaskId)
.WillCascadeOnDelete(false);
}
}
Because you have no primary key defined anywhere?
By the way, it's dependEncy.
It turned out that the problem was caused by an unneeded collection of
public List<TemplateTaskDependancy> Tasks
inside my TemplateTask class.
i.e the foreign key table contained an extra collection of objects.