Moses Machua
Moses Machua

Reputation: 11522

EF 6 Primary key violation with Many To Many relationship

I'm new to the world of 'Entity Framework' and 'Code First' so bear with me. I have 2 entities Articles and Tags. The TagName column in Tags has to be unique as it's the key. I'm having a primary key violation issue when an existing tag is used in subsequent articles. My entities look as follows

public class Article
{
    public Article()
    {
    }
    public int Id { get; set; }
    [StringLength(100)]
    [Required]
    public string Title { get; set; }
    [Required]
    public string Body { get; set; }
    public virtual List<Tag> Tags { get; set; }

}

and

public class Tag
{
    public Tag()
    {
    }
    public Tag(string tagName)
    {
        TagName = tagName;
    }
    [Key]
    [StringLength(25)]
    public string TagName { get; set; }
    public virtual List<Article> Articles { get; set; }

    public override string ToString()
    {
        return this.TagName;
    }
}

EF creates 3 tables for Articles, Tags and TagArticles relationships

Here's my code to save the article

var editedTags = Request["Tags"];
if (!string.IsNullOrEmpty(editedTags))
{
  var tags = Regex.Split(editedTags, "\r\n");
  if (tags != null && tags.Length > 0)
  {
    foreach (var tag in tags)
    {
      article.Tags.Add(new Tag(tag));
    }
  }
}
//db is my context
db.Articles.Add(article);
db.SaveChanges(); //Crashes here if the tag already exists

Aside from creating the tags manually, is there a way to tell EF to check if a tag exists first before creating it so to avoid duplication?

Upvotes: 1

Views: 1176

Answers (2)

Moses Machua
Moses Machua

Reputation: 11522

Found a solution. For the Tag class, I added an int column Id and made it the primary key. I then updated the database to reflect the changes. Here it is:

public class Tag
{
    public Tag()
    {
    }
    public Tag(string tagName)
    {
        TagName = tagName;
    }
    [Key]
    public int Id { get; set; }
    [Required]
    [StringLength(25)]
    public string TagName { get; set; }
    public virtual List<Article> Articles { get; set; }

    public override string ToString()
    {
        return this.TagName;
    }
}

I added a function that adds new tags to the article

private Article ParseTags(Article article)
{
  var strTags = Request["Tags"];
  if (!string.IsNullOrEmpty(strTags))
  {
    var arrTags = Regex.Split(strTags, "\r\n");
    if (arrTags != null && arrTags.Length > 0)
    {
      foreach (var tagName in arrTags)
      {
        var tag = db.Tags.FirstOrDefault(t => t.TagName.ToLower() == tagName.ToLower());
        if (tag == null)
          article.Tags.Add(new Tag(tagName));
        else
          article.Tags.Add(tag);
      }
    }
  }
  return article;
}

Finally the Create action

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include="Title,Body")] Article article)
{
  if (ModelState.IsValid)
  {               
    db.Articles.Add(ParseTags(article));
    db.SaveChanges();
    return RedirectToAction("Index");
  }
  return View(article);
}

Now only new tags are created and existing ones are not duplicated and the relationship are properly created in the TagsArticles join table.

Upvotes: 1

Jignesh Suvariya
Jignesh Suvariya

Reputation: 424

Replce following line of your code

foreach (var tag in tags)
    {
      article.Tags.Add(new Tag(tag));
    }

With

foreach (var tag in tags)
{

Tag objTag = db.Tags.SingleOrDefault(c => c.TagName == tag);

if (!(objTag != null && objTag.TagName == tag))
    objTag = new Tag(tag);

article.Tags.Add(objTag);

}

Upvotes: 0

Related Questions