How to work with Navigation Property and non-standard keys? - c#

Assume I have following database tables with 1:1 mapping
Table dbo.Foo with PrimaryKey FooRowId
Table dbo.Bar with PrimaryKey BarRowId
No foreign keys exist on either table.
Using EF, I defined models as follows.
Modeling Foo table
public class Foo
{
[Key]
public long FooRowId { get; set; }
// Navigation
public virtual Bar Bar { get; set; }
}
Modeling Bar table
public class Bar
{
[Key]
public long BarRowId { get; set; }
// Navigation
public virtual Foo Foo { get; set; }
}
This gives me navigation property related error as follows.
Unable to determine the principal end of an association between the types 'MyOrg.Models.Foo'
and 'MyOrg.Models.Bar'. The principal end of this association must be explicitly
configured using either the relationship fluent API or data annotations.
One way to fix is by standardizing property names as follows.
On Foo
public class Foo
{
[Key]
[Column("FooRowId")]
public long FooId { get; set; }
// Navigation
public virtual Bar Bar { get; set; }
}
On Bar
public class Bar
{
[Key]
[Column("BarRowId")]
public long BarId { get; set; }
// Navigation
public virtual Foo Foo { get; set; }
}
However, requirement states I must keep the original properties FooRowID and BarRowId. Given this constraint, how to make the navigation properties work?

Although Attributes seem easy to use, it limits reuse of your classes in other databases.
Suppose you have created a class BillingAddress according to the standards of your company (country maybe). You want to use this class in two different DbContexts, each to represent their own database. The BillingAddress in database 1 has a primary key in column "MyPrimaryKey", the BillingAddress in database 2 has a primary key in column "Id". You can't solve that using attributes.
The names of columns and tables, the relation between tables is part of the database. Therefore this ought to be described in the DbContext. If you don't use Attributes in the DbSet classes, you'll be able to use the same class in different databases.
So let's write your table design in fluent Api
See:
Fluent API to configure tables, like table name
Fluent API to configure properties, like primary key, column names, foreign keys
Fluent API to configure relations between tables, like one-to-many
Below I give examples for all three categories:
Configure the table name and the primary key of the table
Configure one of the properties: column name
Configure relation between tables, for example one-to-many
Keep in mind: if you use code first, and you stick to the entity framework code first conventions, none of this is needed. As long as you stick to these conventions, Entity Framework will be very capable in detecting primary keys, foreign keys, relations between tables.
But if your tables are different, the following fluent API is needed in your DbContext
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configure entity table FOO:
var entityFoo = modelBuilder.Entity<Foo>();
// example: configure Name and primary key of foo table
entityFoo.ToTable("MySpecialFooTable");
entifyFoo.HasKey(foo => foo.FooRowId);
// example: configure property Name is in column FooName:
entityFoo.Property(foo => foo.Name).HasColumnName("MyFooName");
// example: one-to-many relation between foo and bar
// every Foo has zero or more Bars in property MyBars,
// every Bar belongs to exactly one For in property MyFoo
// using foreign key MyFooId:
entityFoo.HasMany(foo => foo.MyBars) // Foo has zero or more Bars in MyBars
.WithRequired(bar => bar.MyFoo) // every Bar belongs to one Foo
.HasForeignKey(bar => bar.MyFooId); // using foreign key MyFooId
}

Related

Entity Framework throwing conflict with reference constraint while deleting

