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

标签 c# entity-framework deep-copy

我正在尝试先编写通用 Entity Framework 代码复制例程。 此例程复制源属性、创建新的子实体、复制对查找实体的引用并复制子集合。 它似乎有效。 在检查创建的实体时,所有子项、查找和集合都存在,但是当我调用 DbContext.ChangeTracker.HasChanges() 时,出现以下错误;

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

经调查,错误似乎是由新创建的子实体(而不是新创建的根实体/父实体)引发的

这是填充了相关部分的例程 (if (dest.PropertyHasCustomAttribute(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)))
                ...

            }
        }

    }

所以他们可以复制自己,我的所有实体都在其基类上定义了方法 CopyMeToProvidedEntity(target) 并在其实现中被覆盖。看起来像这样并调用了上面的函数;

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

我还使用我自己创建的附加属性(EntityParent、EntityLookup、EntityChild、EntityChildCollection)进一步定义我的实体的关联。

我有点卡住了。复制例程忽略 ID,并且从不在新实体上写入它们。 ID是这样定义的;

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

so 在初始化时总是设置为 0

我们将不胜感激

24/06/2017 - 添加了完整的 CloneCopy 例程

    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 错误... 我不知道为什么会这样。 我已将它重命名为“Peter”,以防它是一些 EF 自动的东西。

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

如果有人能解释这个属性有什么问题,我将不胜感激:)

最佳答案

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

  1. DbContext 实例上关闭延迟加载(如果尚未关闭)。
  2. 检索要克隆的根实体
    • Include 语句以获取您想要包含在克隆中的所有合成。
    • 不要检索您不会克隆但与之相关的实体,而是依赖 FK 会为您处理。
    • 在检索语句中我们AsNoTracking
  3. 将根实体及其所有组合的键设置为默认状态,就好像该实体是新的一样。
  4. 将根实体添加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();

关于c# - EF 代码优先,复杂实体的深层复制, "ID is part of objects key info and cannot be modified"- 错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44720176/

相关文章:

按钮上的 C# 图标(调整大小)

c# - XAML WPF 如何在 FlowDocument 上添加内嵌背景图像?

c# - Azure 云服务无法将 vmsize 设置为 : ExtraSmall, Small、Medium 以外的任何值

c# - 如何在 ASP.Net Core 中实现自己的 DBContext 并仍然使用给定的 ApplicationUser 进行身份验证?

Java递归深度克隆

c# - 连接的 SQL 列返回空值,是否有解决方法?

c# - 如何在不同的上下文中加载/修改/保存实体对象?

mysql - Entity Framework 电动工具 Beta 4 与非 - 使用 MySql

c++ - 深拷贝int数组c++

elasticsearch - 如何深拷贝elasticsearch QueryBuilder?