65.9K
CodeProject 正在变化。 阅读更多。
Home

使用Entity Framework克隆Entity对象及其所有关联的子对象

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (7投票s)

2010 年 12 月 19 日

CPOL

4分钟阅读

viewsIcon

157288

downloadIcon

3488

使用 Entity Framework、LINQ 和反射克隆实体对象及其所有子项。

引言

在现实世界中,有很多场景需要通过简单的接口复制大量记录及其所有相关记录,这些接口稍作修改即可重用,而无需从头开始创建。在本文中,我们将看到一种在 LINQ to Entities 中解决此问题的方法。本文试图解决以下问题:

  • 实体延迟加载:一次方法调用即可加载实体及其所有关联。
  • 实体克隆:如何在 LINQ to Entities (Entity Framework) 中克隆实体及其所有关联。这包括在克隆后将数据重新写入数据库。

注意:新版本修复了错误“已存在具有相同键的对象”

背景

本文使用了以下技术:

  • 序列化 - 用于克隆实体。
  • 反射 - 用于动态调用方法和访问属性。反射用于实现加载子对象和清除克隆对象中的实体引用。
  • LINQ - LINQ 用于整个实现过程中。
  • 扩展方法 - 实体对象被扩展以实现所描述的功能。

本文将更侧重于解决方案和逻辑,而不是解释这些技术的基础知识。

使用代码

示例项目是一个简单的员工主数据创建应用程序,包含一些 CRUD 操作。该项目使用 ASP.NET MVC 和 Entity Framework 实现。

ER diagram

附加项目中的文件夹和文件详细信息

  • DbScript/efcloningsample.sql - 创建数据库对象的数据库脚本。
  • CLonehelper.cs - 此类包含加载实体、克隆实体和清除实体引用的扩展方法。
  • View/ Employeemaster - 所有视图都包含数据库中定义的模型的视图内容。
  • HomeController - 用于员工 CRUD 操作的控制器。
  • Models/ EmpModel.edmx - 从数据库创建的 EF 模型。

CloneHelper.CS 的实现细节

CloneHelper 类包含所有逻辑和实现。现在我们将查看其方法及其在实现中的作用。

克隆

这是一个扩展到 EntityObject 的扩展方法。它使用 DataContractSerializer 来序列化对象。克隆的实体将返回给调用者。

public static T Clone<t>(this T source) where T:EntityObject  { 
    var obj = new System.Runtime.Serialization.DataContractSerializer(typeof(T)); 
    using (var stream = new System.IO.MemoryStream())
    {
        obj.WriteObject(stream, source);
        stream.Seek(0, System.IO.SeekOrigin.Begin);
        return (T)obj.ReadObject(stream); 
    } 
}
LoadAllChild

LoadAllChild 将从数据库加载基本实体所有关联的子对象。LINQ 查询将获取子对象,并使用反射,在所有子对象上调用 Load 方法。对对象进行检查以确保它们不会被加载两次。这是通过惰性加载逻辑实现的。

public static EntityObject LoadAllChild(this EntityObject source)
{
    List<propertyinfo> PropList = (from a in source.GetType().GetProperties()
                                   where a.PropertyType.Name == "EntityCollection`1"
                                   select a).ToList();
    foreach (PropertyInfo prop in PropList)
    {
        object instance = prop.GetValue(source, null);
        bool isLoad = 
          (bool)instance.GetType().GetProperty("IsLoaded").GetValue(instance, null);
        if (!isLoad)
        {
            MethodInfo mi = (from a in instance.GetType().GetMethods()
                             where a.Name == "Load" && a.GetParameters().Length == 0
                             select a).FirstOrDefault();

            mi.Invoke(instance, null);
        }
    }
    return (EntityObject)source;
}
ClearEntityReference

此方法用于清除克隆实体上的实体引用。克隆的实体将在清除实体引用后附加到对象。克隆的对象应被视为新数据,并应创建新的主键并与参照完整性关联。一旦在克隆对象上清除了实体引用,框架将为关联创建临时键(将其视为新实体并遵循相同逻辑)。当数据移至数据库时,将创建原始键并将其关联回 EF 模型。

此方法也是 EntityObject 的扩展。bcheckHierarchy 属性用于决定仅应用于基本对象的逻辑,还是需要应用于所有子对象。基本上有两个属性:EntityKeyEntityReferences。在此过程中,EntityKey 设置为 null。还会递归调用以清除子对象中的实体。

