When saving new object: [NakedObjectAssertException: expected true]

Feb 23, 2013 at 1:33 PM
Hello,

Just spent the day experimenting with the Naked Objects framework. Watched a YouTube video to get myself up and running and everything was going well.

Without doing anything extra-ordinary, I have started getting the following error whenever I save a new object:
Error in: /Snowball/Action/AddPlayer?id=NakedSnowball.Snowball%3B1%3BSystem.Int32%3B1%3BFalse%3B%3B0 error:
Server Error in '/' Application.

expected true
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: NakedObjects.Core.Util.NakedObjectAssertException: expected true

Source Error: 

An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace: 


[NakedObjectAssertException: expected true]
   NakedObjects.EntityObjectStore.EntityObjectStore.PostSave() +621
   NakedObjects.EntityObjectStore.EntityObjectStore.EndTransaction() +481
   NakedObjects.Persistor.Objectstore.ObjectStoreTransaction.Commit() +208
   NakedObjects.Persistor.Objectstore.ObjectStoreTransactionManager.EndTransaction() +101
   NakedObjects.Persistor.Objectstore.ObjectStorePersistor.EndTransaction() +65
   NakedObjects.Web.Mvc.Controllers.NakedObjectsController.OnActionExecuted(ActionExecutedContext filterContext) +103
   NakedObjects.Web.Mvc.Controllers.GenericControllerImpl.OnActionExecuted(ActionExecutedContext filterContext) +84
   System.Web.Mvc.Controller.System.Web.Mvc.IActionFilter.OnActionExecuted(ActionExecutedContext filterContext) +38
   System.Web.Mvc.Async.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49() +354
   System.Web.Mvc.Async.<>c__DisplayClass37.<BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult) +44
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +139
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +102
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult) +50
   System.Web.Mvc.Async.<>c__DisplayClass2a.<BeginInvokeAction>b__20() +68
   System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult) +184
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +136
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +56
   System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult) +40
   System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__18(IAsyncResult asyncResult) +40
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +151
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +44
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +151
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +39
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.EndExecute(IAsyncResult asyncResult) +39
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult) +45
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +47
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +151
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsyncResult asyncResult, Object tag) +40
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +40
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.EndProcessRequest(IAsyncResult result) +38
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +9629296
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155
The error is happening in the PostSave method and seems to stem from an Assert that the object is still Transient even though the object has just been saved.