I have the following entity declared
public class TransactionEvent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public virtual List<TransactionSignInError> SignInErrors { get; set; }
}
And the context
public class TransactionAuditsDbContext : DbContext
{
public virtual DbSet<TransactionEvent> TransactionEvents { get; set; }
}
Now when I try to delete a transaction event, I want the relevant SignInError rows to be deleted as well. I realize I can do this by using cascade on delete if I had set that up in the context, too late for that now.
How can I delete successfully a transaction? I'm getting this error.
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TransactionSignInErrors_dbo.TransactionEvents_TransactionEvent_Id". The conflict occurred in database "db", table "dbo.TransactionSignInErrors", column 'TransactionEvent_Id'
I have tried clearing the SignInErrors list before deleting, that did get rid of the above error but left NULLs in the TransactionSignInErrors table.
What you want, is "Cascade on Delete": if a TransactionEvent is deleted, then you also want that all its TransactionSignInErrors are deleted.
This works on a one-to-many relation, this does not work on a many-to-many-relation.
If you have a one-to-many relation between TransactionEvents and TransactionSignInErrors, and you followed the entity framework conventions, you will have classes like
public class TransactionEvent
{
public Guid Id { get; set; }
...
// Every TransactionEvent has zero or more TransactionSignInErrors (one-to-many)
public virtual ICollection<TransactionSignInError> SignInErrors { get; set; }
}
public class TransactionSignInError
{
public Guid Id { get; set; }
...
// Every TransactionSignInError belongs to exactly oneTransactionEvent, using foreign key
public Guid TransactionEventId {get; set;}
public virtual TransactionEvent TransactionEvent { get; set; }
}
public class TransactionAuditsDbContext : DbContext
{
public DbSet<TransactionEvent> TransactionEvents { get; set; }
public DbSet<TransactionSignInError> TransactionSignInErrors {get; set;}
}
This is all that entity framework needs to know to detect the tables, the columns in the tables and the one-to-many relation between these two tables.
In entity framework the non virtual properties represent the columns in the table, the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
The foreign key TransactionEventId is a real column, hence it is non-virtual. TransactionEvent is not a real column, it only refers to the relation, hence it is declared virtual.
If you stick to the conventions, there is no need for attributes, nor fluent API. Only if you want non-default identifiers for tables, columns, column types or non-default behaviour for table relations, you might need attributes or fluent API.
Default behaviour is cascade on delete: if you delete a TransactionEvent, all its TransactioinSigninErrors are also deleted.
I'm not sure whether your problems arise because you have a GUID as primary key, instead of an int. If you want, you can inform entity framework about your one-to-many relation and cascade on delete in OnModelCreating:
protected override void OnModelCreating (DbModelBuilder modelBuilder)
{
// Every TransactionEvent has zero or more TransactionSignInErrors
// Every TransactionSignInError belongs to exactly one TransactionEvent
// using foreign key TransactionEventId.
// Also: cascade on delete:
modelBuilder.Entity<TransactionEvent>()
.HasMany(transactionEvent => transactionEvent.TransactionSignInErrors)
.WithRequired(transactionSignInError => transactionSignInError.TransactionEvent)
.HasForeignKey(transactionSignInError => transactionSignInError.TransactionEventId)
.WillCascadeOnDelete();
So three major changes to your code:
The DbSets in the DbContext are non-virtual
Added the table TransactionSignInErrors to your DbContext
If that is not enough for CascadeOnDelete (check this first!) add fluent API.
Small change: Use ICollection instead of IList.
Rationale: if you fetch a TransactionEvent with its TransactionSignInErrors, does TransactionEvent.SignInErrors[4] have a defined meaning? Wouldn't it be better if people have no access to methods that they don't know what they really mean?
If you want to use a cascade delete you have to include the children:
var removingRow=_context.Set<TransactionEvent>()
.Include(x=> x.SignInErrors )
.Where(x => x.Id ==id)
.FirstOrDefault();
if(removingRow != null)
{
_context.Remove(removingRow);
_context.SaveChanges();
}
Your post has the tag of entity-framework. I'm not sure how things work with Entity Framework 6 or previous versions, but with Entity Framework Core you can solve your issue like -
var tEvent = dbCtx.TransactionEvents
.Include(p=> p.SignInErrors)
.FirstOrDefault(p => p.Id == id);
foreach (var error in eventx.SignInErrors)
{
dbCtx.SignInErrors.Remove(error);
}
dbCtx.TransactionEvents.Remove(tEvent);
dbCtx.SaveChanges();

Entity Framework 6 Code First TPH Foreign Key in multiple derived classes

I'm trying to understand what I need to do in order to introduce the same Foreign Key in multiple derived classes when the Foreign Key is not present in the base class. The Foreign Key is to the same type, and I'm able to make the various derived classes all use the same column name, but when I try to introduce the Foreign Key annotation, Entity Framework 6 silently fails to create any Foreign Key at all.
It's worth mentioning that, if I allow EF to create Bar_Name1 instead of reusing the existing column, it adds the Foreign Key appropriately. But I come from a relational database background, and it offends my sensibilities to have multiple columns for the same thing.
I would prefer to be able to stick to using Annotations to mark up my code, but if this is something that can't be done with Annotations but can be done with the Fluent API, I'm willing to delve into that.
public class Foo
{
[Key]
public string Name { get; set; }
}
public class FooSub1 : Foo
{
[Required, Column("Bar_Name")]
public string Bar_Name { get; set; }
[ForeignKey("Bar_Name")]
public Bar Bar { get; set; }
}
public class FooSub2 : Foo
{
[Required, Column("Bar_Name")]
public string Bar_Name { get; set; }
[ForeignKey("Bar_Name")]
public Bar Bar { get; set; }
}
public class Bar
{
[Key]
public string Name { get; set; }
}
The resolution to EF issue 1964 explains: "However, having an association in s-space here doesn't work anyway because it results in two database constraints which can only be satisfied if the dependent also matches the PK for the other relationship type. Such a match would usually only happen accidentally. The solution is to remove the associations from s-space like we do for similar TPC mappings." (emphasis mine)
EF drops the FK's on the merged column. In your case, the two FK's are logically the same, but EF doesn't know (or care about) that.

Do I define a relationship between two entities on the dependent or the principal?

When using Entity Framework, I get confused as to where I need to define a relationship between two entities. I feel like whenever I look for examples, I end up finding examples of the same thing from two different perspectives - dependent → principal, as well as principal → dependent.
Given the following entities:
class Foo
{
public int Id { get; set; }
public virtual ICollection<Bar> Bars { get; set; }
}
class Bar
{
public int Id { get; set; }
public Foo Foo { get; set; }
public virtual Baz { get; set; }
}
class Baz
{
public int Id { get; set; }
public Bar Bar { get; set; }
}
We have several scenarios here. Foo has many Bars pointing to it. Baz has an optional foreign key to Bar. Baz can exist without Bar specified.
Where would I defined these relations? By where, I mean when using fluent API, which entity would these relations be defined in relation to? Maybe to make it more clear, if I were using fluent API and EntityTypeConfiguration classes for binding, for which entity would these be defined in?
An example of why I'm confused is because I see answers like this one that say that a one-to-one should be defined in the class with the virtual. So in these entities, the optional one-to-one between Baz and Bar would be, or something similar to:
modelBuilder.Entity<bar>()
.HasOptional(f => f.Baz)
.WithRequired(s => s.Bar);
I think you are confused by the virtual keyword. I did try to find it on the page you linked to, but it's not there.
The virtual keyword lets the Entity Framework override that property in a proxy entity that it creates behind the scenes and which inherits from Bar. The override will then make a database call to lazy load Baz when the property is accessed.
The virtual keyword has nothing to do with the definition of the relationship, and if you don't want lazy loading, you don't need it.
You define the principal when you map:
modelBuilder.Entity<bar>()
.HasOptional(f => f.Baz). //Baz is dependent
.WithRequired(s => s.Bar);//Bar is principal
modelBuilder.Entity<bar>()
.HasOptional(f => f.Bar). //Bar is dependent
.WithRequired(s => s.Baz);//Baz is principal
As for the other relationship in your example between Foo and Bar, Foo has a collection of Bars but Foo has only one Bar so the foreign key goes on Bar. EF will do that by default.
The dependent gets the foreign key that references the principal's key. When it's a one to one, that foreign key is also the dependent's primary key but EF can't work out which is which and that's why you get an error until you've specified it.
Reference: http://msdn.microsoft.com/en-us/library/ee382827.aspx
when you use EF as you defined in your classes you already defined you relations.
EF has the ability to understand that when you define you navigation properties in Collection in a class
public virtual ICollection<Bar> Bars { get; set; }
that you want one to many Relation.
On the other hand if you add a collection to the other class
public virtual ICollection<Foo> Foos { get; set; }
EF will understand that you want Many to may Relation
same thing will happen if you add an instance of class as property in the other class it will understand that as one to one (or zero to one ) relation.
virtual keyword has nothing to do with your relation as mentioned before, it concerned in lazy, eager loading

Option 1:1 mapping, main entity doesn't have FK in it

I am using entity framework 4, mvc4 and code first.
I'm struggling with creating an option 1:1 mapping, where the main entity that has the optional 1:1 mapping doesn't have the FK in it:
public class User
{
[Column("user_id")]
public int Id {get;set;}
public virtual House House {get;set;} // optional mapping
}
public class House
{
[Column("house_id")]
public int Id {get;set;}
[Column("user_id")]
public int UserId {get;set;}
}
Notice how the user table doesn't have teh houseId column.
How can I map this correctly?
Note: the below method isn't what i really want to do since it forces me to add a navigational property on the House model also back to User.
I tried this method, although I had to add a virtual property to the House model which I didn't want to do: How do I code an optional one-to-one relationship in EF 4.1 code first with lazy loading and the same primary key on both tables?
So my configuration looks like with the above attempt:
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
this.ToTable("User", SchemaName);
this.HasKey(x => x.Id);
this.HasOptional(x => x.House);
}
}
public class HouseConfiguration : EntityTypeConfiguration<House>
{
public HouseConfiguration()
{
this.ToTable("House", SchemaName);
this.HasKey(x => x.Id);
this.HasRequired(vc => vc.User).WithRequiredDependent(v => v.House);
}
}
But when I do this, saving the model I get this error:
Cannot insert explicit value for identity column in table 'House' when IDENTITY_INSERT is set to OFF
Note: without the above setup (mapping and configuration), the House entity saves just fine to the database and the identity is setup correctly.
Remove the UserId property from House, remove the this.HasRequired... mapping from the HouseConfiguration constructor and use in UserConfiguration:
this.HasOptional(x => x.House).WithRequired();
This will define a shared primary key association (i.e. House.Id is primary key of House and the foreign key to User at the same time).
If you have an existing database with a separate foreign key column user_id in the House table and this column has a unique key constraint to enforce a one-to-one relation in the database you cannot map this as one-to-one relationship with Entity Framework because EF doesn't support foreign key one-to-one associations.
You would have to map this as one-to-many relationship in this case and unfortunately you can't have a single reference House on the principal User. You would have to use a collection of Housees instead (and ensure in business logic that you never add more than one element to this collection, otherwise upon saving you would get an exception due to a violation of the unique FK constraint in the House table) or no navigation property at all in User. But then you need a navigation reference User in entity House so that EF would be able to map a relationship at all (at least a navigation property on one side of a relationship is always needed).
Can't rollback to EF4, but was only using it in a similar way a while ago, so don't believe this has changed.
You need to set a Key on the House object and set it to DB generated:
using System.ComponentModel.DataAnnotations.Schema
using System.ComponentModel.DataAnnotations
...
public class House
{
[Column("house_id")]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Column("user_id")]
public int UserId { get; set; }
}

