“EntityCollection 已初始化” Asp.Net MVC 中实体作为模型时出错?

发布于 2024-10-19 08:09:37 字数 4140 浏览 2 评论 0原文

我在创建一个复杂的对象时遇到了很大的困难。我有一个带有顾问表的 EF 模型,该表与许多其他表具有一对多关系。我想按原样使用 Consultant 对象作为模型,因为它非常简单(正如 NerdDinner 教程中所做的那样),就像我对其他没有一对多关系的对象所做的那样。问题是,当我尝试发布到 Create 方法时,这些关系会导致此错误:“EntityCollection 已初始化”。

有几个人建议我改用 ViewModel,我发布了一个关于此的问题 (ViewModels 以及与 MVC 中的实体框架的一对多关系?)因为我不太理解它。问题是代码变得非常荒谬...这与我迄今为止所欣赏的 MVC 的简单性相去甚远。

在那个问题中,我忘记提及除了 Create 方法之外,Edit 方法还使用相同的 CreateConsultant 方法(名称可能会产生误导,它实际上填充 Consultant 对象)。因此,为了不在编辑时添加额外的“程序”,我需要使该方法进一步复杂化。所以现在看起来是这样的:

private Consultant CreateConsultant(ConsultantViewModel vm, Consultant consultant) //Parameter Consultant needed because an object may already exist from Edit method.
        {
            consultant.Description = vm.Description;
            consultant.FirstName = vm.FirstName;
            consultant.LastName = vm.LastName;
            consultant.UserName = User.Identity.Name;

            if (vm.Programs != null)
                for (int i = 0; i < vm.Programs.Count; i++)
                {
                    if (consultant.Programs.Count == i)
                        consultant.Programs.Add(vm.Programs[i]);
                    else
                        consultant.Programs.ToList()[i] = vm.Programs[i];
                }
            if (vm.Languages != null)
                for (int i = 0; i < vm.Languages.Count; i++)
                {
                    if (consultant.Languages.Count == i)
                        consultant.Languages.Add(vm.Languages[i]);
                    else
                        consultant.Languages.ToList()[i] = vm.Languages[i];
                }
            if (vm.Educations != null)
                for (int i = 0; i < vm.Educations.Count; i++)
                {
                    if (consultant.Educations.Count == i)
                        consultant.Educations.Add(vm.Educations[i]);
                    else
                        consultant.Educations.ToList()[i] = vm.Educations[i];
                }
            if (vm.WorkExperiences != null)
                for (int i = 0; i < vm.WorkExperiences.Count; i++)
                {
                    if (consultant.WorkExperiences.Count == i)
                        consultant.WorkExperiences.Add(vm.WorkExperiences[i]);
                    else
                        consultant.WorkExperiences.ToList()[i] = vm.WorkExperiences[i];
                }

            if (vm.CompetenceAreas != null)
                for (int i = 0; i < vm.CompetenceAreas.Count; i++)
                {
                    if (consultant.CompetenceAreas.Count == i)
                        consultant.CompetenceAreas.Add(vm.CompetenceAreas[i]);
                    else
                        consultant.CompetenceAreas.ToList()[i] = vm.CompetenceAreas[i];
                }

            string uploadDir = Server.MapPath(Request.ApplicationPath) + "FileArea\\ConsultantImages\\";
            foreach (string f in Request.Files.Keys)
            {
                var filePath = Path.Combine(uploadDir, Path.GetFileName(Request.Files[f].FileName));
                if (Request.Files[f].ContentLength > 0)
                {
                    Request.Files[f].SaveAs(filePath);
                    consultant.Image = filePath;
                }

            }
            return consultant;
        }

这是荒谬的,这可能是由于我的无能,但我需要知道如何正确地做到这一点。仅回答“使用 ViewModel”显然是不够的,因为这就是让我陷入困境的原因。我希望简单的实体对象作为模型的简单性,但没有“EntityCollection 已初始化”错误。我该如何解决这个问题?

当然,如果我只是以错误的方式执行 ViewModel 策略,也欢迎提出建议,但主要是我想知道如果我以简单的“NerdDinner”简单对象方式执行此操作,会导致此错误。另请记住,相关视图仅限于网站的授权用户。我很乐意以“正确”的方式做到这一点,但如果使用 ViewModels 意味着代码很难维护,我会放弃它......

请帮忙!

更新:

事实证明这段代码根本不起作用。我只是在调用 Edit 更新值后进行了检查,但它没有更新它们。所以只有创建部分有效。

这是不起作用的部分:

consultant.Programs.ToList()[i] = vm.Programs[i];

