Can't define two relationships between the same two entities - c#

I can't get EF 6 Code First to map the following two relationships the way I want them.
There are two entities: Template and TemplateVersion.
Every TemplateVersion has exactly one ParentTemplate.
A Template has a collection of TemplateVersions.
This was the first, simple, 1:many relationship, with navigation properties on both sides.
Now for the second:
From all TemplateVersions associated to a Template, only one (e.g. the "newest") is the CurrentTemplateVersion for that Template.
So: Template has a navigation property CurrentVersion, and an associated property CurrentVersionId.
There is no corresponding navigation property on the TemplateVersion side.
So, I would say, this second Template : TemplateVersion relation is 0..1 : 1.
Here are the models:
public class Template
{
public int Id { get; set; }
[...]
public virtual int CurrentVersionId { get; set; }
public virtual TemplateVersion CurrentVersion { get; set; }
public virtual ICollection<TemplateVersion> Versions { get; set; }
}
public class TemplateVersion
{
public int Id { get; set; }
[...]
public virtual int ParentTemplateId { get; set; }
public virtual Template ParentTemplate { get; set; }
}
I like to keep my model classes free from DB specifics, so I defined the relationships in the context:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Template>()
.HasMany(template => template.Versions)
.WithRequired(version => version.ParentTemplate)
;
modelBuilder.Entity<Template>()
.HasRequired(template => template.CurrentVersion)
.WithOptional()
;
}
The problem is, the 2nd relation doesn't work as expected.
Using EF Power Tools plugin, I reverse-engineer the model diagram. Here's what I get:
1st relation, 1:many (ok)
2nd relation, 0..1:1
Notice that CurrentVersionId property is not part of the relation, and Template.Id is !
The generated DB tables mirror exactly this: CurrentVersionId is not part of any foreign key, and Id on the Template table incorrectly is defined as a foreign key to Id on the TemplateVersion table.
What am I missing ?
PS. Even if I remove the 1st relationship completely, the 2nd one is the same.

In a one-to-one relationship, EF requires the PK of the dependent end also has to be the FK of the relationship:
public class Foo
{
public int Id{get;set;}
//...
}
public class Boo
{
[Key,ForeignKey("Foo")]
public int FooId{get;set;}
public virtual Foo Foo{get;set;}
//...
}
If you need that TemplateVersion has its own Id, then, to resolve your issue you could configure that relationship this way:
modelBuilder.Entity<Template>()
.HasRequired(template => template.CurrentVersion)
.WithMany().HasForeignKey(t=>t.CurrentVersionId);

Related

One-To-One relationship with FK distinct from PK

I have 2 tables in database: ReceivedGoods and ReceivedGoodsProperties
ReceivedGoods contains ReceivingId as PK and must have its extending data in ReceivedGoodsProperties which contains ReceivingId as FK referencing to the ReceivedGoods's ReceivingId. Current ReceivedGoodsProperties, however, has its own PK Id and is therefore distinct from FK. So I have following:
public class ReceivedGoods
{
...
public int ReceivingId { get; set; }
...
public virtual ReceivedGoodsProperties properties { get; set; }
}
public class ReceivedGoodsProperties
{
...
public int Id { get; set; } // This is PK
public int ReceivingId { get; set; } // This is FK
...
public virtual ReceivedGoods goods { get; set; }
}
I would like to get ReceivedGoods object and have properties automatically loaded as well but I am not able to figure out, how to set up this within EF.
I've tried something like this (from the ReceivedGoodsProperties side mapping):
this.HasRequired(p => p.goods)
.WithRequiredDependent(d => d.properties)
.Map(m => m.MapKey("ReceivingId"));
but I am ending up with following error:
ReceivingId: Name: Each property name in a type must be unique. Property
name 'ReceivingId' is already defined.
When commenting out ReceivingId in ReceivedGoodsProperties, upper exception is not thrown, ReceivedGoods is loaded correctly except the properties property.
Can somebody explain me, how to do one-to-one mapping in situation like this?
Could you try:
public class ReceivedGoods
{
...
public int ReceivingId { get; set; }
...
public virtual ReceivedGoodsProperties properties { get; set; }
}
public class ReceivedGoodsProperties
{
...
public int Id { get; set; } // This is PK
[ForeignKey( "goods " )]
public int ReceivingId { get; set; } // This is FK
...
[Required]
public virtual ReceivedGoods goods { get; set; }
}
BTW, in C# the standard guidelines is to PascalCase members, so Goods and Properties
Try defining the relationship this way:
this.HasRequired(p => p.goods)
.WithRequiredDependent(p => p.properties)
.HasForeignKey(p => p.ReceivingId);
If you follow the standard EF naming conventions, it can usually figure out these relationships on its own. You only really run in to trouble when your navigation property names don't correspond to the class name, or if you have multiple FKs to the same destination in the source table.
If you want the navigation properties to get filled out "automatically", use the Include extension method on the query, as in:context.Goods.Include(g=>g.properties). You don't have to declare them as virtual unless you want to make use of lazy loading.
You may need to come at this from the other entity:
this.HasRequired(p => p.properties)
.WithRequiredPrincipal(p => p.goods)
.HasForeignKey(p => p.ReceivingId);

