实体框架:跟踪独立关联的变化

时间:2011-06-14 15:24:50

标签: entity-framework

实体框架可以轻松跟踪两个实体之间关系的变化。以下内容将返回所有已添加和已删除关系的ObjectStateEntry实例:

ObjectStateManager.GetObjectStateEntries(
                EntityState.Added | EntityState.Deleted)
                .Where(e => e.IsRelationship);

出于审计目的,我想检索有关每个关联结束的以下信息:

  • 导航属性名称(如果有)
  • 多个

对于添加的关联,ObjectStateEntry实例包含关系中涉及的两个实体的EntityKey对象:

var key0 = entry.CurrentValues[0] as EntityKey;
var key1 = entry.CurrentValues[1] as EntityKey;

它还引用代表当前关系的类型:

var relationshipType = objectStateEntry.EntitySet.ElementType;

我要做的是将每个实体键(key0,key1)映射到关联的相应末尾(relationshipType.KeyMembers)。据我所知,唯一可行的方法是查看每个关联端的引用类型,并将其与实体键所代表的实体类型相匹配。但是,如果关联的两端引用相同的实体类型(认为员工实体与经理FK引用另一个员工),这将不起作用。

所以这里是帮助函数,我到目前为止得到导航属性名称和关系结束的多样性。还有更好的方法吗?

public static string GetNavigationPropertyName(
        this ObjectStateEntry entry, EntityKey key)
{
    var relationshipType = entry.EntitySet.ElementType;
    var entitySet = key.GetEntitySet(
            entry.ObjectStateManager.MetadataWorkspace);
    var property = entitySet.ElementType.NavigationProperties.Where(
            p => p.RelationshipType == relationshipType)
            .SingleOrDefault();

    if (property == null)
    {
        return null;
    }

    return property.Name;
}

public static RelationshipMultiplicity GetAssociationEndMultiplicity(
        this ObjectStateEntry entry, EntityKey key)
{
    var relationshipType = entry.EntitySet.ElementType;

    var entitySet = key.GetEntitySet(
            entry.ObjectStateManager.MetadataWorkspace);

    foreach (EdmMember member in relationshipType.KeyMembers)
    {
        var refType = member.TypeUsage.EdmType as RefType;
        if (refType.ElementType == entitySet.ElementType)
        {
            var relEndMember = member as RelationshipEndMember;
            return relEndMember.RelationshipMultiplicity;
        }
    }
    throw new InvalidOperationException(
            "couldn't find association end");
}

欢迎任何建议/想法。

谢谢, 丹尼尔

2 个答案:

答案 0 :(得分:3)

不应查看实体键,而应从对象状态条目中提取关联结束。我在丹尼尔西蒙斯的博客http://blogs.msdn.com/b/dsimmons/archive/2008/01/17/ef-extension-methods-extravaganza-part-ii-relationship-entry-irelatedend.aspx中看到了一个暗示。以下是我提出的一些扩展方法(UsableValues也来自Daniel的博客):

public static AssociationEndMember[] GetAssociationEnds(
        this ObjectStateEntry entry)
{
    var fieldMetadata = 
            entry.UsableValues().DataRecordInfo.FieldMetadata;

    return fieldMetadata.Select(
            m => m.FieldType as AssociationEndMember).ToArray();
}

public static AssociationEndMember GetOtherAssociationEnd(
        this ObjectStateEntry entry, AssociationEndMember end)
{
    end.ValidateBelongsTo(entry);
    AssociationEndMember[] ends = entry.GetAssociationEnds();
    if (ends[0] == end)
    {
        return ends[1];
    }
    return ends[0];
}

public static EntityKey GetEndEntityKey(
        this ObjectStateEntry entry, AssociationEndMember end)
{
    end.ValidateBelongsTo(entry);

    AssociationEndMember[] ends = entry.GetAssociationEnds();

    if (ends[0] == end)
    {
        return entry.UsableValues()[0] as EntityKey;
    }

    return entry.UsableValues()[1] as EntityKey;
}