Why is EF code-first generating an extraneous foreign key column?

I'm using entity framework code-first to create my database schema automatically, and one of my entities looks like this:
public class AssessmentsCaseStudies {
#region Persisted fields
[Required]
[Key, Column(Order=0)]
[ForeignKey("Assessment")]
public int AssessmentId { get; set; }
[Required]
[Key, Column(Order=1)]
[ForeignKey("CaseStudy")]
public int CaseStudyId { get; set; }
[Required]
public int Score { get; set; }
[ForeignKey("Follows")]
public int? FollowsCaseStudyId { get; set; }
#endregion
#region Navigation properties
public virtual Assessment Assessment { get; set; }
public virtual CaseStudy CaseStudy { get; set; }
public virtual CaseStudy Follows { get; set; }
#endregion
}
When EF auto-generates my database, it generates a table with the following columns:
AssessmentId (PK, FK, int, not null)
CaseStudyId (PK, FK, int, not null)
Score (int, not null)
FollowsCaseStudyId (FK, int, null)
CaseStudy_CaseStudyId (FK, int, null)
This is all fine apart from the CaseStudy_CaseStudyId column. Why has that been generated? What is it for? How can I stop it being generated? My suspicion is that EF can no longer automatically match up CaseStudy's ICollection<AssessmentsCaseStudies> with the CaseStudyId column, so it creates its own column to link the two together for that navigation property.
Because you have two navigation properties of type CaseStudy in your AssessmentsCaseStudies entity and an AssessmentsCaseStudies collection in your CaseStudy entity EF cannot decide which of the two CaseStudy navigation properties this collection refers to. Both could be possible and both options would result in a valid but different entity model and database schema.
In such an ambiguous situation the EF convention is to create actually three relationships, i.e. your collection in CaseStudy does not refer to any of the two CaseStudy navigation properties but has a third (but not exposed and "invisible") endpoint in AssessmentsCaseStudies. This third relationship is the reason for the third foreign key your are seeing in the database - the one with the underscore. (The underscore is always a strong indication that something happend by mapping convention and not by your explicit configuration or data annotations.)
To fix the problem and to override the convention you can apply the [InverseProperty] attribute, thereby specifying the CaseStudy navigation property the AssessmentsCaseStudies collection belongs to:
[InverseProperty("AssessmentsCaseStudies")] // the collection in CaseStudy entity
public virtual CaseStudy CaseStudy { get; set; }
You can also (alternatively, you don't need both) put the attribute on the collection side:
[InverseProperty("CaseStudy")] // the CaseStudy property in AssessmentsCaseStudies entity
public virtual ICollection<AssessmentsCaseStudies> AssessmentsCaseStudies { get; set; }
For some reason, Slauma's InverseProperty attribute suggestion didn't work. What did work was me specifying the relationship between the two CaseStudy navigation properties in AssessmentsCaseStudies, and the CaseStudy entity, via the Fluent API in my database context's OnModelCreating method:
modelBuilder.Entity<AssessmentsCaseStudies>()
.HasRequired(acs => acs.CaseStudy)
.WithMany(cs => cs.AssessmentsCaseStudies)
.HasForeignKey(acs => acs.CaseStudyId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<AssessmentsCaseStudies>()
.HasOptional(acs => acs.Follows)
.WithMany() // No reverse navigation property
.HasForeignKey(acs => acs.FollowsCaseStudy)
.WillCascadeOnDelete(false);
Once that's added, the migration code that's generated when I Add-Migration no longer tries to add the CaseStudy_CaseStudyId column and I just get the FollowsCaseStudyId column added, with the appropriate foreign key relationship.
For anyone else landing here looking for a solution, if you've tried the previous answers and are still getting an extra foreign key column, look for any properties you may have defined further down your POCO class that you did not intend to map to DB fields. Even if they contain code blocks, as with complex get accessors, Entity Framework will try to map them to the database somehow. This may result in extra foreign key columns if your properties return entities. To be safe, either decorate such properties with the [NotMapped] attribute or convert them to methods.

Categories

Resources