LINQ无法从模型绑定更新实体

时间:2011-04-29 14:20:23

标签: 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)
    {
        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);线。控制器和存储库之间是服务层,因此我不直接调用存储库。我认为这比在存储库中混合数据访问,验证和业务逻辑更好。

目前,_service中的 - not working - 方法是:

public void Update(UserHour userHour)
{
    CRMDataContext c = new CRMDataContext();
    c.Refresh(RefreshMode.KeepCurrentValues, userHour);
    c.SubmitChanges();
}

我已经尝试了所有c.Attach(...)调用可能和刷新调用的所有内容,但是我得到了各种异常,比如附加一个已经存在密钥的对象。

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

是否有可能在控制器中使用模型绑定创建userHour,为其提供userHour的身份,它实际上是更新并将其存储在数据库中?它现在整天都在困扰着我。

非常感谢任何帮助!

3 个答案:

答案 0 :(得分:0)

如果您正在使用视图模型或演示模型,那么您将不得不处理所有丑陋的左/右分配代码。它没有好办法。这就是DTO的本质。他们添加了一层抽象,很多时候他们都很有帮助,但是你必须处理后果。

但是,您的示例似乎表明您的数据库与视图之间存在一对一的关系。我们可以使用Attach()来利用它。这是如何做到的。

  • 确保您的表具有时间戳列。如果没有时间戳列,L2S在跟踪更新方面做得不是很好。

示例:

timestamp column

  • 在您的视图中,确保您将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();
}

答案 1 :(得分:0)

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

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

我知道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 });
    }
}

使用此扩展程序后,您的更新功能应如下所示:

public void Update(UserHour userHour)
{
    CRMDataContext c = new CRMDataContext();
    c.UserHour.UpdateOnSubmit(userHour, new string[] { "Id", "Duration", "HourCategoryId", "Explanation", "InvoiceTypeId", "StartDate", "StartTime", "TicketId" });
    c.SubmitChanges();
}

如果要更新所有属性,则可以省略具有属性名称的数组。

答案 2 :(得分:0)

我会跟@Jarett说,如果可行的话。

真正的问题是L2S隐藏了对象本身的实体状态,因此当你连接它时,它认为它是一个全新的实体。如果它知道更好的话,L2S就不会杀死这个交易。这就是为什么@ Jarett的选择可能会有效,因为它内置于L2S中。

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

一个可能的选择是切换到PLINQO,这可以克服L2S缺少一个超过它的datacontext的实体。它仍然是L2S的更多功能。很遗憾,您需要CodeSmith才能使用PLINQO。