Asp.net mvc LINQ无法从模型绑定更新实体

Asp.net mvc LINQ无法从模型绑定更新实体,asp.net-mvc,linq,extjs,Asp.net Mvc,Linq,Extjs,我有一个带有ExtJs的编辑表单,但是ExtJs与这个问题无关。提交时,我将表单绑定到模型 public ActionResult EditUserHour([Bind(Include = "Id, Duration, HourCategoryId, Explanation, InvoiceTypeId, StartDate, StartTime, TicketId")] UserHour userHour) { if (ModelState.IsValid) {

我有一个带有ExtJs的编辑表单,但是ExtJs与这个问题无关。提交时,我将表单绑定到模型

public ActionResult EditUserHour([Bind(Include = "Id, Duration, HourCategoryId, Explanation, InvoiceTypeId, StartDate, StartTime, TicketId")] UserHour userHour)
{
    if (ModelState.IsValid)
    {
        try
        {
            _service.Update(userHour);
        }
        catch (RuleException ex)
        {
            ex.CopyToModelState(ModelState, string.Empty);
        }

    }
    if (ModelState.IsValid)
        return Json(new { success = true, redirect = Url.Action(ListAction) });
    else
        return Json(new { success = false, errors = ModelState.ToDictionary() });
}
查看_service.Update(userHour);线路。控制器和存储库之间是一个服务层,因此我不直接调用存储库。我认为这比在数据库中混合数据访问、验证和业务逻辑更好。 目前,\ u服务中的-not working-方法是:

public void Update(UserHour userHour)
{
    CRMDataContext c = new CRMDataContext();
    c.Refresh(RefreshMode.KeepCurrentValues, userHour);
    c.SubmitChanges();
}
我尝试了所有可能的c.Attach(…)调用和refresh调用,但是我遇到了各种异常,比如附加一个已经存在密钥的对象

我遇到的一个可能的解决方案是从datacontext检索原始实体并简单地设置所有属性,但这远不是一个简洁的解决方案。第二种选择是使用FormCollection映射到原始实体,但出于安全原因,我避免使用FormCollection,而更喜欢使用模型绑定。此外,我无法想象模型绑定与更新不兼容

是否有可能使用模型绑定在控制器中创建userHour,为其提供实际更新的userHour的标识,并将其存储在数据库中?到现在为止,它已经困扰了我一整天了


非常感谢您的帮助

如果您使用的是视图模型或表示模型,那么您将不得不处理所有难看的左/右赋值代码。没有什么好办法。这就是DTO的本质。它们增加了一层抽象,很多时候它们是有用的,但是你必须处理后果

然而,您的示例似乎表明您的数据库和视图之间存在一对一的关系。我们可以使用Attach()利用这一点。下面是如何做到这一点

  • 确保您的表具有时间戳列。如果没有timestamp列,L2S就无法很好地跟踪更新
例如:

@Html.HiddenFor(x => x.Id)
@Html.HiddenFor(x => x.Timestamp)
public void Update(UserHour userHour)
{
  var ctx = new CRMDataContext();
  ctx.UserHours.Attach(userHour, true);
  ctx.SubmitChanges();
}

  • 在您的视图中,确保ID和时间戳都是隐藏属性。当您重新附着对象时,您将需要这两个
例如:

@Html.HiddenFor(x => x.Id)
@Html.HiddenFor(x => x.Timestamp)
public void Update(UserHour userHour)
{
  var ctx = new CRMDataContext();
  ctx.UserHours.Attach(userHour, true);
  ctx.SubmitChanges();
}
  • 在控制器或服务中,“连接”现在可以工作
例如:

@Html.HiddenFor(x => x.Id)
@Html.HiddenFor(x => x.Timestamp)
public void Update(UserHour userHour)
{
  var ctx = new CRMDataContext();
  ctx.UserHours.Attach(userHour, true);
  ctx.SubmitChanges();
}

正如您所说,唯一的方法是获取原始对象并更新其属性

您可以实现Data.Linq.Table的通用扩展,该扩展接受模型绑定对象,并通过其主键从DataContext检索原始项。在此之后,使用refrect迭代所有属性,并将新值设置为检索到的实体。如果不是所有属性都通过模型绑定绑定,因此设置为默认值,则可以提供一个要更新的属性数组

我知道Refelction不是一个好的解决方案,但它有时会有帮助

public static class TableExtensions
{
    public static void UpdateOnSubmit<T>(this Table<T> table, T changes) where T : class
    {
        UpdateOnSubmit(table, changes, null);
    }

    public static void UpdateOnSubmit<T>(this Table<T> table, T changes, string[] properties) where T : class
    {
        var item = table.FirstOrDefault(GetPkExpression(table, changes));
        if (item != null)
        {
            UpdateItem<T>(ref item, changes, properties);
        }
    }

    private static void UpdateItem<T>(ref T original, T changes, string[] properties) where T : class
    {
        Type OriginalType = typeof(T);

        if (properties == null)
        {
            PropertyInfo[] Info = OriginalType.GetProperties();

            foreach (PropertyInfo PI in Info)
            {
                if (IsUpdateableColumn(PI))
                {
                    PI.SetValue(original, PI.GetValue(changes, null), null);
                }
            }
        }
        else
        {
            foreach (string propName in properties)
            {
                PropertyInfo PI = OriginalType.GetProperty(propName);

                if (PI != null && IsUpdateableColumn(PI))
                {
                    PI.SetValue(original, PI.GetValue(changes, null), null);
                }
            }
        }
    }

    private static bool IsUpdateableColumn(PropertyInfo pi)
    {
        object[] attributes = pi.GetCustomAttributes(typeof(ColumnAttribute), true);

        if (attributes.Length == 0)
            return false;

        foreach (ColumnAttribute attr in attributes)
        {
            if (attr.IsDbGenerated)
                return false;
        }

        return true;
    }

    private static Expression<Func<T, bool>> GetPkExpression<T>(Table<T> table, T value) where T : class
    {
        var mapping = table.Context.Mapping.GetTable(typeof(T));
        var pk = mapping.RowType.DataMembers.FirstOrDefault(d => d.IsPrimaryKey);

        if (pk == null)
        {
            throw new Exception(string.Format("Table {0} does not contain a Primary Key field", mapping.TableName));
        }

        var pkValue = typeof(T).GetProperty(pk.Name).GetValue(value, null);

        var param = Expression.Parameter(typeof(T), "e");
        return Expression.Lambda<Func<T, bool>>(Expression.Equal(Expression.Property(param, pk.Name), Expression.Constant(pkValue)), new ParameterExpression[] { param });
    }
}

如果所有属性都需要更新,您可以省略带有属性名的数组。

如果可行,我会选择@Jarett says

真正的问题是L2S对对象本身隐藏了实体状态,因此即使附加它,它也会认为它是一个全新的实体。如果它知道更好的L2S不会终止事务,这就是为什么@Jarett的选项可能会起作用,因为它内置于L2S中

这是L2S的一个问题,所以除非你改变你的ORM(这可能不是一个选项),你必须处理它的怪癖

一个可能的选择是切换到它,以克服L2S缺少超出其datacontext的实体。它仍然是L2S,只是更多的功能。不幸的是,您需要使用PLINQO