public static NavigationProperty GetNavigationProperty(
        this ObjectStateEntry entry, AssociationEndMember end)
{
    end.ValidateBelongsTo(entry);

    var otherEnd = entry.GetOtherAssociationEnd(end);
    var relationshipType = entry.EntitySet.ElementType;
    var key = entry.GetEndEntityKey(end);            
    var entitySet = key.GetEntitySet(
            entry.ObjectStateManager.MetadataWorkspace);
    var property = entitySet.ElementType.NavigationProperties.Where(
            p => p.RelationshipType == relationshipType &&
                    p.FromEndMember == end && p.ToEndMember == otherEnd)
            .SingleOrDefault();
    return property;
}

ValidateBelongsTo扩展方法(UsableValue是Daniel博客的另一种扩展方法):

static void ValidateBelongsTo(
        this AssociationEndMember end, ObjectStateEntry entry)
{
    if (!entry.IsRelationship)
    {
        throw new ArgumentException("is not a relationship entry", "entry");
    }

    var fieldMetadata =
            entry.UsableValues().DataRecordInfo.FieldMetadata;
    if (fieldMetadata[0].FieldType as AssociationEndMember != end &&
        fieldMetadata[1].FieldType as AssociationEndMember != end)
    {
        throw new InvalidOperationException(string.Format(
                "association end {0} does not participate in the " +
                "relationship {1}", end, entry));
    }
}

答案 1 :(得分:0)

选择的答案对我不起作用,这可能是因为我正在使用继承。以下对LeffeBrune的回答的修改在Entity Framework 6.1中为我解决了这个问题:

public static NavigationProperty GetNavigationProperty(this ObjectStateEntry entry, AssociationEndMember end)
{
    end.ValidateBelongsTo(entry);

    var otherEnd = entry.GetOtherAssociationEnd(end);
    var relationshipType = entry.EntitySet.ElementType;
    var entitySet = end.GetEntityType();
    var property = entitySet.NavigationProperties
                            .SingleOrDefault(p => p.RelationshipType == relationshipType &&
                                                  p.FromEndMember == end &&
                                                  p.ToEndMember == otherEnd);
    return property;
}

原始问题是从密钥检索的EntitySet是基类的EntitySet,这意味着NavigationProperties集不包含子类中的任何属性。

我还需要其他一些东西,所以因为我花了很长时间才能完成所有这些工作,所以我将其余部分张贴在后代:

internal void GetPropertyNames(ObjectContext ctx, ObjectStateEntry entry,
                                out string fromPropName, out string toPropName,
                                out object sourceEntity, out object destEntity)
{
    var valueCollection = entry.UsableValues();

    EntityKey entityKeyFrom = (EntityKey) valueCollection[1];
    EntityKey entityKeyTo = (EntityKey) valueCollection[0];

    ObjectStateEntry sourceEntry = ctx.ObjectStateManager.GetObjectStateEntry(entityKeyFrom);
    ObjectStateEntry destEntry = ctx.ObjectStateManager.GetObjectStateEntry(entityKeyTo);
    sourceEntity = sourceEntry.Entity;
    destEntity = destEntry.Entity;

    Debug.Assert(sourceEntity != null || destEntity != null, "Wait, what?  One of these MUST exist.");

    AssociationEndMember[] ends = entry.GetAssociationEnds();

    Debug.Assert(ends.Length == 2);

    NavigationProperty sourceProp = entry.GetNavigationProperty(ends[1]);
    NavigationProperty destProp = entry.GetNavigationProperty(ends[0]);

    fromPropName = sourceProp.Name;
    toPropName = destProp.Name;

    if (fromPropName == null && toPropName == null)
    {
        AssociationSet assoc = (AssociationSet)entry.EntitySet;
        AssociationType assocType = (AssociationType)assoc.ElementType;

        throw new Exception(String.Format("Couldn't find at least one property for relationship \"{0}\"", assocType.Name));
    }
}