I need to map a many-to-many relationship using Entity Framework Code First. Its a standard socialnetworking FriendRequests mapping. A User Object has a Collection of FriendRequests of type List<User>. In the database I'm using a join table for the relationship as follows:
CREATE TABLE dbo.FriendRequests(
UserId INT NOT NULL FOREIGN KEY REFERENCES dbo.Users(id),
FriendId INT NOT NULL FOREIGN KEY REFERENCES dbo.Users(id),
RequestDate SMALLDATETIME NOT NULL DEFAULT GETUTCDATE())
GO
ALTER TABLE dbo.FriendRequests ADD PRIMARY KEY (UserId,FriendId)
GO
How do I map the user object in Entity Framework Code First to enable a Collection via a join table?
You can try it this way:
public class User
{
public int Id { get; set; }
public ICollection<FriendRequest> FriendRequests { get; set; }
}
public class FriendRequest
{
public int UserId { get; set; }
public int FriendId { get; set; }
public DateTime RequestDate { get; set; }
public User User { get; set; }
public User Friend { get; set; }
}
Mapping with Fluent API:
modelBuilder.Entity<User>()
.HasMany(u => u.FriendRequests)
.WithRequired(f => f.User)
.HasForeignKey(f => f.UserId);
modelBuilder.Entity<FriendRequest>()
.HasKey(f => new { f.UserId, f.FriendId });
modelBuilder.Entity<FriendRequest>()
.HasRequired(f => f.Friend)
.WithMany()
.HasForeignKey(f => f.FriendId);
Because of the RequestDate property in the link table you cannot map this as a many-to-many relationship. Instead you need two one-to-many relationships as shown above.
Possibly you need to disable cascading delete, I am not sure. You can do this by appending .WillCascadeOnDelete(false) at the end of the two one-to-many mappings.
Edit
To your comment: If you remove the RequestDate column you can create your model with a many-to-many relationship. You don't need the FriendRequest entity in this case:
public class User
{
public int Id { get; set; }
public ICollection<User> FriendRequests { get; set; }
}
Mapping with Fluent API:
modelBuilder.Entity<User>()
.HasMany(u => u.FriendRequests)
.WithMany()
.Map(m =>
{
m.ToTable("FriendRequests");
m.MapLeftKey("UserId");
m.MapRightKey("FriendId");
});
Related
This is what i am trying to achieve :
modelBuilder.Entity<ApplicationUser>()
.HasMany(u => u.Following)
.WithMany(u => u.Followers)
.Map(m =>
{
m.ToTable("FollowTables");
m.MapLeftKey("UserId");
m.MapRightKey("FollowId");
});
In application user class, i have configured following and followers like this:
public ICollection<ApplicationUser> Following { get; set; }
public ICollection<ApplicationUser> Followers { get; set; }
follow table should be something like this:
public class FollowTable
{
[Key]
public int autoId { get; set; }
public int UserId { get; set; }
public int? FollowId { get; set; }
}
autoId is primary key and UserId and FollowId both are foreign key to ApplicationUser class where UserId is user's own id and FollowId are the ids which user is following.Its data could be following:
autoId UserId FollowId
1 4 11
2 4 12
3 4 13
Now, i problem is when i update database through pmc, it is creating two database tables one is FollowTables with column (USerId, FollowId) and one is FollowTables1(autoId, USerId, FollowId).
If i remove this line from applicationDbContext class:
public DbSet<FollowTable> FollowTables { get; set; }
then its creating only one table but with no primary key.
please someone help me out . how to properly configure UserId and followId as foreign key and these two should map to ApplicationUser's Id.
I want to use those Collection's following and Followers too.how to do it.
You have to decide if you want to work with an entity that represents the junction table or not. If you don't need to add any other properties to that table, excluding the FKs, then I suggest you don't map the junction table as entity. It is going to be more easy for you due to Entity Framework will handle that table for you.
Now if you really need to map that table then you need to delete many-to-many fluent api configuration and change the type of your navigation properties:
public ICollection<FollowTable> Following { get; set; }
public ICollection<FollowTable> Followers { get; set; }
That is going to create two one-to-many relationships with the junction table, an explicit representation of a many-to-many relationship. To do that you also need to do some changes in that entity:
public class FollowTable
{
[Key]
public int autoId { get; set; }
public int UserId { get; set; }
[ForeignKey("User")]
public ApplicationUser User{ get; set; }
[ForeignKey("Follow")]
public int? FollowId { get; set; }
public ApplicationUser Follow{ get; set; }
}
Also, I don't think FollowId Fk property should be a nullable FK, because you want to represent a relationship between two persons.
If you ask my opinion about what option you should take, I suggest you don't map the junction table if you are going to have only those properties.
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.
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 2 tables:
1) Parent
2) Child
In codefirst I have the following definition:
public class Parent
{
public int ParentId { get; set; }
public ICollection<Child> child { get; set; }
}
However in the db, the child table has the following foreign key defined:
ParentParentId (FK, int, NULL)
How do I ensure it just specifies ParentId in the foreign key? Do I need to explicitly set the parent key using the fluent configuration?
Yes you must either include Foreign key property in the child entity:
public class Child
{
public int ChildId { get; set; }
public int ParentId { get; set; } // FK
public virtual Parent { get; set; }
}
Or you must rename the column with fluent API:
modelBuilder.Entity<Child>()
.HasRequired(c => c.Parent)
.WithMany(p => p.Childs)
.Map(m => m.MapKey("ParentId"));
I'm using the CTP 5 of EF 4 and Code first.
I don't get a many-many relation working with a composite key on one side.
modelBuilder.Entity<Item>()
.HasMany(i => i.Categories)
.WithMany(o => o.Items)
.Map(
mc =>
{
mc.ToTable("ItemCategories");
mc.MapLeftKey(i => i.Id, "ItemId");
mc.MapRightKey(o => o.TemplateID, "TemplateId");
mc.MapRightKey(o => o.ItemId, "ItemId");
}
);
So instead of having a simple key for Categories in my matching table, I've got a composite one. And one part of the composite key is also the key for the Item type,
which seems to be the problem here.
I get the error: "Each property name in a type must be unique. Property name 'ItemId' was already defined."
How can I configure EF to use a composite key in this case?
Of course you cannot have 2 columns with the same name within one table. This will work:
modelBuilder.Entity<Item>()
.HasMany(i => i.Categories)
.WithMany(c => c.Items)
.Map(m =>
{
m.MapRightKey(i => i.Id, "ItemId");
m.MapLeftKey(c => c.ItemId, "ItemId2");
m.MapLeftKey(c => c.TemplateId, "TemplateId");
});
public class Category
{
[Key]
public string ItemId { get; set; }
[Key]
public string TemplateId { get; set; }
public string Value { get; set; }
public ICollection<Item> Items { get; set; }
}
public class Item
{
public string Id { get; set; }
public string Name { get; set; }
public ICollection<Category> Categories { get; set; }
}
The mapping table ItemCategories is not a POCO, but used for mapping those 2 as
shown.
It has SQL columns
Id (own primary key)
ItemId (FK to Item table and Category table)
TemplateId (FK to Category table)
and another ID column which maps to a different table.
In my opinion the only difference here to "normal" many-many scenario is the composite key
in the ItemCategories table, which builds the relation to the Category table.