0

I am trying to get a generic CloneEntity function working with EF6.0.2

public static T CopyEntity<T>(MyContext ctx, T entity, 
bool copyKeys = false) where T : EntityObject
{
T clone = ctx.CreateObject<T>();
PropertyInfo[] pis = entity.GetType().GetProperties();

foreach (PropertyInfo pi in pis)
{
    EdmScalarPropertyAttribute[] attrs = (EdmScalarPropertyAttribute[])
                  pi.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);

    foreach (EdmScalarPropertyAttribute attr in attrs)
    {
        if (!copyKeys && attr.EntityKeyProperty)
            continue;

        pi.SetValue(clone, pi.GetValue(entity, null), null);
    }
}

return clone;
}

This code comes from here

[update] I had to change the declaration to

public static T CloneEntity<T>(T entity, bool copyKeys = false) where T : class

This solved a compile error : 'T' must be a reference type in order to use it as parameter 'T' in the generic type or method

However when I use this method no properties are copied to the new object.

I aren't using code first. The object I am using with it has been generated from the edmx file.

The kind of object I am using it with is generated from T4

public partial class job
{
    public short LineID { get; set; }
    public short CycleID { get; set; }
    // other fields 
}

My DBContext is like

public partial class DataEntities : DbContext
{

    public DbSet<job> Jobs { get; set; }
}

[Update]

I tried

using (var db = CreateDb())
{

    var nJob = new job();
    db.jobs.Attach(nJob);
    db.Entry(nJob).CurrentValues.SetValues(job);

    return nJob;
}

but I get an error

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

The context is a partial class

there is also

partial class DataEntities
{
    public DataEntities(string efConnectString)
        : base(efConnectString)
    {

    }
}
Kirsten
  • 15,730
  • 41
  • 179
  • 318
  • Can you show us the definition of the object you're using it with – Kevin Jan 14 '14 at 02:39
  • I'm guessing the partial keyword is throwing it off. Can you make sure that each definition of the type you're using inherits from EntityObject? – Kevin Jan 14 '14 at 02:59
  • 1
    Here they talk about your error: http://stackoverflow.com/questions/6451120/the-type-must-be-a-reference-type-in-order-to-use-it-as-parameter-t-in-the-gen – JBrooks Jan 14 '14 at 03:25
  • @Kevin the Entities are generated from T4 are you suggesting I alter the template? – Kirsten Jan 14 '14 at 03:36

3 Answers3

4

I think they give you one out of the box. Try something like:

context.Entry(MyNewEntity).CurrentValues.SetValues(MyOldEntity);
JBrooks
  • 9,901
  • 2
  • 28
  • 32
  • nice, but i still have a problem. I updated the question to describe. – Kirsten Jan 14 '14 at 03:18
  • I solved the problem with the key by setting it to the old value first. The issue is discussed at `http://stackoverflow.com/questions/16646922/how-to-stop-dbentityentry-currentvalues-setvalues-trying-to-change-entitykey-val` Not generic, but good enough – Kirsten Jan 14 '14 at 04:12
1
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;
    }
}
MAD
  • 41
  • 8
  • 1
    @ MAD Hello ! Your code works to copy an entity without childs collection. what changes should I make in order to copy one ( ore some ) childs collection and to attach to new object ? Thank you ! – alex Apr 13 '15 at 21:29
-1

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.

askrich
  • 598
  • 5
  • 20
  • Hi alex. I'm not sure, I haven't tried. Give it a whirl and let me know. – askrich Apr 13 '15 at 07:24
  • Please , if you can , I posted a question using your code and the problems I have : http://stackoverflow.com/questions/29598831/entity-framework-why-this-code-doesnt-work/29600726?noredirect=1#comment47365672_29600726. Can you give an opinion if you can ? thank you ! – alex Apr 13 '15 at 16:41