仅使用引用的ICollection而不是创建副本/克隆

时间:2013-09-30 13:33:43

标签: entity-framework ef-code-first entity-framework-5 entities

我的调查包含问题以及哪些用户可以/将参与调查。

喜欢这样

public virtual ICollection<User> ParticipatingUsers { get; set; }
public virtual ICollection<Question> SpecificQuestions { get; set; }

然而,由于ajaxy解决方案,我首先创建问题,然后简单地使用调查数据发送我创建的问题的ID。所以我需要做的就是更改问题的sortingIndex,然后在我的调查中添加对它的引用。

对于用户而言,他们属于公司实体,我只想从调查中引用它们而不是拥有它们。

但是目前我在我的操作方法(.net mvc)中获得问题和用户的所有ID,所以我现在加载所有问题和用户,并在将调查发送到存储库之前将它们附加到我的调查实体。

但是当我的Repository在dbset上调用Add时,它会克隆用户和问题数据,而不是简单地引用现有数据。

我迷路了,我通过添加[Foreignkey]解决了正常导航属性的这个确切问题,但我不知道这对ICollection有什么作用

为了完整性

这是我接收数据的行动方法

    [HttpPost]
    [FlexAuthorize(Roles = "RootAdmin")]
    public ActionResult SaveSurvey(EditSurveyViewModel editModel)
    {
        if (!ModelState.IsValid)
        {
            //We dont bother to send this in so we need to fetch the list again
            editModel.CompanyList = _companyRepository.GetAll();

            List<string> deletionList = new List<string>();

            //We clear out all questions from the state as we have custom logic to rerender them with the correct values
            foreach (var modelstateItem in ModelState)
            {
                if (modelstateItem.Key.StartsWith("Questions"))
                {
                    deletionList.Add(modelstateItem.Key);
                }
            }

            foreach (string key in deletionList)
            {
                ModelState.Remove(key);
            }

            return View("EditSurvey", editModel);
        }

        List<Question> questionlist = new List<Question>();
        int sort = 1;
        Question q;

        //We have questions sent in from the ui/client
        if (editModel.Questions != null)
        {
            //Go trough each questions sent in
            foreach (var question in editModel.Questions)
            {
                //if it's a page break, just assign our new question the sent in one and set sort index
                if (question.IsPageBreak)
                {
                    q = question;
                    q.SortIndex = sort;
                }
                else 
                {
                    //It's a question and all questions are already created with ajax from the client
                    //So we simply find the question and then set sort index and tie it to our survey
                    q = _questionRepository.GetById(question.Id);
                    q.SortIndex = sort;
                }

                questionlist.Add(q);
                sort++;
            }
        }
        //assign the new sorted questions to our Survey
        editModel.Item.SpecificQuestions = questionlist;

        List<User> userlist = new List<User>();

        foreach (int id in editModel.SelectedUsers)
        {
            userlist.Add(_userRepository.GetById(id));
        }

        editModel.Item.ParticipatingUsers = userlist.ToList();

        _surveyRepository.SaveSurveyBindAndSortQuestionsLinkUsers(editModel.Item);

        return RedirectToAction("Index");
    }

以下是该方法在

中发送的视图模型
public class EditSurveyViewModel
{
    public Survey Item { get; set; }
    public IEnumerable<Question> Questions { get; set; }
    public bool FullyEditable { get; set; }
    public IEnumerable<Company> CompanyList { get; set; }
    public IEnumerable<int> SelectedUsers { get; set; }
}

最后这里是repo方法(到目前为止我只实现了插入,而不是更新)

    public void SaveSurveyBindAndSortQuestionsLinkUsers(Survey item)
    {
        if (item.Id == 0)
        {
            Add(item);
        }
               ActiveContext.SaveChanges();
     }

更新/修改

Moho:你当然是对的,我觉得我很惭愧,我正在测试一些东西,忘记重置方法,然后在这里粘贴它。 我已经更新了上面的操作方法。

Slauma:很抱歉没有细节,这里有更多。

我的所有存储库都是这样的

public class EFSurveyRepository : Repository<Survey>, ISurveyRepository

因此,他们继承了通用存储库并实现了一个接口 通用存储库(我们在上面的代码中使用的部分,看起来像这样)

public abstract class Repository<T> : IRepository<T> where T : class
{
    public EFDbContext ActiveContext { get; private set; }
    private readonly IDbSet<T> dbset;

    public Repository()
    {
        this.ActiveContext = new EFDbContext("SurveyConnection");
        dbset = ActiveContext.Set<T>();
    }

    public virtual void Add(T entity)
    {
        dbset.Add(entity);
    }
    public virtual T GetById(int id)
    {
        return dbset.Find(id);
    }

我在数据库中注意到我的User表(对于User实体)现在包含一个我不希望它具有的Survey_Id字段。我想要一个多对多,许多调查可以链接到许多用户(相同的用户),但用户应该在实体方面仍然只属于公司的部门。

此外,现在当我运行代码时(在我更正了我的操作方法之后),我收到以下错误:

An entity object cannot be referenced by multiple instances of IEntityChangeTracker.

没有InnerException,只是当我尝试添加新调查时。

2 个答案:

答案 0 :(得分:1)

您构建questionList,将其设置为editModel.Item.SpecificQuestions,然后通过将同一属性设置为editModel.Questions.ToList()来覆盖引用,该属性来自您的视图模型(即:未通过您的加载数据库上下文,如questionList的问题对象),因此似乎是对数据库上下文的新问题,

    editModel.Item.SpecificQuestions = questionlist;

    // what is this?  why?
    editModel.Item.SpecificQuestions = editModel.Questions.ToList();

问题更新后编辑:

不要使用questionList并分配Survey的questions属性,只需直接使用该属性即可。

此外,您是否意识到,如果您正在重复使用数据库中的Question条记录多个Survey,那么您将更新问题本身的排序顺序,而不仅仅是Survey Question 1}?每次您保存重新使用问题的新调查时,我都会更改其他调查的问题排序。看起来您需要一个将Survey映射到{{1}}的关系实体,您还可以在其中存储排序顺序,以便每个调查都可以重复使用问题实体,而不会弄乱现有的调查问题排序。

答案 1 :(得分:1)

问题是您每个存储库使用单独的上下文:

public Repository()
{
    this.ActiveContext = new EFDbContext("SurveyConnection");
    //...
}

在您的POST操作中,您有四个存储库:_companyRepository_questionRepository_userRepository_surveyRepository。这意味着您正在使用四种不同的上下文,即从不同的上下文加载数据,在附加到不同上下文的实体之间创建关系,并将数据保存在另一个上下文中。

这就是数据库中实体重复的原因,“ IEntityChangeTracker的多个实例”异常,并且将成为您将来可能遇到的许多其他问题的来源。

您必须重构体系结构,以便在每个存储库中仅使用一个相同的上下文实例(“工作单元”),例如将其注入构造函数而不是创建新的:

private readonly EFDbContext _activeContext;
private readonly IDbSet<T> _dbset;

public Repository(EFDbContext activeContext)
{
    _activeContext = activeContext;
    _dbset = activeContext.Set<T>();
}