我有一种预感,我无法使用 ToList 并更新 EntityCollection 中的项目。但这使得事情变得更加困难。所以现在我不知道如何直接对实体进行操作,我更喜欢这样做(见上文)。我不知道如何让这个 ViewModel 的东西工作,更不用说让它干净了......

有什么想法吗?这里一定有什么真正的错误,我希望有人会发现我是如何错过了一些简单的东西,使所有这些代码都发生了转变!

I'm having great difficulties with creating a complex object. I have an EF model with a Consultant table that has one-to-many relationships to a number of other tables. I wanted to use the Consultant object as the model as is because it would be very simple and easy (and as it's done in the NerdDinner tutorial), as I've done with other objects that didn't have one-to-many relationships. The problem is that these relationships cause this error: "EntityCollection already initialized" when I try to post to the Create method.

Several people have advised me to use a ViewModel instead, and I posted a question about this (ViewModels and one-to-many relationships with Entity Framework in MVC?) because I don't really understand it. The problem is the code gets reeeeally ridiculous... It's a far cry from the simplicity I've so far appreciated in MVC.

In that question I forgot to mention that besides the Create method, the Edit method uses the same CreateConsultant method (the name may be misleading, it actually populates the Consultant object). And so in order not to have additional, say "Programs" added when editing, I needed to complicate that method further. So now it looks like this:

private Consultant CreateConsultant(ConsultantViewModel vm, Consultant consultant) //Parameter Consultant needed because an object may already exist from Edit method.
        {
            consultant.Description = vm.Description;
            consultant.FirstName = vm.FirstName;
            consultant.LastName = vm.LastName;
            consultant.UserName = User.Identity.Name;

            if (vm.Programs != null)
                for (int i = 0; i < vm.Programs.Count; i++)
                {
                    if (consultant.Programs.Count == i)
                        consultant.Programs.Add(vm.Programs[i]);
                    else
                        consultant.Programs.ToList()[i] = vm.Programs[i];
                }
            if (vm.Languages != null)
                for (int i = 0; i < vm.Languages.Count; i++)
                {
                    if (consultant.Languages.Count == i)
                        consultant.Languages.Add(vm.Languages[i]);
                    else
                        consultant.Languages.ToList()[i] = vm.Languages[i];
                }
            if (vm.Educations != null)
                for (int i = 0; i < vm.Educations.Count; i++)
                {
                    if (consultant.Educations.Count == i)
                        consultant.Educations.Add(vm.Educations[i]);
                    else
                        consultant.Educations.ToList()[i] = vm.Educations[i];
                }
            if (vm.WorkExperiences != null)
                for (int i = 0; i < vm.WorkExperiences.Count; i++)
                {
                    if (consultant.WorkExperiences.Count == i)
                        consultant.WorkExperiences.Add(vm.WorkExperiences[i]);
                    else
                        consultant.WorkExperiences.ToList()[i] = vm.WorkExperiences[i];
                }

            if (vm.CompetenceAreas != null)
                for (int i = 0; i < vm.CompetenceAreas.Count; i++)
                {
                    if (consultant.CompetenceAreas.Count == i)
                        consultant.CompetenceAreas.Add(vm.CompetenceAreas[i]);
                    else
                        consultant.CompetenceAreas.ToList()[i] = vm.CompetenceAreas[i];
                }

            string uploadDir = Server.MapPath(Request.ApplicationPath) + "FileArea\\ConsultantImages\\";
            foreach (string f in Request.Files.Keys)
            {
                var filePath = Path.Combine(uploadDir, Path.GetFileName(Request.Files[f].FileName));
                if (Request.Files[f].ContentLength > 0)
                {
                    Request.Files[f].SaveAs(filePath);
                    consultant.Image = filePath;
                }

            }
            return consultant;
        }

This is absurd, and it's probably due to my incompetence, but I need to know how to do this properly. Just the answer "use a ViewModel" obviously won't suffice, because that's what got me into this trouble to begin with. I want the simplicity of the simple entity object as model but without the "EntityCollection has already been initialized" error. How do I get around this?

Of course, if I'm just doing the ViewModel strategy the wrong way, suggestions on that are welcome too, but mainly I want to know what is causing this error if I do it the simple "NerdDinner" simple object way. Please keep in mind also that the View in question is restricted to authorized users of the site. I would love to do it the "correct" way, but if using ViewModels implies having code that is this hard to maintain, I'll forgo it...

Please help!

UPDATE:

Turns out this code doesn't even work. I just checked after calling Edit to update values, and it doesn't update them. So only the Create part works.

This is the part that doesn't work:

consultant.Programs.ToList()[i] = vm.Programs[i];

