【问题标题】:AutoMapper is giving invalid operation exceptionAutoMapper 给出无效操作异常
【发布时间】:2017-05-26 15:32:50
【问题描述】:

在我的编辑页面上,我收到了这个错误:

附加一个类型的实体失败,因为另一个相同类型的实体已经具有相同的主键值。

所以,我对此进行了研究并来到this

这让我想使用 Auto-Mapper 来简化事情。

这是我的代码:

if (db.TableName.Find(value.ID).stringProperty.Equals(value.stringProperty, StringComparison.CurrentCultureIgnoreCase))
{
    Table inContextVariable = db.TableName.Find(value.ID);

    Mapper.Initialize(config => config.CreateMap<ModelName, ModelName>());

    Mapper.Map<ModelName, ModelName>(value, inContextVariable);

    db.Entry(inContextVariable).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
}

这导致我出现此错误:

附加信息:操作失败:无法更改关系,因为一个或多个外键属性不可为空。当对关系进行更改时,相关的外键属性将设置为空值。如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象。

这个表有 1 个外键,当我调试时外键的值为 11,所以我很难理解这个错误。


更新


想象一下,如果我有一个数据库表,其中包含包含某人信息的记录,例如:

|    ID    |    First Name    |    Last Name    |          Email           |
----------------------------------------------------------------------------
     1           John                Doe             john.doe@test.com
     2           Christopher       Columbus     |  chris.columbus@test.com |

等等等等..现在,由于一个人只能有一个电子邮件地址,因此您必须在其他用户创建/编辑记录时采取保护措施..

例如,如果我要使用此应用程序并想创建一个帐户。我应该无法使用数据库表中已存在的电子邮件地址成功填写表单。我应该收到一条错误消息说类似

该电子邮件地址已经存在!请输入另一个电子邮件地址

代码如下:

if (db.TableName.Any(x => x.Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase)))
{
    ModelState.AddModelError("Email", "This email already exists!");
    return View(value);
}

现在....当用户成功创建一个帐户..然后去编辑他们的帐户电子邮件地址到一个已经存在的东西时会发生什么?应该会出现相同的错误消息。

但是当该用户创建他们的帐户时,他们的电子邮件地址现在在数据库表中。所以当他们进入编辑页面时,使用他们的原始电子邮件,他们点击“保存”,代码将根据数据库中的所有电子邮件地址检查该电子邮件,它会看到它已经在数据库中,导致显示错误消息(请更改您的电子邮件。..等)..

因此,我需要尝试创建一种有效的方法来检查何时用户在 edit 页面上,并使用他们的原始页面点击“保存”电子邮件,它不会告诉他们更改电子邮件。

所以我有这个代码:

if (db.TableName.Find(value.ID).Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase))
{
    var inContextVariable = db.TableName.Find(value.ID);
    MappingMethods.MapModelName(inContextVariable , value);

    \\ without the mapping.. I receive the error saying 'Attaching an entity of type failed because another entity of the same type already has the same primary key value.'

    db.Entry(inContextVariable).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
}

【问题讨论】:

  • 我建议您在启动时而不是在使用时初始化您的映射。我们通常在 Startup.cs 中运行AutoMapperConfig.Configure() 方法,该方法会初始化映射。
  • 我认为您的根本问题与 EF 的关系比任何事情都重要。尝试创建一个视图模型来容纳从 inContextVariable 到 vew 模型变量的副本和映射。然后,您可以在其他地方使用该变量而不必担心 EF。 (假设这是目标。)
  • @nurdyguy 你能提供代码以便我更好地理解吗?
  • @nurdyguy 所以创建一个与ModelName具有完全相同属性的视图模型?
  • 请给我们看一下ModelName类,肯定是你映射后的外键属性为空

标签: c# asp.net-mvc automapper


【解决方案1】:

这是我将在这里使用的基本逻辑

Table inContextVariable = db.TableName.Find(value.ID);
if(!inContextVariable.Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase))
{
    // user input a new email so make sure it doesn't match anyone else
    if(db.TableName.Any(x => x.Email.Equals(value.Email, StringComparison.CurrentCultureIgnoreCase) && x.ID != value.ID))
    {
        // user's new email DOES match someone else's email so handle it
    }
    else
    {
        // no match so do the update

    }

}

