10
\$\begingroup\$

I'm currently writting piece of logic which copies entities from one user account to another. My current strategy in doing this is like that: Consider the following code:

 public class MobileOrderSettings:ICloneable
{
    public long Id { get; set; }
    public bool allowCash { get; set; }
    public int deliveryPrice { get; set; }
    public int deliveryTreshold { get; set; }
    public bool orderingEnabled { get; set; }
    public bool paymentsEnabled { get; set; }
    public int shippingType { get; set; }

    public virtual MobileApp MobileApp { get; set; }

    public object Clone()
    {
        var copy = CopyUtils.ShallowCopyEntity(this);
        copy.Id = default(int);
        copy.MobileApp = null;
        return copy;
    }
}

The ShallowCopyEntity method defined like in this way:

 public static TEntity ShallowCopyEntity<TEntity>(TEntity source) where TEntity : class, new()
    {

        // Get properties from EF that are read/write and not marked witht he NotMappedAttribute
        var sourceProperties = typeof(TEntity)
                                .GetProperties()
                                .Where(p => p.CanRead && p.CanWrite &&
                                            p.GetCustomAttributes(typeof(System.ComponentModel.DataAnnotations.Schema.NotMappedAttribute), true).Length == 0);
        var notVirtualProperties = sourceProperties.Where(p => !p.GetGetMethod().IsVirtual);
        var newObj = new TEntity();

        foreach (var property in notVirtualProperties)
        {

            // Copy value
            property.SetValue(newObj, property.GetValue(source, null), null);

        }

        return newObj;

    }

So, as you can see, I firstly copy all fields which is not virtual, reassing Id value and then perform copy of the dependent object(collection)(In this particular situation MobileOrderSettings depends on MobileApp entity, so I make MobileApp null, and in MobileApp Clone menthod I assign MobileOrderSettings virtual field the copy of MobileOrderSettings). Is this approach is good, or you can suggest any better solution?

\$\endgroup\$
1
  • 2
    \$\begingroup\$ Would .AsNoTracking() work for your scenario? \$\endgroup\$
    – Neil
    Commented Jun 27, 2018 at 8:28

1 Answer 1

9
\$\begingroup\$

Two possible sources of errors:

  1. If you forget to use (or don't want to apply) the virtual modifier, it will no longer be a shallow copy (because virtual is typically used for navigation properties).
  2. It relies on unmapping properties by attributes. If at any time in the future you start using the fluent API for that purpose you'll change behavior in an unexpected place.

You evidently want to clone mapped and scalar properties only. Your approach works (if you remember the error sources), but I'd prefer to use a method provided by EF itself:

MobileOrderSettings settings = context.MobileOrderSettings.FirstOrDefault();
var shallowCopy = (MobileOrderSettings)context.Entry(settings)
                                              .CurrentValues.ToObject()

As you see, the CurrentValues property of an entry in EF's change tracker is used to build a clone, which is always a shallow clone of mapped properties.

Of course, you can't apply this method inside an entity class, because it requires a context to which the entity object is attached. But is that bad? I'd say no, because this clone method has a very specific purpose which is tied to an EF environment. To me it would break the persistence ignorance principle if a class would require knowledge of the data layer implementation for which it clones itself.

\$\endgroup\$
2
  • \$\begingroup\$ PropertyValues (what CurrentValues returns) has both ToObject and Clone methods, but the docs don't really distinguish them -- any reason why you'd choose one over the other? \$\endgroup\$
    – Rich
    Commented Nov 29, 2018 at 16:13
  • \$\begingroup\$ Never mind - Clone returns another PropertyValues, ToObject returns an entity type. Overthinking it. \$\endgroup\$
    – Rich
    Commented Nov 29, 2018 at 16:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.