I sort of had a hunch I couldn't use ToList and update an item in the EntityCollection. But this makes it even harder. So now I don't know how to do it with the entity directly, which I would prefer (see above). And I don't know how to get this ViewModel stuff working, let alone get it clean...

Any ideas? There must be something really wrong here, and I'm hoping someone will spot how I've just missed something simple that turns all of this code on its head!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

南城旧梦 2024-10-26 08:09:37

好吧,根据我的经验,EF 的东西在网络上使用时不容易很好地发挥作用,句号(也就是说,无论您是在 MVC 还是 WCF 或其他环境中,它都不是传递数据的好格式)。对我来说,这是一个 EF 问题,而不是 MVC 问题,因为您在直接使用 EF 模型时遇到的问题与它们在 EF 对象上下文中的跟踪方式有关。有人告诉我,有一些解决方案涉及重新附加传递的实体,但我发现这比它的价值更麻烦;和其他人一样,这就是为什么大多数人在这种情况下说“使用视图模型”作为输入参数。

我同意你上面的代码有点令人不快,有几种方法可以修复它。首先,查看 AutoMapper,它有很大帮助,因为您可以跳过定义明显的内容(例如,当两个模型都有一个简单的属性,例如相同类型的“名称”,

其次,您所做的循环有点不对劲,您不应该假设从客户端发布的列表是相同的名称,等等。作为 EF 正在跟踪的列表(而是列出;指的是您的程序等)。相反,请考虑可能发生的情况并考虑这些情况,我将使用程序作为示例

1) 顾问有一个程序 。 ,但有关该计划的详细信息已更改。
2) 顾问在视图模型中没有程序
2a) 该程序已经存在于数据库中的某个地方,只是不属于该顾问的一部分
2b) 该程序是新的。
3)顾问有一个不在视图模型中的程序(您当前没有考虑到的东西)。

我不知道在每种情况下您希望发生什么(即视图模型是完整且规范的,还是更新期间的实体模型?),但假设您正在循环查看视图模型的程序,因为您做上面的。对于场景 1(这似乎是您的主要问题),您将需要执行类似的操作(使用 AutoMapper):

var updated = vm.Programs[i];    
var original = consultant.Programs.SingleOrdefault(p=>p.ID == uppdated.ID);
Mapper.Map(updated,original);
yourEfContext.SaveChanges(); // at some point after this, doesn't have to be inside the loop

编辑:
另一种可能对您有用的想法是,当您从数据库中获取顾问时(不确定您使用什么机制),请确保您也对要更新的集合调用 Include 。否则,您所做的每一次迭代都将是到数据库的另一次往返,如果您只是使用 Include 一次性加载它们,显然您可以避免这种情况。

Ok, so in my experience the EF stuff doesn't easily play nicely when used on the wire, full stop (meaning, it's not a good format for passing data in whether you're in MVC or WCF, or whatever). It's an EF issue, to me, not a MVC issue because the problem you're experiencing with the direct use of your EF models has to do w/ how they're tracked in the EF objectcontext. I've been told that there's solutions to that involving re-attaching the passed entity, but I've found it to be more trouble than it's worth; as have others which is why most folks say "use viewmodels" for your input parameters in this situation.

I agree that your code above is kind unpleasant, there's a couple ways to fix it. First, check out AutoMapper, which helps a lot in that you can skip defining the obvious (e.g. when both models have a simple property like "Name" of hte same type & name.

Second, what you're doing w/ the loops is a bit off anyway. You should not assume that your list being posted from the client is the same order, etc as the list that EF is tracking (lists, rather; referring to your Programs, etc). Instead, think of the scenarios that might occur and account for those. I'll use Programs as an example.

1) a consultant has a program, but details about that program have changed.
2) a consultant does not have a program that is in the viewmodel
2a) the program already exists in the database somewhere, just not part of this consultant
2b) the program is new.
3) a consultant has a program that's not in the viewmodel (something you don't currently account for).

I don't know what you want to have happen in each of those cases (i.e. is the viewmodel complete and canonical, or is the entity model, during an update?), but let's say you're looping over your viewmodel's Programs as you do above. For the scenario 1 (which seems to be your main problem), you will want to do something like (using AutoMapper):

var updated = vm.Programs[i];    
var original = consultant.Programs.SingleOrdefault(p=>p.ID == uppdated.ID);
Mapper.Map(updated,original);
yourEfContext.SaveChanges(); // at some point after this, doesn't have to be inside the loop

EDIT:
one other thought that might be useful to you is that I when you fetch your consultant out of the database (not sure what mechanism you use for that), make sure you call Include on the collections that you're going to update as well. Otherwise, each of those iterations you do will be another round-trip to the database, which obviously you could avoid if you just used Include to eager-load them all in one shot.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文