EF 代码优先,复杂实体的深层拷贝,"ID is part of objects key info and cannot be modified" - 错误



我正在尝试编写通用实体框架代码首先复制例程。此例程会复制源属性,创建新的儿童实体,复制对查找实体的参考并复制子收集。它似乎有效。在检查创建的实体时,所有孩子都存在,但是当我调用dbcontext.changetracker.haschanges((时,我会收到以下错误;

'The property 'ID' is part of the object's key information and cannot be modified. '

在调查中,似乎是由新创建的儿童实体(而不是新创建的根实体/父级(提出的错误

这是相关部分填充的例程(if(dest.propertyhascustomatomattribute(propertyInfo.name,typeof(entityChildAttribute(((;(;(

    public static void CloneCopy<T>( T original, object destination) where T : class
    {
        var dest = destination as T;
        if (dest == null)
            throw new Exception("destination does not match source type");

        PropertyInfo[] props = typeof(T).GetProperties();
        foreach (var propertyInfo in props)
        {
            if (propertyInfo.PropertyType.Namespace == "System" || propertyInfo.PropertyType.IsEnum)
            {
                if (!propertyInfo.CanWrite) continue;
                if (propertyInfo.Name.Equals("ID")) continue;
                if (propertyInfo.Name.EndsWith("ID") && propertyInfo.Name.Length > 2) continue;
                var pv = propertyInfo.GetValue(original, null);
                propertyInfo.SetValue(destination, pv, null);
            }
            else
            {
                //dont need to copy parent entity
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityParentAttribute)))
                ...
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityLookupAttribute)))
                ...
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityInterfaceLookupAttribute)))
            ...

                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildAttribute)))
                {
                    dynamic source = propertyInfo.GetValue(original, null);
                    var target = propertyInfo.GetValue(dest, null);
                    if (source == null) return;
                    if (target == null)
                    {
                        var t = source.GetType();
                        target = Activator.CreateInstance(t);
                    }
                    source.CopyMeToProvidedEntity(target);
                    propertyInfo.SetValue(dest, target, null);
                }
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildCollectionAttribute)))
                ...
            }
        }
    }

因此,他们可以复制自己,我所有的实体都有在其基类别上定义的copymetoprovidententity(目标(的方法,并在其实施中覆盖。它看起来像这样,称为上述函数;

    public override void CopyMeToProvidedEntity(object destination)
    {
        CloneUtil.CloneCopy(this, destination);
    }

我还进一步定义了我自己的创建的其他属性(EntityParent,Entitylook,EntityChild,EntityCollection(的其他属性的关联。

我有点卡住。副本例程忽略了身份证,从不在新实体上写信给他们。ID因此被定义;

    [Browsable(false)]
    [Key]
    public int ID { get; set; }

因此,初始化始终设置为0

任何帮助都会感激

24/06/2017-添加完整的克隆例程

    public static void CloneCopy<T>( T original, object destination) where T : class
    {
        var dest = destination as T;
        if (dest == null)
            throw new Exception("destination does not match source type");
        //set cloning property so update triggers etc can be ignored
        ((IsCloneable)original).IsCloning = true;
        ((IsCloneable)dest).IsCloning = true;

        PropertyInfo[] props = typeof(T).GetProperties();
        foreach (var propertyInfo in props)
        {
            if (propertyInfo.PropertyType.Namespace == "System" || propertyInfo.PropertyType.IsEnum)
            {
                if (!propertyInfo.CanWrite) continue;
                if (propertyInfo.Name.Equals("ID")) continue;
                if (propertyInfo.Name.EndsWith("ID") && propertyInfo.Name.Length > 2) continue;
                var pv = propertyInfo.GetValue(original, null);
                propertyInfo.SetValue(destination, pv, null);
            }
            else
            {
                //Shouldn't need to do anything here as Entity Framework handles it
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityParentAttribute)))
                {
                    //object pv = propertyInfo.GetValue(original, null);
                    //propertyInfo.SetValue(Destination, pv, null);
                }
                //Just put the entity here
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityLookupAttribute)))
                {
                    var pv = propertyInfo.GetValue(original, null);
                    propertyInfo.SetValue(dest, pv, null);
                }
                //Just put the entity here
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityInterfaceLookupAttribute)))
                {
                    var pv = propertyInfo.GetValue(original, null);
                    propertyInfo.SetValue(dest, pv, null);
                }
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildAttribute)))
                {
                    dynamic source = propertyInfo.GetValue(original, null);
                    var target = propertyInfo.GetValue(dest, null);
                    if (source == null) return;
                    if (target == null)
                    {
                        var t = source.GetType();
                        target = Activator.CreateInstance(t);
                    }
                    source.CopyMeToProvidedEntity(target);
                    propertyInfo.SetValue(dest, target, null);
                }
                if (dest.PropertyHasCustomAttribute(propertyInfo.Name, typeof(EntityChildCollectionAttribute)))
                {
                    var source = propertyInfo.GetValue(original, null) as IList;
                    var target = propertyInfo.GetValue(dest, null) as IList;
                    foreach (dynamic sourceEntity in source)
                    {
                        var found = false;
                        object targetEntity = null;
                        foreach (dynamic tEntity in target)
                        {
                            if (sourceEntity.IdentityGuid != tEntity.IdentityGuid) continue;
                            found = true;
                            targetEntity = tEntity;
                            break;
                        }
                        if (!found)
                        {
                            var b = propertyInfo.PropertyType.GetGenericArguments()[0];
                            targetEntity = Activator.CreateInstance(b);
                        }
                        sourceEntity.CopyMeToProvidedEntity(targetEntity);
                        if (!found)
                        {
                            target.Add(targetEntity);
                        }
                    }
                }
            }
        }
        ((IsCloneable)original).IsCloning = false;
        ((IsCloneable)dest).IsCloning = false;
    }

27/06/2017-我找到了问题所在

我在我的实体的基类中有一个财产,以协助克隆实体与发起人配对;

    private string _identityGuid = Guid.NewGuid().ToString();
    [Browsable(false)]
    [NotMapped]
    public virtual string IdentityGuid
    {
        get { return _identityGuid; }
        set { CheckPropertyChanged(ref _identityGuid, value); }
    }

如果复制了此属性,我会在问题标题中获得ID错误...我不知道为什么这样。我将其重命名为"彼得",而只是一定是自动的。

为了修复,我有条件地排除了名为" IndentityGuid"的任何属性,并且复制例程现在有效,并且可以将副本保存到数据库中。

如果有人可以解释这个属性的问题,我将最感激:(

我经常认为需要对此任务使用反思,因为您不太可能只想克隆任何实体。通常,您想要克隆的内容是已知的,并且有一个业务用例。如果以后确定是这种情况,则可以执行以下操作。

  1. 如果尚未关闭,请在DbContext实例上关闭懒惰加载。
  2. 检索要克隆的根实体
    • Include语句,以获取要在克隆中包含的所有构图。
    • 不要检索您不会克隆而不是与之有关系的实体,而要依靠FK会为您照顾。
    • 在检索语句中我们AsNoTracking
  3. 将根实体的键及其所有组成设置为默认状态,就好像实体是新的。
  4. Add DbContext的根实体
  5. 保存更改。

过于简化的示例代码:

var root = dbContext.TypeA
        .AsNoTracking()
        .Where(x => someCondition)
        .Include(x => composititions)
        .SingleOrDefault();
root.Key = 0; // reset key
root.Comps.ForEach(comp => comp.Key = 0); // reset composition keys
dbContext.TypeA.Add(root);
dbContext.SaveChanges();

最新更新