I am using an existing database with EF Code First and using the modelBuilder to configure. I have two tables where a SESSION can have a SUBJECT, classes are as such:
public class SessionItem {
[Key]
public int SessionId { get;set; }
// Other Values
public int Subject_ID { get;set; }
public virtual Subject Subject { get;set; }
}
public class SubjectItem {
[Key]
public int Subject_ID { get;set; }
// Other Values
public virtual SessionItem Session { get;set; }
}
And then the modelBuilder code is:
modelBuilder.Entity<SessionItem>().ToTable("tblTblSessions");
modelBuilder.Entity<Subject>().ToTable("tblTblSubjects");
modelBuilder.Entity<SessionItem>()
.HasOptional<Subject>(u => u.Subject)
.WithOptionalDependent(c => c.Session).Map(p => p.MapKey("Subject_ID"));
This failed at first until I removed Subject_ID from the SessionItem class, then I got the error: A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.
Any idea where I have gone wrong?
Unfortunely one-to-one foreign key associations are not supported with Entity Framework because EF doesn't know what a unique key constraint is (that your Subject_ID column in the Session table apparently has).
You must workaround this by mapping the relationship as one-to-many. Follow the mapping in #flem's answer for the SessionItem entity and for the SubjectItem entity either remove the public virtual SessionItem Session { get;set; } altogether or replace it by
public virtual ICollection<SessionItem> Sessions { get; set; }
You don't need the mapping with Fluent API anymore for this relationship, or if you want, it should be:
modelBuilder.Entity<SessionItem>()
.HasOptional(se => se.Subject)
.WithMany() // or WithMany(su => su.Sessions)
.HasForeignKey(se => se.Subject_ID);
When you add items to that collection you must ensure in your business logic that you don't add more than one item because you can't have more than one row with the same Subject_ID in your database due to the unique key constraint. When you load a subject from the database including the sessions the session collection is either empty or has one single element, but not more.
Try this:
public class SessionItem
{
[Key]
public int SessionId { get;set; }
// Other Values
[ForeignKey("Subject")]
public int? Subject_ID { get;set; }
[ForeignKey("Subject_ID")]
public virtual SubjectItem Subject { get;set; }
}
You need to make Subject_ID foreign key nullable.
[Table("tblTblSessions")]
public class SessionItem {
[Key]
public int SessionId { get; set; }
public virtual SubjectItem Subject { get; set; }
}
[Table("tblTblSubjects")]
public class SubjectItem {
[Key, ForeignKey("Session")]
public int Subject_ID { get; set; }
public virtual SessionItem Session { get; set; }
}
One-to-one relationship between SubjectItem and SessionItem, and you can get rid of all your modelBuilder code - all the table naming and one-to-one mapping you were doing is taken care of with the attributes and properties above.
Edit: Fixed a typo and marked the Dependent side of the one-to-one.
Related
I have two models, One ApplicationUser which holds all users in the system and I have a Quotation model which will hold all Quotations made. now I want to store two mappings to ApplicationUser inside Quotations. So that I can map to created User as well as cancelled User. My model looks like this
public class Quotation
{
public int QuotationID { get; set; }
public DateTime QuotationDate { get; set; }
public DateTime QuotationCancelDate { get; set; }
public int ApplicationUserID { get; set; }
public virtual ApplicationUser CreatedUser { get; set; }
[ForeignKey("ApplicationUserID")]
public ApplicationUser CancelledUser { get; set; }
}
But this throws an error
Quotation_CancelledUser_Target_Quotation_CancelledUser_Source: : The types of all properties in the Dependent Role of a referential constraint must be the same as the corresponding property types in the Principal Role. The type of property 'ApplicationUserID' on entity 'Quotation' does not match the type of property 'Id' on entity 'ApplicationUser' in the referential constraint 'Quotation_CancelledUser'.
So I guess , The approach I am taking is wrong. Can anyone point out the correct way to achieve this?
The problem you are observing is called "Multiple Cascade Path". A Multiple Cascade Path happens when a cascade path goes from column col1 in table A to table B and also from column col2 in table A to table B.
The exception is caused by SQL Server when code first attempted to add table that has columns appearing more than once of another table.
In SQL Server, a table cannot appear more than one time in a list of all the cascading referential actions that are started by either a DELETE or an UPDATE statement. For example, the tree of cascading referential actions must only have one path to a particular table on the cascading referential actions tree.
You will need to use FluentAPI to configure the relationship. I am using EF5 currently and do not know if this can be accomplished in EF6/7.
So modifying your code sample, it would look like:
public class Quotation
{
public int QuotationID { get; set; }
public DateTime QuotationDate { get; set; }
public DateTime QuotationCancelDate { get; set; }
public int CreatedUserID { get; set; }
// Navigation property
public virtual ApplicationUser CreatedUser { get; set; }
public int CancelledUserID { get; set; }
// Navigation property
public virtual ApplicationUser CancelledUser { get; set; }
}
// Created a simple class for example
public class ApplicationUser
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
Now in you context class you can write:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Disable the default PluralizingTableNameConvention
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Add configuration here
modelBuilder.Entity<Quotation>()
.HasKey(e => e.QuotationID);
modelBuilder.Entity<ApplicationUser>()
.HasKey(e => e.Id);
modelBuilder.Entity<Quotation>()
.HasRequired(a => a.CreatedUser)
.WithMany()
.HasForeignKey(u => u.CreatedUserID);
modelBuilder.Entity<Quotation>()
.HasRequired(a => a.CancelledUser)
.WithMany()
.HasForeignKey(u => u.CancelledUserID);
}
For more information with example refer this link.
I have these two models:
public partial class Country
{
public Country()
{
this.Dinners = new HashSet<Dinner>();
}
public int CountryID { get; set; }
public string Name { get; set; }
public virtual ICollection<Dinner> Dinners { get; set; }
}
and
public partial class Dinner
{
public Dinner()
{
this.TRsvps = new HashSet<TRsvp>();
}
public int DinnerID { get; set; }
public System.DateTime EventDate { get; set; }
public string Description { get; set; }
public string HostedBy { get; set; }
public Nullable<int> CountryID { get; set; }
public virtual Country Country { get; set; }
}
I got a bit confused on how Entity Framework will act when a user tries to delete a parent entity (in our case it is the Country entity) that has child records (Dinners).
For example if I have the following code inside my mvc action method:-
public ActionResult DeleteConfirmed(int id)
{
Country country = db.Countries.Find(id);
db.Countries.Remove(country);
db.SaveChanges();
return RedirectToAction("Index");
}
And exception will be raised if I try to remove a country which has dinners, which sounds valid.
I tried modifying my code as follow, by including the Dinners when retrieving the Country object:
Country country = db.Countries.Include(a => a.Dinners).Single(a2 => a2.CountryId = id);
db.Countries.Remove(country);
db.SaveChanges();
return RedirectToAction("Index");
No exception will be raised, so I thought that EF would have deleted the child dinners, but what happens is that it updates the countryID FK inside the Dinners table to be null.... (Cascade Set to Null)
I tried looping over the Dinners collection as follows:
public ActionResult DeleteConfirmed(int id)
{
Country country2 = db.Countries.Find(id) ;
foreach(var d in country2.Dinners)
{
db.Dinners.Remove(d);
}
db.Countries.Remove(country2);
db.SaveChanges();
return RedirectToAction("Index");
}
but this raised the following error:
An exception of type 'System.InvalidOperationException' occurred in
System.Core.dll but was not handled in user code
Additional information: Collection was modified; enumeration operation
may not execute.
I realized that I should explicitly call the .Tolist() on the foreach to get the parent and all its children deleted as follows:
foreach(var d in country2.Dinners.ToList())
Can anyone advice if I getting things wrong, or this is the only way to support cascade on delete using EF ?
Thanks
If you want your deletes to cascade automatically, in your OnModelCreating method, you need to manually enable it:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Country>().WillCascadeOnDelete(true);
}
You are describing the documented behaviour for cascade delete when the foreign key is nullable:
If a foreign key on the dependent entity is nullable, Code First does
not set cascade delete on the relationship, and when the principal is
deleted the foreign key will be set to null.
If you want it to cascade delete then the relationship is required and the foreign key should not be nullable. Change public Nullable<int> CountryID { get; set; } to public int CountryID { get; set; }
Reference:
http://msdn.microsoft.com/en-us/data/jj591620.aspx#CascadeDelete
Additional info following your comment
You don't have to .Include to get cascade delete to work on required relationships i.e. once you have made the foreign key non-nullable. I am sure of this because that is how my application works.
I think you are observing that Remove marks your entire object graph for removal - whether required or not - in the same way that Add marks the entire graph for insertion. NB - I am not 100% sure of this bit so you should test this before you rely on it.
Further reading here:
using Dbset.Add Versus using EntityState.Added
Why Does Entity Framework Reinsert Existing Objects into My Database?
What is the difference between IDbSet.Add and DbEntityEntry.State = EntityState.Added?
I've been trying to figure out how to create a one to optional one relationship in Entity Framework but everything I try seems to result in an exception or with the navigation properties not working correctly. Could anyone explain how to annotate the below so both objects can exist in the database and a link can optionally be made from one to the other?
Many thanks
public class ObjectA
{
public int ID { get; set; }
public virtual ObjectB ObjectB { get; set; }
}
public class ObjectB
{
public int ID { get; set; }
public virtual ObjectA ObjectA { get; set; }
public int? ObjectAID { get; set; }
}
Edit:
With the code as it stands I get the following exception:
An exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll but was not handled in user code
Additional information: Unable to determine the principal end of an association between the types 'Namespace.ObjectA' and 'Namespace.ObjectB'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
You need to choose which entity will be a principal and which will be a dependent in this relationship. If it's true that ObjectB is dependent, then you could solve the problem with the following options:
1) Data Annotations. Mark ObjectB's ObjectAID property with RequiredAttribute:
public class ObjectB
{
public int ID { get; set; }
public virtual ObjectA ObjectA { get; set; }
[Required]
public int? ObjectAID { get; set; }
}
2) Fluent API. Configure Model Builder:
public class AppDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ObjectB>().HasRequired(b => b.ObjectA).WithOptional(a => a.ObjectB);
}
}
Unable to determine the principal end of an association between the
types 'XYZ.Models.Attachment' and 'XYZ.Models.Accounts.User'. The
principal end of this association must be explicitly configured using
either the relationship fluent API or data annotations.
Exception has been thrown by the target of an invocation.
That error I get, when I try to update-database with my EF Models.
Part of User.cs:
[Table("Users")]
public class User
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int UserId { get; set; }
public int MainPhotoId { get; set; }
[ForeignKey("MainPhotoId")]
public virtual Attachment Photo { get; set; }
}
Attachment.cs
[Table("Attachments")]
public class Attachment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AttachmentId { get; set; }
public string name { get; set; }
public int UserId { get; set; }
public DateTime AddDate { get; set; }
public bool del { get; set; }
[ForeignKey("UserId")]
public virtual User Author { get; set; }
}
Why I get this error? And how to resolve it?
Regards
Mapping conventions detect a one-to-one relationship between User.Photo and Attachment.Author and cannot infer what the principal and what the dependent end is. Hence the exception.
Actually, according to your comments, you want two relationships and not a single one-to-one relationship. You can achieve that only by overriding the convention with Fluent API and you probably need to make one of the relationships optional because otherwise you have a circular mutual dependency between User and Attachment. You can, for example, make the User.Photo property optional by choosing a nullable foreign key:
public int? MainPhotoId { get; set; }
Then the mapping would look like this:
modelBuilder.Entity<User>()
.HasOptional(u => u.Photo)
.WithMany()
.HasForeignKey(u => u.MainPhotoId);
modelBuilder.Entity<Attachment>()
.HasRequired(a => a.Author)
.WithMany()
.HasForeignKey(a => a.UserId);
With this mapping you can remove the [ForeignKey] attributes because the definition of the FK properties is part of the Fluent API mapping (HasForeignKey).
I have a class with a relationship to another table.
public class MyClass
{
[Key]
public Guid Id {get; set; }
public virtual OtherClass OtherClass { get; set; }
}
I hook this up to a controller and create views for CRUD - all works fine.
In the DB a OtherClass_OtherClassId column is created, but this is not in the model.
How can I put a reference in this Id column during the controller's Create method?
How can I force this relationship to be [Required] without having to create a brand new OtherClass each time?
Annotated class with some description:
public class MyClass
{
// [Key] - Don't actually need this attribute
// EF Code First has a number of conventions.
// Columns called "Id" are assumed to be the Key.
public Guid Id {get; set; }
// This reference creates an 'Independent Association'. The Database
// foreign key is created by convention and hidden away in the code.
[Required]
public virtual OtherClass OtherClass { get; set; }
// This setup explicitly declares the foreign key property.
// Again, by convention, EF assumes that "FooId" will be the key for
// a reference to object "Foo"
// This will still be required and a cascade-on-delete property
// like above - an int? would make the association optional.
public int OtherClass2Id { get; set; }
// Leave the navigation property as this - no [Required]
public virtual OtherClass2 { get; set; }
}
So which is better? Independent associations or declaring the foriegn key?
Independent associations match object programming closer. With OOP, one object doesn't really care much about the Id of a member. ORM's try to cover these relationships up, with varying degrees of success.
Declaring the foreign key puts database concerns into your model, but there are scenarios where this makes dealing with EF much easier.
Example - when updating an object with a required independent association, EF will want to have the entire object graph in place.
public class MyClass
{
public int Id {get; set; }
public string Name { get; set; }
[Required] // Note the required. An optional won't have issues below.
public virtual OtherClass OtherClass { get; set; }
}
var c = db.MyClasses.Find(1);
c.Name = "Bruce Wayne";
// Validation error on c.OtherClass.
// EF expects required associations to be loaded.
db.SaveChanges();
If all you want to do is update the name, you'll either have to pull OtherClass from the database as well since it's required for entity validation or attach a stubbed entity (assuming you know the id). If you explicitly declare foreign key, then you won't run into this scenario.
Now with foreign keys, you run into a different issue:
public class MyClass
{
public Guid Id {get; set; }
public string Name { get; set; }
public int OtherClassId { get; set }
public virtual OtherClass OtherClass { get; set; }
}
var c = db.MyClasses.Find(1);
// Stepping through dubugger, here, c.OtherClassId = old id
c.OtherClass = somethingElse;
// c.OtherClassId = old id - Object and id not synced!
db.SaveChanges();
// c.OtherClassId = new id, association persists correctly though.
In summary -
Independent associations
Good: Match OOP and POCO's better
Bad: Often requires a full object graph, even if you're only updating one or two properties. More EF headaches.
Foreign Keys
Good: Easier to work with sometimes - less EF headaches.
Bad: Can be out of sync with their object
Bad: Database concerns in your POCO's
EF generally require handholding with the model configuration. This should get you started. However doing a good tutorial on EF Code First and DB first would be greatly beneficial.
Following has:
Order with multiple OrderItems
single User
and single OrderType made by keeping the identity OrderTypeId and the actual OrderType ref object.
public class Order
{
public Order()
{
OrderItems = new OrderItemCollection();
}
public int OrderID { get; set; }
public DateTime OrderDate { get; set; }
public string OrderName { get; set; }
public int UserId { get; set; }
public virtual User OrderUser { get; set; }
public virtual OrderItemCollection OrderItems { get; set; }
public int? OrderTypeId { get; set; }
public OrderType OrderType { get; set; }
public override int GetHashCode() { return OrderID.GetHashCode();}
}
public class OrderConfiguration : EntityTypeConfiguration
{
public OrderConfiguration()
{
this.ToTable("ORDERS");
this.HasKey(p => p.OrderID);
this.Property(x => x.OrderID).HasColumnName("ORDER_ID");
this.Property(x => x.OrderName).HasMaxLength(200);
this.HasMany(x => x.OrderItems).WithOptional().HasForeignKey(x => x.OrderID).WillCascadeOnDelete(true);
this.HasRequired(u => u.OrderUser).WithMany().HasForeignKey(u => u.UserId);
this.Property(x => x.OrderTypeId).HasColumnName("ORDER_TYPE_ID");
this.HasOptional(u => u.OrderType).WithMany().HasForeignKey(u => u.OrderTypeId);
}
}
public class OrderContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderConfiguration());
}
}
'