C# EF:包含所有子对象(包括子子对象)的复制对象(深度复制)
我有一个具有如下属性的表:C# EF:包含所有子对象(包括子子对象)的复制对象(深度复制),c#,entity-framework,linq,linq-to-entities,C#,Entity Framework,Linq,Linq To Entities,我有一个具有如下属性的表: Id Name ParentId ParentId是主列Id的外键。现在让我们假设我有几行,如:(仅显示来自行的ParentId) 现在,假设我们要复制其ParentId为空的行对象及其所有子对象 var row = db.FirstOrDefault(x=> x.Id == 1); var new_row = new Table1(); var subrows = row.Table1.ToArray(); foreach(var
Id Name ParentId
ParentId
是主列Id
的外键。现在让我们假设我有几行,如:(仅显示来自行的ParentId
)
现在,假设我们要复制其ParentId
为空的行对象及其所有子对象
var row = db.FirstOrDefault(x=> x.Id == 1);
var new_row = new Table1();
var subrows = row.Table1.ToArray();
foreach(var row in subrows)
{
db.Entry(row).State = System.Data.Entity.EntityState.Detached;
}
new_row.Table1 = subrows;
db.Table.Add(new_row);
db.saveChanges();
结果:新插入的结构如下:
NULL
/ \
1 2
我假设只复制了一个子级别。如何复制/插入所有子级
编辑:由于“分离”有助于创建一个副本直到一个级别,因此我尝试了以下方法:
private void RecursiveDetach(Table1 parent)
{
var subrows = parent.Table1.ToArray();
foreach (var row in subrows)
{
if(row.Table1.Count() > 0)
{
RecursiveDetach(row);
}
db.Entry(row).State = System.Data.Entity.EntityState.Detached;
}
}
但是,现在我得到一个错误:
收集被修改;枚举操作不能执行
我以前不得不这么做。我完全是在代码中完成的,递归地复制对象并在需要的地方清理惟一的ID,但我所构建的最干净的方法是将对象序列化为XML,然后反序列化为新对象。这种方法效率较低,但非常灵活,易于实现
//Save object to XML file. Returns filename.
public string SaveObjectAsXML(int id)
{
//however you get your EF context and disable proxy creation
var db = GetContext();
bool currentProxySetting = db.Configuration.ProxyCreationEnabled;
db.Configuration.ProxyCreationEnabled = false;
//get the data
var item = db.GetItem(id); //retrieval be unique to your setup, but I have
//a more generic solution if you need it. Make
//sure you have all the sub items included
//in your object or they won't be saved.
db.Configuration.ProxyCreationEnabled = currentProxySetting;
//if no item is found, do whatever needs to be done
if (item == null)
{
return string.Empty;
}
//I actually write my data to a file so I can save states if needed, but you could
//modify the method to just spit out the XML instead
Directory.CreateDirectory(DATA_PATH); //make sure path exists to prevent write errors
string path = $"{DATA_PATH}{id}{DATA_EXT}";
var bf = new BinaryFormatter();
using (FileStream fs = new FileStream(path, FileMode.Create))
{
bf.Serialize(fs, repair);
}
return path;
}
//Load object from XML file. Returns ID.
public int LoadXMLData(string path)
{
//make sure the file exists
if (!File.Exists(path))
{
throw new Exception("File not found.");
}
//load data from file
try
{
using (FileStream fs = new FileStream(path, FileMode.Open))
{
var item = (YourItemType)new BinaryFormatter().Deserialize(fs);
db.YourItemTypes.Add(item);
db.SaveChanges();
return item.Id;
}
}
catch (Exception ex) {
//Exceptions here are common when copying between databases where differences in config entries result in mis-matches
throw;
}
}
用法很简单
//save object
var savedObjectFilename = SaveObjectAsXML(myObjID);
//loading the item will create a copy
var newID = LoadXMLData(savedObjectFilename);
祝你好运 我以前不得不这么做。我完全是在代码中完成的,递归地复制对象并在需要的地方清理惟一的ID,但我所构建的最干净的方法是将对象序列化为XML,然后反序列化为新对象。这种方法效率较低,但非常灵活,易于实现
//Save object to XML file. Returns filename.
public string SaveObjectAsXML(int id)
{
//however you get your EF context and disable proxy creation
var db = GetContext();
bool currentProxySetting = db.Configuration.ProxyCreationEnabled;
db.Configuration.ProxyCreationEnabled = false;
//get the data
var item = db.GetItem(id); //retrieval be unique to your setup, but I have
//a more generic solution if you need it. Make
//sure you have all the sub items included
//in your object or they won't be saved.
db.Configuration.ProxyCreationEnabled = currentProxySetting;
//if no item is found, do whatever needs to be done
if (item == null)
{
return string.Empty;
}
//I actually write my data to a file so I can save states if needed, but you could
//modify the method to just spit out the XML instead
Directory.CreateDirectory(DATA_PATH); //make sure path exists to prevent write errors
string path = $"{DATA_PATH}{id}{DATA_EXT}";
var bf = new BinaryFormatter();
using (FileStream fs = new FileStream(path, FileMode.Create))
{
bf.Serialize(fs, repair);
}
return path;
}
//Load object from XML file. Returns ID.
public int LoadXMLData(string path)
{
//make sure the file exists
if (!File.Exists(path))
{
throw new Exception("File not found.");
}
//load data from file
try
{
using (FileStream fs = new FileStream(path, FileMode.Open))
{
var item = (YourItemType)new BinaryFormatter().Deserialize(fs);
db.YourItemTypes.Add(item);
db.SaveChanges();
return item.Id;
}
}
catch (Exception ex) {
//Exceptions here are common when copying between databases where differences in config entries result in mis-matches
throw;
}
}
用法很简单
//save object
var savedObjectFilename = SaveObjectAsXML(myObjID);
//loading the item will create a copy
var newID = LoadXMLData(savedObjectFilename);
祝你好运 这里有第二个完全不同的答案:递归地分离整个对象,而不仅仅是父对象。以下内容是作为上下文对象的扩展方法编写的:
/// <summary>
/// Recursively detaches item and sub-items from EF. Assumes that all sub-objects are properties (not fields).
/// </summary>
/// <param name="item">The item to detach</param>
/// <param name="recursionDepth">Number of levels to go before stopping. object.Property is 1, object.Property.SubProperty is 2, and so on.</param>
public static void DetachAll(this DbContext db, object item, int recursionDepth = 3)
{
//Exit if no remaining recursion depth
if (recursionDepth <= 0) return;
//detach this object
db.Entry(item).State = EntityState.Detached;
//get reflection data for all the properties we mean to detach
Type t = item.GetType();
var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod()?.IsPublic == true) //get only properties we can set
.Where(p => p.PropertyType.IsClass) //only classes can be EF objects
.Where(p => p.PropertyType != typeof(string)) //oh, strings. What a pain.
.Where(p => p.GetValue(item) != null); //only get set properties
//if we're recursing, we'll check here to make sure we should keep going
if (properties.Count() == 0) return;
foreach (var p in properties)
{
//handle generics
if (p.PropertyType.IsGenericType)
{
//assume its Enumerable. More logic can be built here if that's not true.
IEnumerable collection = (IEnumerable)p.GetValue(item);
foreach (var obj in collection)
{
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
else
{
var obj = p.GetValue(item);
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
}
要检索我的可查询集合,我现在将其称为:
var queryableOrders = context.GetQueryable(includes);
同样,这里的目的是创建一个可查询的对象,该对象将只急切地加载您实际需要的子对象(和子对象)
要获取特定项,请像使用任何其他可查询源一样使用:
var order = context.GetQueryable(includes).FirstOrDefault(o => o.OrderNumber == myOrderNumber);
请注意,您还可以提供内嵌的include表达式;但是,您需要指定泛型:
//you can provide includes inline if you just have a couple
var order = context.GetQueryable<Order>(o => o.Tenders, o => o.SalesItems).FirstOrDefault(o => o.OrderNumber == myOrderNumber);
//如果您只有一对,就可以提供includesinline
var order=context.GetQueryable(o=>o.Tenders,o=>o.SalesItems);
这里有第二个完全不同的答案:递归地分离整个对象,而不仅仅是父对象。以下内容是作为上下文对象的扩展方法编写的:
/// <summary>
/// Recursively detaches item and sub-items from EF. Assumes that all sub-objects are properties (not fields).
/// </summary>
/// <param name="item">The item to detach</param>
/// <param name="recursionDepth">Number of levels to go before stopping. object.Property is 1, object.Property.SubProperty is 2, and so on.</param>
public static void DetachAll(this DbContext db, object item, int recursionDepth = 3)
{
//Exit if no remaining recursion depth
if (recursionDepth <= 0) return;
//detach this object
db.Entry(item).State = EntityState.Detached;
//get reflection data for all the properties we mean to detach
Type t = item.GetType();
var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod()?.IsPublic == true) //get only properties we can set
.Where(p => p.PropertyType.IsClass) //only classes can be EF objects
.Where(p => p.PropertyType != typeof(string)) //oh, strings. What a pain.
.Where(p => p.GetValue(item) != null); //only get set properties
//if we're recursing, we'll check here to make sure we should keep going
if (properties.Count() == 0) return;
foreach (var p in properties)
{
//handle generics
if (p.PropertyType.IsGenericType)
{
//assume its Enumerable. More logic can be built here if that's not true.
IEnumerable collection = (IEnumerable)p.GetValue(item);
foreach (var obj in collection)
{
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
else
{
var obj = p.GetValue(item);
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
}
要检索我的可查询集合,我现在将其称为:
var queryableOrders = context.GetQueryable(includes);
同样,这里的目的是创建一个可查询的对象,该对象将只急切地加载您实际需要的子对象(和子对象)
要获取特定项,请像使用任何其他可查询源一样使用:
var order = context.GetQueryable(includes).FirstOrDefault(o => o.OrderNumber == myOrderNumber);
请注意,您还可以提供内嵌的include表达式;但是,您需要指定泛型:
//you can provide includes inline if you just have a couple
var order = context.GetQueryable<Order>(o => o.Tenders, o => o.SalesItems).FirstOrDefault(o => o.OrderNumber == myOrderNumber);
//如果您只有一对,就可以提供includesinline
var order=context.GetQueryable(o=>o.Tenders,o=>o.SalesItems);
您是在处理自引用关系,还是涉及两个不同的实体?我不知道您想问什么,但这里有更多上下文entities db=new entities()
这里是如何定义外键的ALTER TABLE[dbo].[TABLE]WITH NOCHECK ADD CONSTRAINT[FK_TABLE_TABLE]外键([ParentId])引用[dbo].[TABLE]([Id])
您使用的是自引用关系还是涉及两个不同的实体?我不知道您想问什么,但这里有更多上下文entities db=newentities()
这里是如何定义外键的ALTER TABLE[dbo].[TABLE]WITH NOCHECK ADD CONSTRAINT[FK_TABLE\u TABLE]外键([ParentId])引用[dbo].[TABLE]([Id])
如果递归深度未知怎么办?我还用我尝试过的方法修改了这个问题。建议是否有一种简单的方法来解决集合修改错误。只要没有任何循环引用(即Person.Address.State.Person
),就不需要递归检查。在这种情况下,您可以删除递归深度,也不会出现集合修改错误。这根本不起作用。它只返回父对象,而不返回子对象。一旦您分离父实体,子实体就不再可访问。@Shymalparikh,我不知道您的实现,但我怀疑它不起作用,因为您没有急切地加载所有数据。我已经更新了我的答案,详细说明了在使用此方法之前如何确保所有数据都已就绪。如果递归深度未知,该怎么办?我还用我尝试过的方法修改了这个问题。建议是否有一种简单的方法来解决集合修改错误。只要没有任何循环引用(即Person.Address.State.Person
),就不需要递归检查。在这种情况下,您可以删除递归深度,也不会出现集合修改错误。这根本不起作用。它只返回父对象,而不返回子对象。一旦您分离父实体,子实体就不再可访问。@Shymalparikh,我不知道您的实现,但我怀疑它不起作用,因为您没有急切地加载所有数据。我已经更新了我的答案,详细说明了在使用此方法之前如何确保所有数据都已就绪。