public static EntityObject ClearEntityReference(this EntityObject source, 
                                                bool bcheckHierarchy)
{
    return source.ClearEntityObject(bcheckHierarchy);
}

private static T ClearEntityObject<t>(this  T source, 
                 bool bcheckHierarchy) where T : class
{
    //Throw if passed object has nothing
    if (source == null) { throw new Exception("Null Object cannot be cloned"); }
    // get the TYpe of passed object 
    Type tObj = source.GetType();
    // check object Passed does not have entity key Attribute 
    if (tObj.GetProperty("EntityKey") != null)
    {
        tObj.GetProperty("EntityKey").SetValue(source, null, null);
    }

    //bcheckHierarchy this flag is used to check
    //and clear child object releation keys 
    if (!bcheckHierarchy)
    {
        return (T)source;
    }

    // Clearing the Entity for Child Objects 
    // Using the Linq get only Child Reference objects   from source object 
    List<propertyinfo> PropList = (from a in source.GetType().GetProperties()
               where a.PropertyType.Name.ToUpper() == "ENTITYCOLLECTION`1"
               select a).ToList();

    // Loop thorough List of Child Object and Clear the Entity Reference 
    foreach (PropertyInfo prop in PropList)
    {
        IEnumerable keys = 
          (IEnumerable)tObj.GetProperty(prop.Name).GetValue(source, null);
        
        foreach (object key in keys)
        {
            //Clearing Entity Reference from Parnet Object
            var ochildprop = (from a in key.GetType().GetProperties()
                              where a.PropertyType.Name == "EntityReference`1"
                              select a).SingleOrDefault();

            ochildprop.GetValue(key, null).ClearEntityObject(false);

            //Clearing the the Entity Reference from
            //Child object .This will recrusive action
            key.ClearEntityObject(true);
        }
    }
    return (T)source;
}

示例实现

附加的示例应用程序有一个员工主表,带有两个子表和一个孙子表。

  • Employee (Level 1)
  • EmpAddress (Level 2)
  • EmpBasesal (Level 2)
  • EmpSalDetail (Level 3)

当用户想要克隆员工数据时,他会选择一个员工并克隆它。用户有两个选项:仅克隆父/基本实体,或克隆父、子和孙子。

实现没有对关联级别的深度/广度做任何限制。它将支持任何级别。

示例具有三级层级

public ActionResult Clone(int id)
{
    //Load the Empbasic into Context
    var Emp = (from a in osamp.Employees
               where a.EMPID == id
               select a).FirstOrDefault();
    //Load EmpAddress using lazy load
    //  Emp.EmpAddresses.Load();
    //Call the extesnion Method Clone(cast is requried
    //as it was a Entity Object)
    var EMpnew =  Emp.Clone();

    //Clear Entity Values of New Object 
    EMpnew.ClearEntityReference(false );
    //detach the Load empbasic from Context
    osamp.Detach(Emp);
    //Add new Clone Object and save it DB 
    osamp.AddToEmployees((Employee)EMpnew );
    osamp.SaveChanges();
    osamp.AcceptAllChanges();
    return RedirectToAction("index"); ;
}

public ActionResult CloneChild(int id)
{
    var Emp = (from a in osamp.Employees
                where a.EMPID == id
                select a).FirstOrDefault();
                //Load EmpAddress using lazy load
                Emp.LoadAllChild();

    //Call the extesnion Method Clone(cast is requried
    //as it was a Entity Object)
    var Empnew =Emp.Clone();
    //Detach the real Object 
    osamp.Detach(Emp);
    //Clear the Entities in the Cloned object
    Empnew.ClearEntityReference(true);

    //Attach and save the clone in database
    osamp.AddToEmployees((Employee)Empnew);
    osamp.SaveChanges();

    return RedirectToAction("index");
}

系统要求

代码和示例将在 .NET Framework 4.0 上运行。

参考文献

以下博客文章在很大程度上提供了帮助:

结论

我写了一篇 博客,讨论了使用 ADO.NET 3.5 的相同实现,但仅限于两级关联,并且一些方法已过时。此代码将在 .NET 3.5 上存在一些问题。只需将 CloneHelper 类包含到项目中即可实现此解决方案。希望这对一些实际场景有所帮助,并提高开发人员的生产力。祝您编码愉快!

© . All rights reserved.