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.
Related
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();
I have a parent child relationship where the parent has a ValueObject and I cannot determine how to correctly define the relationship.
Adding a migration for the Child/Parent relationship fails with the error...
The entity type 'Address' requires a primary key to be defined.
The following is the current code structure.
public class Address
{
[Required]
public string BuildingNumber { get; private set; }
// other address properties...
}
public class Parent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
[Required]
public Address PrimaryAddress { get; private set; }
}
public class ParentContext : DbContext
{
public ParentContext(DbContextOptions<ParentContext> options) :
base(options)
{
}
public DbSet<Parent> Parents { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress);
// Flatten the ValueObject fields into table
modelBuilder.Entity<Parent>().OwnsOne(p => p.PrimaryAddress).
Property(b => b.BuildingNumber).IsRequired().
HasColumnName("Primary_BuildingName");
}
}
public class Child
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; protected set; }
[Required]
public int ParentId { get; private set; }
[ForeignKey("ParentId")]
public Parent Parent { get; private set; }
}
public class ChildContext : DbContext
{
public ChildContext(DbContextOptions<ChildContext> options) : base(options)
{
}
public DbSet<Child> Children { get; set; }
}
Using the above code example I can run separate commands to create migrations for Parent and Child and the tables look correct.
add-migration create-parent -c parentcontext
add-migration create-child -c childcontext
Adding in the relationship to the entities and adding the final migration fails.
add-migration add-parent-child-fk -c childcontext
The problem only occurs where I have Child and Parent in a different Context.
I have tried defining the relationship different ways in both the parent and child to map the address fields so that the child 'understands' the mapping but I cannot avoid EF errors with anything I have tried.
Example Project is here
https://github.com/cimatt55/ef-parent-valueobject
The main problem are the separate contexts. Value object (owned entity type) is just a consequence - if there wasn't value object, then you would have another issues.
You seem to base your design on a wrong assumption that only entity classes from publicly exposed DbSet. But that's not true. Referenced entities by navigation properties are also included, as well as referenced entities by them etc.
This is logical because EF Core context represents a database with tables and relationships. EF Core needs to know all the related entities in order to correctly support loading related data, querying (joining), cascade delete, tables, columns, primary and foreign key property/columns and their mappings etc.
This is explained in the Including & Excluding Types section of the EF Core documentation:
By convention, types that are exposed in DbSet properties on your context are included in your model. In addition, types that are mentioned in the OnModelCreating method are also included. Finally, any types that are found by recursively exploring the navigation properties of discovered types are also included in the model.
Adjusting their example for your ChildContext, the following types are discovered:
Child because it is exposed in a DbSet property on the context
Parent because it is discovered via the Child.Parent navigation property
Address because it is discovered via the Parent.PrimaryAddress navigation property
Since ChildContext has no Parent entity configuration, EF assumes everything related to Parent (and Address) to be by convention, hence the exception.
Shorty, using separate contexts containing related entities is not a good idea. The solution is to put and maintain all related entities in a single context.
Looking at the terminology used, you've probably are after DDD and bounded contexts, but these do not fit in EF Core (and generally in relational database) model.
Given the following SQL tables:
EntityGroup:
Id int, (PK)
GroupName nvarchar(100)
Entity:
Id int, (PK)
EntityGroupId int, (FK Non-nullable)
Description nvarchar(100)
And the following POCOs
public class Entity
{
public int Id { get; set; }
public int EntityGroupId { get; set; }
public int RefNumber { get; set; }
}
public class EntityGroup
{
public int Id { get; set; }
public virtual IList<Entity> Entities { get; set; }
}
How do I configure the fluent mapping correctly? I want Entity.EntityGroupId to remain as an int rather than an EntityGroup object.
I want to be able to .Include() optionally Include("Entities"). The closest I got is this, but that seems to eager-load all entities even if I dont use .Include("Entities"), which is not the behaviour I want:
modelBuilder.Entity<EntityGroup>()
.HasMany(x => x.Entities);
You must set off the lazy loading,
you can do this for just a specific unit of work or for all by setting your dbContext Like
dbContext.Configuration.LazyLoadingEnabled = false;
dbContext.Configuration.ProxyCreationEnabled = false;
or set it in Ctor of your DbContext.
The way I understand it, you want to configure one-to-many relationship between EntityGroup and Entity without navigation property in Entity class and using Entity.EntityGroupId as a FK. All that with Fluent API.
It's possible, but you have to start the configuration from the class having a navigation property (EntityGroup in your case) because Has methods require property accessor while With methods have parameterless overloads. As usual, for the last part you will use the HasForeignKey method:
modelBuilder.Entity<EntityGroup>()
.HasMany(e => e.Entities)
.WithRequired()
.HasForeignKey(e => e.EntityGroupId);
But note that EF recognizes the naming convention used in your sample classes (in particular the EntityGroupId), so you'll get the same mapping w/o any fluent configuration or data annotations.
The problem is lazy loading is enabled by default, so it will load the related entities every time you try to get access to them.Two options to solve your issue could be:
Disabling lazy loading in your context:
public YourContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
Removing virtual from your navigation property, which is one of
the requirements to work lazy loading and the tracking change:
public ICollection<Entity> Entities { get; set; }
If you want to learn more about the supported ways you can load related entities in EF I suggest you to read this article
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; }
}
I have two tables in a database. One is for a member and one is for a client. The client table has two columns for who created the row, and who has modified the row. Foreign keys were set up from each column to map back to the member table. All of this makes sense until
one runs Entity Framework against the database and I get the following code generated for me.
public Member()
{
public virtual ICollection<Client> Clients { get; set; }
public virtual ICollection<Client> Clients1 { get; set; }
}
public Client()
{
public virtual Member MemberForCreated { get; set; }
public virtual Member MemberForModified { get; set; }
}
My question is why would Entity Framework think to make a backing collection in the member table for each foreign key relationship to the client table? Do I really need this relationship or is this something that I can remove? Any information would be useful.
As a side note: These collections and relationships are found in the .edmx file under the navigation properties collection of the entities.
EF relationships are bidirectional by default. You can remove either direction if you don't need it.
You can also rename them. You might, e.g., want to call them Member.ClientsCreated and Member.ClientsModified.
Julie Lerman has a video examining unidirectional relationships.