【问题标题】:Many to many relation - have I done it right?多对多关系——我做对了吗?
【发布时间】:2013-03-17 12:08:19
【问题描述】:

这个想法很简单。我有一个标签列表。当我创建一个问题时,我想给它添加一些标签。

型号:

public class QuestionModel
{
    public int Id { get; set; }

    public String Content { get; set; }

    public ICollection<TagModeltoQuestionModel> Tags { get; set; }

    [NotMapped]
    public ICollection<TagModel> AssignedTags { get { return Tags.Select(x => x.Tag).ToList(); } }

    public int UserId { get; set; }
}

public class QuestionViewModel // helper - not in database
{
    public QuestionModel Model { get; set; }
    public ICollection<TagModel> Tags { get; set; }
}

public class TagModel
{
    public int Id { get; set; }

    public String Name { get; set; }

    public ICollection<TagModeltoQuestionModel> Questions { get; set; }

    [NotMapped]
    public bool Assigned { get; set; }

    [NotMapped]
    public ICollection<QuestionModel> AssignedQuestions { get { return Questions.Select(x => x.Question).ToList(); } }

}

public class TagModeltoQuestionModel // many to many
{
    [Key, Column(Order = 0)]
    public int TagId { get; set; }
    [Key, Column(Order = 1)]
    public int QuestionId { get; set; }

    public virtual QuestionModel Question { get; set; }
    public virtual TagModel Tag { get; set; }
}

控制器:

[HttpPost]
public ActionResult Edit(QuestionViewModel questionViewModel)
{
    if (ModelState.IsValid)
    {
        _repo.Update(questionViewModel.Model, questionViewModel.Tags); // see repo code below
        return RedirectToAction("Index");
    }
    return View(questionViewModel.Model);
}

回购:

public void Update(QuestionModel entity, ICollection<TagModel> tags)
{
    AssignTags(entity, tags);
    Db.Attach(entity);
    Db.SaveChanges();
}

private void AssignTags(QuestionModel entity, ICollection<TagModel> tags)
{
    tags = tags.Where(x => x.Assigned).ToArray(); // remove unassigned comming form View --> Controller

    var linkedTags =
        Db.TagsToQuestions.Where(x => x.QuestionId == entity.Id);
    var linkedTagsIds = linkedTags.Select(x => x.TagId);

    var selectedTagsIds = tags.Select(x => x.Id);
    var oldTags = linkedTags.Where(x => !selectedTagsIds.Contains(x.TagId));
    var newTags = tags.Where(x => !linkedTagsIds.Contains(x.Id)).Select(x=> new TagModeltoQuestionModel{QuestionId=entity.Id,TagId=x.Id});

    foreach (var t in oldTags)
        Db.Delete(t);

    foreach (var t in newTags)
        Db.Add(t);

    Db.SaveChanges();
}

这很好用,但我不确定这是否是正确的方法(实际上我自己实现了整个多对多逻辑)。有没有更聪明的方法让 EF 为我完成这项工作?我翻阅了一堆教程,但没有一个对我有用。

另外,我觉得 AssignTags 方法可以用更好的方式编写,因此任何与此相关的 cmets 也很感激。

编辑

根据haim770的回答,我按照他建议的方式简化了模型。

我的控制器现在看起来像这样:

public void Update(QuestionModel entity, ICollection<TagModel> tags)
{
    Db.Attach(entity);

    //these lines give the same result
    //var ids = tags.Select(y => y.Id).ToArray();
    //entity.Tags = Db.Tags.Where(x => ids.Contains(x.Id)).ToArray();

    tags.ForEach(x => Db.Attach(x));
    entity.Tags = tags;
    Db.SaveChanges();
}

SaveChanges 导致错误:

An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.
inner:
{"A duplicate value cannot be inserted into a unique index. [ Table name = TagModelQuestionModels,Constraint name = PK_TagModelQuestionModels ]

那么如何正确实现呢?

【问题讨论】:

  • 您的问题似乎更适合codereview.stackexchange.com * 是一个与编程相关的问答网站,您应该只针对特定代码可能遇到的问题提出特定问题。
  • @DarinDimitrov 部分你是对的。我本来可以问“如何实现多对多关系”,因为这是我的真正意思,但我包含了更具体的代码。
  • 好的,那么您在使用这段代码时遇到了什么特殊问题?什么不工作?你得到什么错误信息? * 是一个问答网站,人们可以在其中询问有关他们遇到问题的特定代码的特定问题。那你呢?您在使用此代码时遇到了哪些问题,哪些问题不起作用?如果您无法回答这些问题而只是要求进行代码审查或者是否有更好的方法来实现某些东西,那么,正如我在第一条评论中已经说过的那样,您的问题更适合codereview.stackexchange.com
  • @DarinDimitrov 我的代码问题是只有 QuestionModel 和 TagModel 都有彼此的集合(没有 TagModeltoQuestionModel)。这根本行不通——没有工作关系。我使用 TagModeltoQuestionModel 作为解决方法并将其发布以表明我在问题上付出了一些努力。还不符合条件吗? :(
  • 我引用你的问题:This works fine。所以我重复我的问题:What problems did you encounter with your code? What errors or exceptions did you encounter?.

标签: c# asp.net-mvc entity-framework asp.net-mvc-4 ef-code-first


【解决方案1】:

您不需要 TagModeltoQuestionModel 类。您可以像这样模拟many-to-many 关系:

public class QuestionModel
{
    //....
    public ICollection<TagModel> Tags { get; set; }
}

public class TagModel
{
    //....
    public ICollection<QuestionModel> Questions { get; set; }
}

Question 持有对许多 Tags 的引用,每个 Tag 持有对许多 Questions 的引用。

Entity Framework(与任何其他ORM 一样)的全部意义在于让您不必以database-like 方式建模对象及其关系,而是让您以纯Object Oriented 方式建模它然后让 ORM 做中间表、外键等的“脏活”……

【讨论】:

  • 这适用于创建操作。但是更新崩溃(myQuestion.Tags = someTags)它在 SaveChanges() 中抱怨密钥。 (它会尝试在 myQuestion.Tags 中重新添加标签,而不是更新它们)。
  • 请连同您的“更新”代码一起发布完整的例外情况。
  • 另外,我并不是说按照我推荐的方式更改实体应该立即生效,我只是概述了应该完成的方式。就您而言,在更改课程后,您是否相应地更改了数据库?您是否让 Entity Framework 为您重新创建它?
  • 是的,EF 重新接收了数据库。有关详细信息,请参阅编辑。