Is this a bug in the framework?
Does anyone have an idea how to avoid this problem?
Coordinator
Feb 23, 2013 at 11:17 PM
Not obvious what the problem is. The following are off the top of my head ideas:
  1. How are you persisting - via the UI (i.e. hitting Save) or in code as Container.Persisted(()? If the latter, note that the Persisted method takes a ref parameter. Are you by any chance holding on to a copy of the same object in another variable - such that you have one variable holding on to the pre-persisted object after it has been persisted. (Note. Entity Framework proxies the object when you persist - hence the reason why you must use ref).
  2. Does the object you are persisting have references to other objects that are being persisted in the same transaction? That is normally OK, but some complex relationships need careful handling - though I'd be surprised if you'd got to anything that complex that quickly.
  3. You said 'Without doing anything extra-ordinary' - but can you identify any change you made between when it did work and when it didn't? (N.B. I recommend always working with source version control, with frequent check-in to the (local) repository to facilitate rolling back.
Finally, I'd just like to rule out one other possibility. I notice that your object, Snowball, is in the namespace NakedSnowball. NOF does not like domain objects to have the namespace 'NakedObjects' - I'd be surprised if NakedSnowball was a problem, but I'd like to rule that out. I can't look it up right now. Perhaps you'd just try temporarily changing the namespace to Foo just to rule that one out, and let me know.
Feb 24, 2013 at 1:20 PM
Thanks very much for taking time to respond.

After much head scratching I have resolved this issue by removing IEquatable<T> implementations that I had put on my domain objects.
Coordinator
Feb 24, 2013 at 2:03 PM
Glad you resolved it. Please could you post the code that failed. While Naked Objects doesn't recognise certain method signatures - it should not result in uncaught exceptions like it did for you - it should either simply ignore those methods, or throw an exception with a clear message. If you post the code, I'll raise a ticket as a bug to be fixed.
Feb 24, 2013 at 2:39 PM
I will post the code that failed later today.
Feb 24, 2013 at 3:02 PM
Here's the domain object code that creates the problem:
        [IconName("pawn_glass_green.png")]
        public class Snowball : IEquatable<Snowball>
    {
        public bool Equals(Snowball other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return Id == other.Id;
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != this.GetType()) return false;
            return Equals((Snowball) obj);
        }

        public override int GetHashCode()
        {
            return Id;
        }

        public static bool operator ==(Snowball left, Snowball right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(Snowball left, Snowball right)
        {
            return !Equals(left, right);
        }

        [Hidden]
        [Key]
        [DefaultValue(1)]
        public virtual int Id { get; set; }

        [MemberOrder(10)]
        public virtual string Title { get; set; }

        [MemberOrder(100)]
        [MultiLine(NumberOfLines = 15, Width = 60)] 
        [Optionally]
        [DefaultValue("")]
        public virtual string Notes { get; set; }

        [MemberOrder(30)]
        [Optionally]
        public virtual int MinPlayerCount { get; set; }

        [MemberOrder(50)]
        public virtual int MaxPlayerCount { get; set; }

        private ICollection<Player> _players = new List<Player>();

        [MemberOrder(20)]
        [Optionally]
        public virtual ICollection<Player> Players
        {
            get { return _players; }
            set { _players = value; }
        }

        public void AddPlayer(Player player)
        {
            if (_players.Any(x => x == player))
                return;

            _players.Add(player);

            player.AddSnowball(this);
        }

        public void RemovePlayer(Player player)
        {
            if (_players.All(x => x != player))
                return;
            
            _players.Remove(player);

            player.RemoveSnowball(this);
        }

        private ICollection<Tag> _tags = new List<Tag>();

        [MemberOrder(60)]
        [Optionally]
        public virtual ICollection<Tag> Tags
        {
            get { return _tags; }
            set { _tags = value; }
        }

        public void AddTag(Tag tag)
        {
            if (_tags.Any(t => t == tag)) 
                return;

            _tags.Add(tag);

            tag.AddSnowball(this);
        }

        public void RemoveTag(Tag tag)
        {
            if (!_tags.Any((t => t == tag)))
                return;

            _tags.Remove(tag);

            tag.RemoveSnowball(this);
        }

        public override string ToString()
        {
            return Title + " (" + _players.Count + "/" + MaxPlayerCount + ") " + string.Join("; ", _players.Select(p => p.Name));
        }
    }
With that domain object you should get the error I reported above.

Whilst debugging this problem, I rebuilt copied over the NakedObjects.Core assembly without the Assert.True(bool) statements in the MakePersitent() and MakePersistentAndUpdateKey() methods.

With the Asserts out of the way, using the same domain object you get to the following exception:
Using a reference to a transient object that has been persisted somewhere else (ie Framework has detected an attempt to use an object that has been replaced by a proxy)object : Te (0/2)

   at NakedObjects.Core.Adapter.Map.IdentityMapImpl.AddAdapter(INakedObject nakedObject)
   at NakedObjects.EntityObjectStore.EntityIdentityMapImpl.AddAdapter(INakedObject nakedObject)
   at NakedObjects.Core.Persist.NakedObjectPersistorAbstract.CreateAdapterForNewObject(Object domainObject)
   at NakedObjects.Core.Persist.NakedObjectPersistorAbstract.AdapterFor(Object domainObject, IOid oid, IVersion version)
   at NakedObjects.Core.Persist.PersistorUtils.GetAdapterForElseCreateAdapterForTransient(Object obj)
   at NakedObjects.Web.Mvc.Html.FrameworkHelper.GetOrCreateNakedObject(Object domainObject)
   at NakedObjects.Web.Mvc.Controllers.GenericControllerImpl.OnActionExecuted(ActionExecutedContext filterContext)
   at System.Web.Mvc.Controller.System.Web.Mvc.IActionFilter.OnActionExecuted(ActionExecutedContext filterContext)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass4f.<InvokeActionMethodFilterAsynchronously>b__49()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass37.<BeginInvokeActionMethodWithFilters>b__36(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncResult`1.End()
   at System.Web.Mvc.Async.AsyncResultWrapper.End[TResult](IAsyncResult asyncResult, Object tag)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult)
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<>c__DisplayClass2a.<BeginInvokeAction>b__20()
   at System.Web.Mvc.Async.AsyncControllerActionInvoker.<>c__DisplayClass25.<BeginInvokeAction>b__22(IAsyncResult asyncResult)
I believe this exception stems from this code in the IdentityMapImpl.AddAdapter(INakedObject nakedObject) method:
            if (unloadedObjects.ContainsKey(obj)) {
                string msg = string.Format(Resources.NakedObjects.TransientReferenceMessage, obj);

                throw new TransientReferenceException(msg);
            }
Coordinator
Feb 24, 2013 at 10:04 PM
After a hunch I did a little searching and found this: http://devlicio.us/blogs/derik_whittaker/archive/2012/04/24/ef-overriding-equals-massive-headache.aspx

So we might not be able to do anything about this - except perhaps catch the exception and present it better.
Coordinator
Feb 25, 2013 at 9:34 AM
You shouldn't need to implement IEquatable - the framework handles this for you as long as you create and get instances through the framework (NewTransientInstance/Persist, Instances<T>). - ie you should get the same (ReferenceEquals) object back each time.

In this particular case the problem is in code that is keeping track of transients that have been persisted in order to guard against their continuing to be used after persistence. Because the transient (presumably) hasn't had its Id set before persistence we are getting multiple transients with matching Id's which is giving false key matches on equality. I think that's a bug and I'll change this code to enforce reference matching.

There are other place where we keep track of the objects and we'll have to think about them individually. I'm not sure about EF - the linked blog does have a faulty implementation so EF may work OK with a correct one.