无需处理外部 else,因为他们没有更改电子邮件,因此您无需执行任何操作!

注意:您将在此处出现竞态条件!如果两个人几乎同时提交,那么支票可能会通过,但他们最终都会收到相同的电子邮件。就像假设他们都更改为 x@y.com 但第二个人的检查是在第一个人的保存之前完成的。但是,这就是网络应用的生活。

【讨论】:

  • 任何记录的方式来处理竞争条件?即使,对于这个特定的应用程序来说,可能性极小?
  • 好吧,可能性与应用程序用户数量有关。一个大型应用程序绝对会遇到这种情况。我认为您最好的选择是在 db 列上设置一个唯一约束并将其包装在 try-catch 中。但是,我相信其他人会不同意这一点。伙计,比赛条件很糟糕......
  • 嗯,我敢打赌他们会这样做。我想当我负责构建这样一个应用程序时,我会更多地研究它。
【解决方案2】:

在@nurdyguy 的帮助下,我提出了这个想法。我创建了另一个名为 MappingMethods 的类,它包含此方法:

public static void MapModelName(ModelName inContext, ModelName outOfContext)
{
    inContext.ID = outOfContext.ID;
    inContext.stringProperty= outOfContext.stringProperty;
    inContext.OwnerID = outOfContext.OwnerID; \\ foreign key
    inContext.DateCreated = outOfContext.DateCreated;
}

然后在我的编辑操作中:

if (db.TableName.Find(value.ID).stringProperty.Equals(value.stringProperty, StringComparison.CurrentCultureIgnoreCase))
{
    var inContextVariable = db.TableName.Find(value.ID);
    MappingMethods.MapModelName(inContextVariable , value);

    db.Entry(inContextVariable).State = EntityState.Modified;
    db.SaveChanges();
    return RedirectToAction("Index");
}

这行得通。

【讨论】:

  • @AlexOvechkin 解释?
  • @BviLLe_Kid 您是在尝试将新行保存到数据库还是更新现有行?
  • @nurdyguy 更新现有行。这是Edit 操作,所以我没有创建任何新内容。
  • @nurdyguy 但是当用户在编辑页面上单击“保存”但没有编辑任何内容时会发生什么?这就是它的用途,因为就像我上面所说的那样,我有另一个 if 语句检查 stringProperty 是否已经存在于数据库中的任何记录中..所以没有我的答案..如果用户点击保存并没有'不编辑任何内容,否则将被阻止,因为它已经存在
【解决方案3】:

automapper 的整个想法是避免在静态类中使用此类方法。想象一下,如果你有 100 个属性? (仅举例)。你会复制粘贴obj1.prop1 = obj2.prop1?

首先我建议创建一个 view-model - smth。这将代表您的模型的视图。是的,您可以使用自动映射器创建对象的副本,但 它不是为它设计的。

试着像这样做:

  1. 创建一个视图模型 - 它可以与您的模型相同或不同 - 这完全取决于您(将数据库模型传递到视图中不是一个好习惯)
  2. 将您的模型映射到 view-model 和 virce-verce,如下所示:

    Mapper.CreateMap() .ForMember(d => d.DateCreate, d => d.MapFrom(x => x.DateCreated));

【讨论】:

  • 我 100% 同意您对 AutoMapper 及其用途的解释。 AutoMapper 非常适合这种事情。对于 OP 的实际问题,这完全没有必要。
  • @nurdyguy 我不认为我们在同一页上.....让我用我试图解释的if 声明来更新我的问题。
  • @BviLLe_Kid 哦!!!大声笑...我们只需要检查字符串匹配的唯一时间是 id 匹配的时间。只需添加到您的.Any(...)。所以像.Any(x =&gt; x.string1 == inputString &amp;&amp; x.Id != inputId) 这样你就知道匹配的字符串来自两个不同的人。
  • @nurdyguy 并删除包含映射方法的整个if 语句?
  • @nurdyguy 成功了!哈哈花了一段时间来解释,但值得!谢谢!
猜你喜欢
  • 2010-10-02
  • 1970-01-01
  • 1970-01-01
  • 2015-05-06
  • 1970-01-01
  • 2017-03-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多