2 Foreign Keys as Primary Key using EF Core 2.0 Code First

I have two tables: Comment and Like.
public class Comment {
CommentID { get; set; }
....
}
public class Like {
CommentID { get; set; }
UserID { get; set; }
}
Using entity framework core 2.0 code first, I want to define my "Like" model as only having the two fields which reference other primary keys (as foreign keys) but also I want the combination of the values to be the Primary Key of the table. Any help would be appreciated!
So, here's what's going on here:
1) There are two classes: Comment and User.
2) The third class, Like, holds references (navigation properties) to both two those classes which correspond to foreign keys in database: UserId and CommentId. I explicitly used ForeignKey attribute, so that it would be clear to you which properties EF will use as foreign keys. In this particular case you could omit this attribute since EF will figure it out automatically (since names match in both classes). Note that it's not mandatory to have foreign keys, but they have advantages.
3) The UserId and CommentId comprise composite key. The Column attribute configures the order of columns in database (so called ordinals). This is important for EF.
4) The User and Comment classes also have navigation properties (since it's one side of one-to-many relations): Likes.
5) Finally, always use Table attribute to avoid problems with pluralizations because there's no way to turn it off.
[Table("Comment")]
public class Comment
{
public int CommentID { get; set; }
public List<Like> Likes { get; set; }
}
[Table("User")]
public class User
{
public int UserId { get; set; }
public List<Like> Likes { get; set; }
}
[Table("Like")]
public class Like
{
[Key]
[Column(Order = 1)]
public int CommentID { get; set; }
[Key]
[Column(Order = 2)]
public int UserID { get; set; }
[ForeignKey("CommentId")]
public Comment Comment { get; set; }
[ForeignKey("UserId")]
public User User { get; set; }
}
UPDATE
Setting composite key in EF Core
The Key (and Column) attributes, used to designate composite primary key, actually, don't work in EF Core - they work in EF6. To configure composite key in EF Core, you need to use Fluent Configuration.
You have two options to do it.
OPTION 1. Make all the configuration in OnModelCreatingMethod:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Like>().HasKey(l => new { l.CommentID, l.UserID });
}
OPTION 2. Move all the configuration into separate class and apply it in OnModelCreating:
1) Create separate class for configuration
class LikeConfiguration : IEntityTypeConfiguration<Like>
{
public void Configure(EntityTypeBuilder<Like> builder)
{
builder.HasKey(l => new { l.CommentID, l.UserID });
}
}
2) Apply configuration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new LikeConfiguration());
}
Choose any option you like.
As you see, to configure composite key in Fluent Configuration, you need to use anonymous type. And again, the order of properties matters.

ASP.Net Entity Framework one-to-many relationship: How to establish dependency and lazy loading not working

I have two classes. The class BC_Instance can have many BC_InstanceSession and a BC_InstanceSession is dependent on a BC_Instance and should be deleted when it's relative BC_Instance is deleted.
//Instance
public class BC_Instance
{
public int ID { get; set; }
//sessions
public ICollection<BC_InstanceSession> sessions { get; set; }
}
//Instance session
public class BC_InstanceSession
{
[Key]
public int ID { get; set; }
[ForeignKey("Instance")]
public int InstanceID { get; set; }
public virtual BC_Instance Instance { get; set; }
}
I have detected a few problems with this configuration. First Sessions are not deleted when it's Instance is deleted. Is it possible to specify that a session cannot exist without an instance or I need to manually delete them?
Second there seems to be a problem in the mapping to the database. A Session has two foreign keys on the Database InstanceID and BC_Instance_ID as show in the image below:
Finally Lazy loading does not work. Explicit loading is needed to access the Sessions for an instance (code below)
BC_Instance instance = db.BiocloudInstances.Include(i => i.sessions).Where(i => i.ID == id).First();
For the first question you can use a CascadeOnDelete, something like:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BC_Instance>()
.HasMany(i => i.sessions)
.WithRequired(s => s.Instance)
.WillCascadeOnDelete(true);
}
For the second one, if you look on the EntityFramework documentation they specify that a 1-n relationship is used without the ForeignKey adnotation. So, because you declare your relationship virtual, EF will add 2 keys. To fix this, remove the ForeignKey adnotation and the public int InstanceID { get; set; } row. (More on their page here)
Third, as i specified in the comment,
the lazy loading is not working because you didn't specify virtual to your ICollection. Like: public virtual ICollection<BC_InstanceSession> sessions { get; set; }

