matt
matt

Reputation: 749

Entity Framework - Inserting model with many to many mapping

How can I insert a model Tag that belongs to a model Post when I have the models setup like this:

Post

public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Tag> Tags { get; set; }
    public Post()
    {
       Tags = new List<Tag>();
    }
}

Tag

public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
}

This question suggests to create a Post object then add Tags to the Tags collection, I couldn't get it working: Insert/Update Many to Many Entity Framework . How do I do it?

I want to add Tag to Post already in the database, how can I do that with EF. I'm new to EF.

This is what I've tried, if I send this to the API it doesn't insert any records and I can see that the new tag Id = 0 which doesn't exist in the database, but I'd think that'd cause a foreign key constraint error, not sure If I need to do something to auto generate Id for the tag:

{
    Name: "test"
}

API

[ResponseType(typeof(Tag))]
public IHttpActionResult PostTag(Tag tag)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    var post = new Post();
    var tags = new List<Tag>();
    tags.Add(tag);

    post.Tags.Add(tag);
    post.Id = 10;
    db.Entry(post).State = EntityState.Modified;
    db.SaveChanges();

    return CreatedAtRoute("DefaultApi", new { id = tag.Id }, tag);
}

Upvotes: 1

Views: 306

Answers (1)

Aria
Aria

Reputation: 3844

If you said there is Many-To-Many relation which the PostTag is connection table between Tag and Post then your models don't show any many-to-many relation, so from what I have seen there is one-to-many between Post and Tag because of your model definition.

if you want to make many-to-many relation between them you have to something like below:

public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
    public Tag()
    {
        Posts = new HashSet<Post>();
    }
} 
public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Tag> Tags { get; set; } 
    public Post()
    {
        Tags = new HashSet<Tag>();
    }
}

and in OnModelCreating make relation by fluent api as below :

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{ 
    modelBuilder.Entity<Tag>()
        .HasMany(s => s.Posts)
        .WithMany(c => c.Tags)
        .Map(cs =>
        {
            cs.MapLeftKey("TagId");//TagId
            cs.MapRightKey("PostId");//PostId
            cs.ToTable("PostTag");
        });
}

or vice versa

modelBuilder.Entity<Post>()
    .HasMany(s => s.Tags)
    .WithMany(c => c.Posts)
    .Map(cs =>
        {
            cs.MapLeftKey("PostId");//PostId
            cs.MapRightKey("TagId");//TagId
            cs.ToTable("PostTag");
        });

as you can see and know there should be a table named PostTag in database which have two columns as keys which have a script like :

CREATE TABLE [dbo].[PostTag](
    [TagId] [int] NOT NULL,
    [PostId] [int] NOT NULL,
 CONSTRAINT [PK_PostTag] PRIMARY KEY CLUSTERED 
(
    [TagId] ASC,
    [PostId] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] 

ALTER TABLE [dbo].[PostTag]  WITH CHECK ADD  CONSTRAINT [FK_PostTag_Post] FOREIGN KEY([PostId])
REFERENCES [dbo].[Post] ([Id])
GO 
ALTER TABLE [dbo].[PostTag] CHECK CONSTRAINT [FK_PostTag_Post]
GO 
ALTER TABLE [dbo].[PostTag]  WITH CHECK ADD  CONSTRAINT [FK_PostTag_Tag] FOREIGN KEY([TagId])
REFERENCES [dbo].[Tag] ([TagId])
GO 
ALTER TABLE [dbo].[PostTag] CHECK CONSTRAINT [FK_PostTag_Tag]
GO

take a look at here for more info.

UPDATE:

if you want establish zero-to-many relation between Post and Tag then the models should be like below:

public class Tag
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int? PostId { get; set; }
}
public class Post
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Tag> Tags { get; set; }
    public Post()
    {
        Tags = new HashSet<Tag>();
    }
}

and one to many relation with fluent api :

modelBuilder.Entity<Post>()
   .HasMany(o1 => o1.Tags);

as your comment you don't want Tag have navigate property so you should define a property as Nullable in Tag which is feign key, if there is a relation between them you should establish relation at least by navigate property or Nullable property.

The correct answer came from here (with no change): You want to assign a tag to exist post you should find that Post firstly then add a tag to it, if that tag does exist in DB a relation will be made between Tag and found Post if that Tag does not exist then Tag will be inserted to DB,

Take a look at this :

var post = context.Posts.SingleOrDefault(x => x.Id == 4);//That Id would be you specific Post Id
var existTag = context.Tags.SingleOrDefault(x => x.Id == 1); //Exist Tag in DB
post.Tags.Add(existTag);
context.SaveChanges();
//Inserting new tag to DB and assign it to Post also
Tag newTag = new Tag // Does not exist in DataBase
{
    Name = "tag2"
};
post.Tags.Add(newTag);// By this tag2 will be insert into Tag table first and then added to post (insert new record to PostTag)
context.SaveChanges();

Upvotes: 1

Related Questions