How do I make a one-to-one relationship in EF 7 alpha3?
The old way of just defining navigation properties does not work, and the modelBuilder does not have the previously used HasRequired/HasOptional methods.
Can anyone shed some light on that?
Until recently, there weren't any model builder APIs for defining relationships. Instead, you have to manipulate the underlying modelBuilder.Model object. Here is an example of a one-to-many relationship.
class Blog
{
public Blog()
{
Posts = new List<Post>();
}
public int Id { get; set; }
public ICollection<Post> Posts { get; set; }
}
class Post
{
public int Id { get; set; }
public int BlogId { get; set; }
public Blog Blog { get; set; }
}
class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<Post>().ForeignKeys(x => x.ForeignKey<Blog>(p => p.BlogId));
var model = builder.Model;
var blog = model.GetEntityType(typeof(Blog));
var post = model.GetEntityType(typeof(Post));
var fk = post.ForeignKeys.Single(k => k.ReferencedEntityType == blog);
blog.AddNavigation(new Navigation(fk, "Posts", pointsToPrincipal: false));
post.AddNavigation(new Navigation(fk, "Blog", pointsToPrincipal: true));
}
}
You can read more about our current (as of 2014-07-31) thinking for what these APIs will look like. The end result would look something like the following.
modelBuilder.Entity<Blog>()
.OneToMany(b => b.Posts, p => p.Blog).ForeignKey(b => b.BlogId);
With EF7 beta7, new set of methods are introduced for defining relationships between entities.
For one to many relationship,
modelBuilder.Entity<Post>()
.Reference(typeof(Blog), "Blog")
.InverseCollection("Posts")
.ForeignKey(new string[] { "BlogId" });
With, .Reference(typeof(Blog), "Blog") relationship from Entity Post to Blog is configured. First arguament is the type of the entity that Post targets and second arguament is the name of the navigation property.
With, .InverseCollection("Posts"), one to many relationship is configured. Arguament to this function is the name of the navigation collection.
With, .ForeignKey(new string[] { "BlogId" }), foreign key is configured. If this foreign key is not set, then shadow foreign key is automatically generated for you.
Related
I am using EF7 and have a scenario which needs a many to many relationship.
I have a ParticipantSIR entity and a ParticipantAssessmentReport entity. There is a link table ParticipantSIRAssessmentReport between them.
public class ParticipantSIR
{
public int ParticipantSIRID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
public virtual Participant Participant { get; set; }
}
public class ParticipantAssessmentReport
{
public int ParticipantAssessmentReportID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
}
public partial class ParticipantSIRAssessmentReport
{
public int ParticipantSIRID { get; set; }
public int ParticipantAssessmentReportID { get; set; }
public virtual ParticipantAssessmentReport ParticipantAssessmentReport { get; set; }
public virtual ParticipantSIR ParticipantSIR { get; set; }
}
modelBuilder.Entity<ParticipantSIRAssessmentReport>(entity =>
{
entity.HasKey(e => new { e.ParticipantSIRID, e.ParticipantAssessmentReportID });
entity.HasOne(d => d.ParticipantAssessmentReport).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantAssessmentReportID).OnDelete(DeleteBehavior.Restrict);
entity.HasOne(d => d.ParticipantSIR).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantSIRID).OnDelete(DeleteBehavior.Restrict);
});
This appears to be the way this needs to be setup with EF core including the third entity. I got some of the information from. http://ef.readthedocs.io/en/latest/modeling/relationships.html#many-to-many
When I insert data the 2 outside entities get populated but not the link table.
Since there are no navigation properties between the ParticipantSIR and ParticipantAssessmentReport then I'm not sure how to add the linked data.
_db.ParticipantAssessmentReport.Add(participantAssessmentReport);
foreach (var sir in participantSirs)
{
_db.ParticipantSIR.Add(sir);
}
_db.SaveChanges();
Assuming we're talking about EF Core 1.0rc1 it looks like you have created your model correctly (except the virtual keyword doesn't do anything yet as lazy loading hasn't been implemented).
As many-to-many hasn't been implemented yet as of 1.0rc1 you need to do some extra work. See https://github.com/aspnet/EntityFramework/issues/1368#issuecomment-180066124 for the classic blog Post, Tag, PostTag example code.
In your case you need to explictly add to ParticipantSIRAssessmentReport, something like this:
var participantSIRAssessmentReport = new ParticipantSIRAssessmentReport {ParticipantSIR = participantSIR, ParticipantAssessmentReport = participantAssessmentReport };
_db.ParticipantSIRAssessmentReport.Add(participantSIRAssessmentReport);
_db.SaveChanges();
To map Many-To-Many relationships in EF you need to add the following to your DbContext's OnModelCreating() method:
modelBuilder.Entity<ParticipantSIR>()
.HasMany(e => e.ParticipantSIRAssessmentReport)
.WithMany(e => e.ParticipantSIR)
.Map(e => e.ToTable("ParticipantSIRAssessmentReport") //Name of the linking table
.MapLeftKey("ParticipantSIRId") //Name of the Left column
.MapRightKey("ParticipantSIRAssessmentReportId")); //Name of the right column
From here the relationship will be handled using the Collections inside each of the classes.
So i have these two simple models
public class Blog
{
public int BlogId { get; set; }
private string _Name;
[Column("sNameColumn")]
public string Name { get { return _Name; } set { _Name = value; } }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? blog_id { get; set; }
[ForeignKey("blog_id")]
public virtual Blog Blog { get; set; }
}
I haven't done anything unusual at the dbContext definition. Then , i try to do something like this.
db.Blogs.Remove(db.Blogs.Find(2));
db.SaveChanges();
And i get a FK violation error.Notice that the FK blog_id is null-able, so i thought that EF whould handle the delete, and make the corresponding FK Null.
Can you tell me what i am missing?
The entities have to be loaded for EF to be able to handle setting their foreign key to null.
var b = db.Blogs.Find(2);
db.Entry(b).Collection(b => b.Posts).Load();
db.Blogs.Remove(b);
db.SaveChanges();
Keep in mind that Entity Framework can only update entities it has loaded.
Of course there are ways to update database records by raw SQL statements that you can also execute through EF, but that's not the core of EF as ORM.
So if you want to use EF only, you have no choice. You have to load collections in Blogs explicitly for them to get dissociated from their parent. For instance by Include:
var b = db.Blogs.Include(b => b.Posts).Include(b => b.Comments)
.Single(b => b.BlogId == 2);
db.Blogs.Remove(b);
db.SaveChanges();
Or by Load as in the other answer.
Another option could be to use Entity Framework Extented. One of its features is batch updates, which allows one-shot update statements of records in an IQueryable given a template record. This would look like this:
using EntityFramework.Extensions;
...
db.Posts.Where(p => p.BlogId == 2)
.Update(p => new Post { BlogId = default(int?) });
db.Blogs.Remove(b);
db.SaveChanges();
Only the properties that are set in the template record are modified. To make this transactional, you should wrap all statements in a TransactionScope.
I'm new in Entity Framework and my English is not very good, sorry if i write something wrong. I want to make an unidirectional association in One-to-Many relationship with Entity Framework 6 using the following example:
public class Person
{
public int personId;
public string Name;
.
.
.
//public ICollection<Phone> Phones { get; set; } //I don't want this nav property
}
public class Phone
{
public int phoneId;
public string number;
public Person myPerson { get; set; }
}
In this classes, a Person has many Phone, so a Phone has only a Person (1 to 1...*) but I want to create the navigation property in Phone, not in Person.
How to create this association with Fluent API for mappings?
Use following mapping
modelBuilder.Entity<Phone>().HasRequired(p => p.myPerson).WithMany();
HasRequired configures required relationship for phone (i.e. it's required to have person id)
WithMany() configures relationship to be required:many without navigation property on many side
Consider reading Configuring Relationships with the Fluent API article.
First of all, you need to be more clear on what you want, considering you changed your question in Sergey's answer.
Considering it, this is what your classes will look like (yes, navigation properties inheritance is supported):
public class Person
{
public int personId { get; set; }
public string Name { get; set; }
}
public abstract class Phone
{
public int phoneId { get; set; }
public int personId { get; set; }
public Person myPerson { get; set; }
}
public class PhoneFixedLine : Phone
{ }
public class PhoneCellPhone : Phone
{ }
Obs.: Mind the getters and setters in the Person and Phone classes.
You want an unidirectional mapping with the navigation property in the Phone class.
Since you are mapping derivated classes you need to follow an Inheritance Strategy, see this linkfor more information.
I will be following the TPC strategy where only the concrete classes are mapped.
In the OnModelCreatingMethod do the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PhoneFixedLine>().HasRequired(p => p.myPerson).WithMany()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("FixedPhones");
});
modelBuilder.Entity<PhoneCellPhones>().HasRequired(p => p.myPerson).WithMany()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("CellPhones");
});
}
You will face an identity problem with this strategy because these two table share the same primary key phoneId, in the link of the Inheritance Strategies there are two ways of dealing with it.
I'm new to Entity Framework and C#/.Net and trying to create a TPH inheritance model, I'm not sure if I should be or not, so if not, please advise,
Here's the model:
public abstract class Vote
{
public int VoteID { get; set; }
public int UserID { get; set; }
public bool Value { get; set; }
public DateTime DateCreated { get; set; }
public User User { get; set; }
}
public class ProjectVote_ : Vote
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
public class CommentVote_ : Vote //There are three more like this, votes for different hings
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
Now the Project model (comment and model is similar)
public class Project
{
public int ProjectID { get; set; }
public string Title { get; set; }
public virtual ICollection<Vote> Vote { get; set; }
}
What happens is that ICollection creates a database column Project_ProjectID as the foreign key in the Vote table (I think) instead of using the ProjectID I defined. How do I fix it or should I model it differently. If the fluent API is the way to fix it, I don't know how to do that.
In the end I want to be able to use one table to store 5 different types of votes.
When you have related entities you don't need to have a property to store the FK in your model. Entity framework knows that it needs to make a FK to the Project table in ProjectVote when it detects Project in your ProjectVote_ model. Same thing with User and UserId and Comment and CommentId. You don't need to have a property that stores the FK in your model.
You are getting the FK column with the name you don't like "Project_ProjectID" because Entity framework is detecting that it needs to create a FK for your navigation property "Project". It's using it's own naming convention to create the column hence "Project_ProjectID".
If you want to provide your own name for the column override OnModelCreating in your DBContext class and add this fluent mapping.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasMany(p => p.Vote)
.HasRequired(v => v.Project) //or .WithOptional(v => v.Project)
.Map(m => m.MapKey("ProjectId")); //or any other name you want.
}
And for the future this is a helpful reference for how to use the Fluent API. For example here is some documentation on how to custimize TPH with fluent.
Hope that helps!
I have two tables that are built using codefirst entity framework.
public class TimeEntry:Entity
{
[Required]
[Display(Name = "Activity")]
public int ActivityId { get; set; }
public virtual Activity Activity { get; set; }
}
public class Activity:Entity
{
private ICollection<TimeEntry> _timeEntries;
[Required]
public string Description { get; set; }
public virtual ICollection<TimeEntry> TimeEntries
{
get
{
return _timeEntries ?? (_timeEntries = new List<TimeEntry>());
}
set
{
_timeEntries = value;
}
}
}
public class Entity
{
public int Id { get; set; }
}
These are the classes I have created for my Db. There is no problem with creating the database. When I try to perform CRUD operations I get the error
DataBinding: 'System.Data.Entity.DynamicProxies.Activity_AD12BF558F098271F1F51B3B1489B4B3B281FD0B686C8457333DE5BEE0E8B6A9' does not contain a property with the name 'ActivityId'
It is trying to find ActivityId in the Activity table however the primary key is Id. How do I map the foreign key ActivityId in the TimeEntry table to the primary key Id in the Activity table.
You can use fluent api to let EF know about you mappings.
public class ActivityMap : EntityTypeConfiguration<Activity>
{
public ActivityMap()
{
this.HasKey(a => a.Id);
}
}
public class TimeEntryMap : EntityTypeConfiguration<TimeEntry>
{
public TimeEntryMap()
{
this.HasKey(t => t.Id);
// Relationships
this.HasRequired(t => t.Activity)
.WithMany(t => t.TimeEntries)
} .HasForeignKey(d => d.ActivityId);
}
Then in your context:
public class MyDbContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new ActivityMap());
modelBuilder.Configurations.Add(new TimeEntryMap());
}
}
I think this will solve your issue.
Also, (as a side note) instead of defining _timeEntries, you can use auto implemented property for TimeEntries and initialize it in you ctor. like below:
public class Activity:Entity
{
public virtual ICollection<TimeEntry> TimeEntries { get; set; }
public Activity()
{
this.TimeEntries = new List<TimeEntry>();
}
}
hi i have the same problem
If one specifies DataKeyNames property as ID and the actual column name is CustomerID. It will throw the above error.
If one specifies DataTextField or DataValueField property as ID and the actual column name is CustomerID. It will throw the above error.
and found the answer here it work for me link
If you are using Code First, you need to indicate the mapping of ActivityId => Id by overriding OnModelCreating in your DbContext.
At a suggestion, it seems you are mixing the concerns of DTO and MVC ViewModel in the same entity. Why not separate these concerns into 2 different entities?