3

how can I best copy an entity instance in fluent nhibernate 3.3.1; I read an object out of the database, I got an object, now I change some values of this object. This object with little changes want I save.

I have tried to set the Id to 0, this is not working, also I write a Clone method in my Entityclass. Here is my approach.

public class Entity: ICloneable
{
    public virtual int Id { get; protected set; }

    object ICloneable.Clone()
    {
        return this.Clone();
    }

    public virtual Entity Clone()
    {
      return (Entity)this.MemberwiseClone();
    }
}

Do you some tips for me.

Higune
  • 643
  • 3
  • 13
  • 31
  • Umm, if I understood this right you want to save the object that was *changed*, is that right? Then just keep an open session for the lifetime of those changes and everything will be saved, in such case I don't get the need for cloning... – Patryk Ćwiek Jun 13 '13 at 12:14
  • yes, you have understand me;can you show some example – Higune Jun 13 '13 at 12:16

2 Answers2

2

If your objects are not serializable and you are just looking for a quick one-to-one copy of them, you can do this pretty easily with AutoMapper:

// define a one-to-one map
// .ForMember(x => x.ID, x => x.Ignore()) will copy the object, but reset the ID
AutoMapper.Mapper.CreateMap<MyObject, MyObject>().ForMember(x => x.ID, x => x.Ignore());

And then when you copy method:

// perform the copy
var copy = AutoMapper.Mapper.Map<MyObject, MyObject>(original);

/* make copy updates here */

// evicts everything from the current NHibernate session
mySession.Clear();

// saves the entity
mySession.Save(copy); // mySession.Merge(copy); can also work, depending on the situation

I chose this approach for my own project because I have a lot of relationships with some weird requirements surrounding record duplication, and I felt this gave me a little more control over it. Of course the actual implementation in my project varies a bit, but the basic structure pretty much follows the above pattern.

Just remember, that the Mapper.CreateMap<TSource, TDestination>() creates a static map in memory, so it only needs to be defined once. Calling CreateMap again for the same TSource and TDestination will override a map if it's already defined. Similarly, calling Mapper.Reset() will clear all of the maps.

valverij
  • 4,871
  • 1
  • 22
  • 35
  • should i paste this code AutoMapper.Mapper.CreateMap().ForMember(x => x.ID, x => x.Ignore); in my entity mapping class? – Higune Jun 13 '13 at 12:49
  • I suggest not using AutoMapper for this - it's not designed to clone items (though it might work in some scenarios). View this link http://stackoverflow.com/questions/5713556/copy-object-to-object-with-automapper. The suggested approach is to use the BinaryFormatter() like my example. – Mez Jun 13 '13 at 12:57
  • @StephenBorg, like I said, this approach worked for me, although I admit I am still searching for alternatives. My biggest issue is that *sometimes* child objects/lists need to be copied with the parent, and *sometimes* the copy needs to reference the original relationships. Using the binary formatter/evict approach, NHibernate would freak out since it wouldn't evict the collections from session. [Here's my SO question about it](http://stackoverflow.com/questions/16863504/simple-and-reliable-way-to-clone-entire-object-graph-with-nhibernate). Maybe I was just approaching it in the wrong way? – valverij Jun 13 '13 at 13:14
  • @Higune, that's an example of how you would copy and ignore an ID, which would force NHibernate to assign a new ID (provided it's auto-generated). You can also chain it to take specific action on different properties (ex: `CreateMap().ForMember(...).ForMember(...)`) and assign actions to perform after the map (`CreateMap().AfterMap((source, target) => { target.Child = source.Child; target.Name += " Copy"; /* etc, etc */ })`). Really, you just have to play around with it. – valverij Jun 13 '13 at 13:19
  • @valverij using the BinaryFormatter() is so much easier. Those entities which are lazy loaded might give problems, but you easily identify these and in those cases you can load them by reflection. – Mez Jun 13 '13 at 13:38
  • @StephenBorg, do any of the PK's need reset, or does NHibernate just copy the entire object graph? Also, what about situations where the source entity needs copied, but its relations still need to point to the original's relations? Could you just say `copy.MyItem = source.MyItem`, or would NH still copy the relationship (due to the evict)? – valverij Jun 13 '13 at 14:01
  • @valverij The relationships should be copied without any problems if they are not lazy loaded. – Mez Jun 13 '13 at 14:26
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/31730/discussion-between-stephen-borg-and-valverij) – Mez Jun 13 '13 at 15:55
  • your code is working for me, but it override the existing data in the database, what could be here are reason for – Higune Jun 13 '13 at 16:36
  • Without knowing how your objects are set up, did you try `Session.Merge(copy);` instead of `Session.Save(copy)`? – valverij Jun 13 '13 at 16:43
1

You need to

  1. Load entity with NH
  2. Clone the entity with method below, and create copy
  3. Evict main entity
  4. Do changes to copy
  5. Update the copy, without any reference to the main entity

The clone method is as follows

         /// <summary>
     /// Clone an object without any references of nhibernate
     /// </summary> 
    public static object Copy<T>(this object obj)
    {
        var isNotSerializable = !typeof(T).IsSerializable;
        if (isNotSerializable)
            throw new ArgumentException("The type must be serializable.", "source");

        var sourceIsNull = ReferenceEquals(obj, null);
        if (sourceIsNull)
            return default(T);

        var formatter = new BinaryFormatter();
        using (var stream = new MemoryStream())
        {
            formatter.Serialize(stream, obj);
            stream.Seek(0, SeekOrigin.Begin);
            return (T)formatter.Deserialize(stream);
        }
    }

To use it you call it as follows

object main;
var copy = main.Copy<object>();

To see some other opinions on what to use, you can view this link aswell Copy object to object (with Automapper ?)

Community
  • 1
  • 1
Mez
  • 4,666
  • 4
  • 29
  • 57
  • where should I set the generic Copy method? – Higune Jun 13 '13 at 12:21
  • 1
    I've run into issues with this approach where the NHibernate proxies get copied and throw everything off – valverij Jun 13 '13 at 12:22
  • We have being using this approach in production for more then 2 years now. We never had problems with entities not having lazy loading. Classes which are going to be cloned need to be serializable as well. @valverij create another class called Extensions, and place this method there. Then in your current code, import the namespace, and all objects should have access to the Copy method. – Mez Jun 13 '13 at 12:23