I am trying to get a generic CloneEntity function working with EF6.0.2
public static T CopyEntity(MyContext ctx, T entity,
bool copyKeys = false) where
I thought I would add my contribution to this. It is a VB implementation, and an addition to existing code found on code project.
This implementation allows for the relational properties to be included (but you have to specify this).
Imports System.Data.Objects
Imports System.Data.Objects.DataClasses
Imports System.Runtime.CompilerServices
Public Module Entities
''' <summary>
''' Clone an entity
''' </summary>
''' <remarks>
''' Inspiration from: http://www.codeproject.com/Tips/474296/Clone-an-Entity-in-Entity-Framework-4
''' </remarks>
<Extension()>
Public Function CloneEntity(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
Return CloneEntityHelper(entity, context, include, copyKeys)
End Function
Private Function CloneEntityHelper(Of T As Class)(entity As T, context As ObjectContext, Optional include As List(Of IncludeEntity) = Nothing, Optional copyKeys As Boolean = False) As T
' Set default parameter values
If include Is Nothing Then include = New List(Of IncludeEntity)()
'If visited Is Nothing Then visited = New List(Of String)()
' Get the type of entity we are dealing with
Dim myType = entity.GetType()
' Make a copy of this object
Dim methodInfo = context.GetType().GetMethod("CreateObject").MakeGenericMethod(myType)
Dim result = methodInfo.Invoke(context, Nothing)
' Get the property information for the source object
Dim propertyInfo = entity.GetType().GetProperties()
' Copy over the property information
For Each info In propertyInfo
Dim attributes = info.GetCustomAttributes(GetType(EdmScalarPropertyAttribute), False).ToList()
For Each attr As EdmScalarPropertyAttribute In attributes
If (Not copyKeys) AndAlso attr.EntityKeyProperty
Continue For
End If
info.SetValue(result, info.GetValue(entity, Nothing), Nothing)
Next
' Handle relational properties
If info.PropertyType.Name.Equals("EntityCollection`1", StringComparison.OrdinalIgnoreCase) Then
' Determine whether or not we are allowed to deal with this relationship
Dim shouldInclude = include.SingleOrDefault(Function(i) i.Name.Equals(info.Name, StringComparison.OrdinalIgnoreCase))
If shouldInclude Is Nothing Then Continue For
' Clone the property
Dim relatedChildren = info.GetValue(entity, Nothing)
' Get an EntityCollection instance to hold the relational entries
Dim propertyType As Type = relatedChildren.GetType().GetGenericArguments().First()
Dim genericType As Type = GetType(EntityCollection(Of ))
Dim boundType = genericType.MakeGenericType(propertyType)
Dim children = Activator.CreateInstance(boundType)
' Load the children into the property
For Each child In relatedChildren
Dim cloneChild = CloneEntityHelper(child, context, shouldInclude.Children, shouldInclude.CopyKeys)
children.Add(cloneChild)
Next
' Save this value
info.SetValue(result, children, Nothing)
End If
Next
Return result
End Function
''' <summary>
''' Represent which (relational) properties should be included
''' </summary>
Public Class IncludeEntity
''' <summary>
''' Name of the relationship to include
''' </summary>
Public Property Name As String
''' <summary>
''' Relationships to include from the selected property
''' </summary>
Public Property Children As New List(Of IncludeEntity)
''' <summary>
''' Whether or not to Copy keys
''' </summary>
Public Property CopyKeys As Boolean
''' <summary>
''' Empty Constructor
''' </summary>
Public Sub New()
End Sub
''' <summary>
''' Create with single children
''' </summary>
Public Sub New(propertyName As String, ParamArray childNodes() As String)
Name = propertyName
Children = childNodes.Select(Function(n) new IncludeEntity(n)).ToList()
End Sub
End Class
End Module
and an example usage:
Dim formToClone = con.SF_Forms.FirstOrDefault(Function(e) e.Form_id = f.Id)
' Define what should be copied
Dim inc = New List(Of IncludeEntity)()
Dim validation = New IncludeEntity("SF_Questions_validation", "SF_Validation_Parameters")
Dim questions = new IncludeEntity("SF_Questions", "SF_Question_Parameters")
questions.Children.Add(validation)
Dim questionGroups = new IncludeEntity("SF_Question_Groups")
questionGroups.Children.Add(questions)
Dim actions = New IncludeEntity("SF_Actions", "SF_Action_Parameters")
inc.Add(questionGroups)
inc.Add(actions)
inc.Add(new IncludeEntity("SF_Messages"))
' Save the cloned form
Dim clonedForm = formToClone.CloneEntity(con, include := inc)
It took me a while to work out how to do the relational aspect, so hopefully this will help someone.
public class EntityHelper
{
public static T CopyEntity<T>(MCEntities ctx, T entity, bool copyKeys = false) where T : class, new()
{
T clone = new T();
var en = ctx.Entry(clone);
en.State = System.Data.Entity.EntityState.Added;
ctx.Entry(clone).CurrentValues.SetValues(entity);
en.State = System.Data.Entity.EntityState.Detached;
return clone;
}
}
I think they give you one out of the box. Try something like:
context.Entry(MyNewEntity).CurrentValues.SetValues(MyOldEntity);