如何在泛型方法中导航子实体框架对象?

时间:2010-11-12 18:04:51

标签: entity-framework asp.net-mvc-2 generics

我的存储库中有一个通用方法,用于更新edmx模型中所有对象的公共属性:

    private void SetUpdateParams(TEntity entity)
    {
        PropertyInfo prop = typeof(TEntity).GetProperty("CommonProperty");

        prop.SetValue(entity, "Some Value", null);
    }

此属性由add,update和delete方法调用。例如:

    public void Delete(TEntity entity)
    {
        SetUpdateParams(entity);
        _objectSet.DeleteObject(entity);
        txDB.SaveChanges();
    }

这一切都非常好用,直到我尝试将子项包含在级联删除场景中。由于我必须使用的sprocs要求设置此特定属性,我现在必须通过关系递归并在ObjectSet中的任何已加载子项上设置此属性。问题是我似乎无法找到任何方法来做到这一点。有没有人以前做过这样的事情?

1 个答案:

答案 0 :(得分:1)

这不是最简单的解决方案,可能有点乏味,但我通过跨越对象图,在我找到它时更新属性,并跟踪我的内容来实现您在我的项目中所需的内容参观。它为本科计算机科学课提供了很好的回忆。 =)

基本上,获取您的对象,获取其属性,并将它们推送到堆栈上。对于堆栈中的每个属性,请测试它是否是您要查找的属性。处理如果匹配,则忽略简单数据类型,如果是复杂对象则添加到堆栈。

在我的实施中帮助我的一些事情:

  • 创建一个包含您正在查找的属性的接口,并使所有具有该属性的类实现它。它可以更容易地用反射查询你的类。
  • 尝试使用要遍历的堆栈上的复杂属性类型变得聪明。我使用了一个接口,指定此复杂类型可能具有需要更新的属性。
  • 使用某些东西(我使用HashSet)标记您看过的对象并帮助防止循环引用遍历。
  • 我将此功能作为BaseContext的一部分(继承EF的ObjectContext)自定义Save()方法。我打电话给这个修正,然后拨打base.SaveChanges()

就像我说的,这不是一个简单直接的问题,但我的代码处理深层对象图并更新多个属性实例。如果有兴趣,我可以跟进一些伪代码。


修改/更新

如代码所示,我有兴趣更新可能出现在图表中的任意数量对象的当前UserName和DateTime值。

备注:

  • 接口IInsertedInfo包含我们需要更新的属性。因此,如果对象实现IInsertedInfo,我们就知道要更新其属性。
  • 实现IRequiresCurrentUserDateTime的对象是在其图形中的某个位置具有实现IInsertedInfo的对象的对象。
  • 这是在使用EF保存的情况下完成的。我正在查询需要保存/更新的对象的ObjectStateManager

我并不认为这是最简洁的解决方案,但它对我来说很好。此外,检查IRequiresCurrentUserDateTime和其他过滤器(为清楚起见省略了一些过滤器)仅用于保持搜索空间的可管理性,并避免跨越.NET对象,非类类型等。

private void HandleInsertedUserNames(string userName)
{
    // grab any entity that requires a current user value
    var requiresUser = this.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified)
                                              .Where(ose => ose.Entity is IRequiresCurrentUserDateTime
                                                            || ose.Entity is IInsertedInfo)
                                              .Select(ose => ose.Entity);

    var now = DateTime.Now;
    object current;
    var seen = new HashSet<object>();
    var stack = new Stack<object>();
    // for each entity requiring a current user value...
    foreach (var obj in requiresUser)
    {
        // traverse its object graph and update any objects that implement IRequiresCurrentUserDateTime
        stack.Push(obj);
        while (stack.Count > 0)
        {
            current = stack.Pop();
            if (current != null && !seen.Contains(current))
            {
                // mark object as seen
                seen.Add(current);
                // if object implements IInsertedInfo, then set its property
                if (current is IInsertedInfo)
                {
                    (current as IInsertedInfo).UserName = userName;
                    (current as IInsertedInfo).DateTime = now;
                    // we can continue on to the next object in the stack if we've hit an IInsertedInfo
                    continue;
                }

    // REMOVED FILTERING TESTS I USED TO REDUCE SEARCH SPACE (e.g.: is modified?)

                if (current is IRequiresCurrentUserDateTime)
                {
                    // push any instance, public properties and push them to the stack
                    current.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public)
    // HERE YOU CAN FILTER THE PROPERTY COLLECTION VIA WHERE CLAUSES (e.g.: only certain namespace, type, etc)
                           // select the actual value of the property
                           .Select(type => type.GetValue(current, null))
                           // further filter -- only values NOT already in the requiresUser
                           // list and those that implement IRequiresCurrentUserDateTime or IInsertedInfo
                           .Where(value => !requiresUser.Contains(value)
                                           && (value is IRequiresCurrentUserDateTime
                                               || value is IInsertedInfo))
                           .ToList().ForEach(stack.push);
                }
            }
        }
    }

}