Using both many-to-many and one-to-many to same entity

I have a many-to-many association in EF Code-First (as explained in this question), and I want to use a one-to-many to the same entity as well. The problem is EF does not produce the right database scheme. Code:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<B> ObjectsOfB { get; set; }
}
public class B
{
public int Id { get; set; }
public virtual A ObjectA { get; set; }
public virtual ICollection<A> OtherObjectsOfA { get; set; }
}
When I remove the ObjectA property of class B the many-to-many association is generated correctly.
When generated incorrectly, entity B gets 2 foreign keys to A, and entity A gets 1 foreign key to B (like a many-to-one relation).
If you have more than one navigation property refering to the same entity EF does not know where the inverse navigation property on the other entity belongs to. In your example: Does A.ObjectsOfB refer to B.ObjectA or to B.OtherObjectsOfA? Both would be possible and a valid model.
Now, EF does not throw an exception like "cannot determine relationships unambiguously" or something. Instead it decides that B.ObjectA refers to a third endpoint in B which is not exposed as navigation property in the model. This creates the first foreign key in table B. The two navigation properties in B refer to two endpoints in A which are also not exposed in the model: B.ObjectA creats the second foreign key in table B and B.OtherObjectsOfA creates a foreign key in table A.
To fix this you must specify the relationships explicitely.
Option one (the easiest way) is to use the InverseProperty attribute:
public class A
{
public int Id { get; set; }
public string Name { get; set; }
[InverseProperty("OtherObjectsOfA")]
public virtual ICollection<B> ObjectsOfB { get; set; }
}
This defines that A.ObjectsOfB is part of a many-to-many relation to B.OtherObjectsOfA.
The other option is to define the relationships completely in Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<A>()
.HasMany(a => a.ObjectsOfB)
.WithMany(b => b.OtherObjectsOfA)
.Map(x =>
{
x.MapLeftKey("AId");
x.MapRightKey("BId");
x.ToTable("ABs");
});
modelBuilder.Entity<B>()
.HasRequired(b => b.ObjectA) // or HasOptional
.WithMany()
.WillCascadeOnDelete(false); // not sure if necessary, you can try it
// without if you want cascading delete
}
If table B has foreign key to table A then class B has navigation property to A and A have navigation property to ICollection<A>.
If table B has many to many relation with table A then class A must have ICollection<B> and class B must have ICollection<A>.
Try that, maybe this will clarify your request from the EF.

Entity has two properties which both reference the same entity type in one-to-many relationship

This seems like the most common relationship but for some reason I cannot get code-first EF working. When I run the code below I get the following error:
*{"Introducing FOREIGN KEY constraint 'Recording_RecordingLocation' on table 'Recordings' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.\r\nCould not create constraint. See previous errors."}*
I have researched SO and other places but have not been able to figure this out. I must be having a minor stroke so if this is duplicative I apologize. I don't think it is because all other reference questions I found were for many-to-many relationships... many-to-one.
My scenario is quite simple...
I have an entity (Recording) that has two required properties RecordingLocation and EditingLocation which are both of the same type WorkLocation. Each Recording has exactly one RecordingLocation and one EditingLocation (not many-to-many). I also have the requisite navigation properties.
Each WorkLocation is stand-alone and is not intrinsically linked to the Recording -- it's just a physical place where some work on that Recording took place. So when I delete a recording I do not want to delete the associated WorkLocations.
public class Recording
{
[Key]
public virtual int Id { get; set; }
//... other properties not shown here
public virtual int RecordingLocationId { get; set; }
public virtual WorkLocation RecordingLocation { get; set; }
public virtual int EditingLocationId { get; set; }
public virtual WorkLocation EditingLocation { get; set; }
{
public class WorkLocation
{
[Key]
public virtual int Id { get; set; }
public virtual WorkLocationType Type { get; set; }
public virtual string Description { get; set; }
public virtual LogicalStatus Status { get; set; }
}
// I'll use this on the front-end to filter a selection list
// but don't necessarily assume a Work Location is bound to only items of this type
public enum WorkLocationType
{
RecordingLocation,
EditingLocation,
MasteringLocation
}
What am I missing to get this working?
Your navigation properties RecordingLocation and EditingLocation are required because the corresponding foreign key properties are not nullable. By convention EF assumes that cascading delete is active for a required one-to-many relationship which causes a problem if you have more than one such relationship refering to the same table - hence the exception.
You must disable cascading delete (also your business logic seems to require it) which is only possible in Fluent API:
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Recording>()
.HasRequired(r => r.RecordingLocation)
.WithMany()
.HasForeignKey(f => f.RecordingLocationId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Recording>()
.HasRequired(r => r.EditingLocation)
.WithMany()
.HasForeignKey(f => f.EditingLocationId)
.WillCascadeOnDelete(false);
}
}

Categories