POCO Map already contains object

Jun 28, 2013 at 3:40 PM
I'm trying to implement my first View Model, but the following Exception and stack trace appears when I try to view it:

[NakedObjectAssertException: POCO Map already contains object: {My class name}]
NakedObjects.Core.Util.Assert.AssertTrue(String message, Object target, Boolean flag) +178
NakedObjects.Core.Util.Assert.AssertFalse(String message, Object target, Boolean flag) +50
NakedObjects.Core.Adapter.Map.IdentityMapImpl.AddAdapter(INakedObject nakedObject) +155
NakedObjects.EntityObjectStore.EntityIdentityMapImpl.AddAdapter(INakedObject nakedObject) +63
NakedObjects.Core.Persist.NakedObjectPersistorAbstract.CreateAdapterForViewModel(Object viewModel, INakedObjectSpecification spec) +264
NakedObjects.Core.Persist.NakedObjectPersistorAbstract.CreateViewModel(INakedObjectSpecification specification) +172
NakedObjects.Reflector.DotNet.DotNetDomainObjectContainer.NewViewModel(Type type) +142
NakedObjects.Reflector.DotNet.DotNetDomainObjectContainer.NewViewModel() +122
JEKModel.ContributedActions.OrderContributedActions.GetSupplierWorksheet(Order TheOrder) in C:\Users\baileyp\Documents\Visual Studio 2010\Projects\Employee\JEKModel\ContributedActions\OrderContributedActions.cs:25


I implemented the view model as an IViewModel. It's created by a Contributed Action to another of my classes; the Exception is thrown when I trigger that action while viewing the related class in my MVC project.

Is there, perhaps, somewhere that I need to register the type of this View Model? I tried adding it to the AssociatedClasses in my database-first persistor, but it didn't fix the issue.
Coordinator
Jun 28, 2013 at 4:00 PM
Edited Jun 28, 2013 at 4:01 PM
Not immediately clear what's wrong. There is an example of a ViewModel in the AdventureWorks demo.

So this is a contributed action to create one - on the OrderContributedActions repository
  public QuickOrderForm QuickOrder(Customer customer) {
            var qo = Container.NewViewModel<QuickOrderForm>();
            qo.Customer = customer;
            return qo;
        }
And this is the associated View Model code
  public class OrderLine : IViewModel {
        public IDomainObjectContainer Container { protected get; set; }

        [Hidden]
        public Product Product { get; set; }

        [Hidden]
        public short Number { get; set; }

        [Title]
        public string Description {
            get { return Number.ToString() + " x " + Product.Name; }
        }

        public string[] DeriveKeys() {
            return new[] {Product.ProductID.ToString(), Number.ToString()};
        }

        public void PopulateUsingKeys(string[] instanceId) {
            int p = int.Parse(instanceId.First());
            short n = short.Parse(instanceId.Skip(1).First());
            Product = Container.Instances<Product>().Single(c => c.ProductID == p);
            Number = n;
        }

        [Hidden]
        public void AddTo(SalesOrderHeader salesOrder) {
            SalesOrderDetail sod = salesOrder.AddNewDetail(Product, Number);
            Container.Persist(ref sod);
        }
    }

    public class QuickOrderForm : IViewModel {
        private ICollection<OrderLine> details = new List<OrderLine>();
        public IDomainObjectContainer Container { protected get; set; }
        public OrderContributedActions OrderRepo { protected get; set; }

        [Hidden]
        public Customer Customer { get; set; }


        public string AccountNumber {
            get { return Customer.AccountNumber; }
        }

        [Title]
        public string Description {
            get { return details.Any() ? details.First().Description + "..." : AccountNumber; }
        }

        [Disabled]
        public ICollection<OrderLine> Details {
            get { return details; }
            set { details = value; }
        }

        public string[] DeriveKeys() {
            var keys = new List<string> {Customer.AccountNumber};
            foreach (OrderLine orderLine in details) {
                keys.AddRange(orderLine.DeriveKeys());
            }
            return keys.ToArray();
        }

        public void PopulateUsingKeys(string[] instanceId) {
            string an = instanceId.First();
            Customer = Container.Instances<Customer>().Single(c => c.AccountNumber == an);

            for (int i = 1; i < instanceId.Count(); i = i + 2) {
                var dKeys = new[] {instanceId[i], instanceId[i + 1]};
                var d = Container.NewViewModel<OrderLine>();
                d.PopulateUsingKeys(dKeys);
                details.Add(d);
            }
        }

        public QuickOrderForm AddDetail(Product product, short number) {
            var ol = Container.NewViewModel<OrderLine>();
            ol.Product = product;
            ol.Number = number;
            details.Add(ol);

            return this;
        }

        public SalesOrderHeader CreateOrder() {
            SalesOrderHeader soh = OrderRepo.CreateNewOrder(Customer, true);
            soh.Status = (byte) OrderStatus.InProcess;
            Container.Persist(ref soh);

            foreach (OrderLine d in Details) {
                d.AddTo(soh);
            }

            return soh;
        }
    }
Nothing else should be necessary.
Jul 1, 2013 at 5:47 PM
Thanks for the sample code. Unfortunately, I'm still unable to resolve the issue.

In order to simplify the problem, I've excised the ContributedActions class and made the method to create the view model a method of the model class. I've also stripped all members from the view model, so I'm doing nothing more than using the container to instantiate an empty view model (inheriting from IViewModel). So, the relevant portion of the two classes is reduced to this:

Order.cs:
   public SupplierWorksheet GetSupplierWorksheet()
   {
       SupplierWorksheet Worksheet = Container.NewViewModel<SupplierWorksheet>();

       return Worksheet;
   }
SupplierWorksheet.cs:
public class SupplierWorksheet : IViewModel
{
   public IDomainObjectContainer Container { set; protected get; }  //Injected service

   #region IViewModel Members

   public string[] DeriveKeys()
   {
       return new string[] { };
   }

   public void PopulateUsingKeys(string[] instanceId)
   {

   }

   #endregion
}
Nonetheless, I'm getting the same "POCO Map already contains..." error when triggering the Get Supplier Worksheet action. I've looked over the AdventureWorks project and I just don't see what I'm getting wrong.
Coordinator
Jul 1, 2013 at 5:53 PM
Have you actually implemented the DeriveKeys and PopulateUsingKeys methods - your example code shows them empty? (That would cause a problem). If you have, please show the code.
Jul 1, 2013 at 6:48 PM
This is how they were implemented before I removed all properties and members (aside from the necessary IViewModel methods) from the SupplierWorksheet class:
   #region IViewModel Members

   public string[] DeriveKeys()
   {
       return new string[] { this.TheOrder.Serial.ToString(), this.Supplier1.VendorID, this.Supplier2.VendorID, this.Supplier3.VendorID };
   }

   public void PopulateUsingKeys(string[] keys)
   {
       double serial = Double.Parse(keys.ElementAt(0));
       this.TheOrder = Container.Instances<Order>().Single(x => x.Serial == serial);

       string id1 = keys.ElementAt(1);
       this.Supplier1 = Container.Instances<SupplierCompany>().Single(x => x.VendorID == id1);
       string id2 = keys.ElementAt(2);
       this.Supplier2 = Container.Instances<SupplierCompany>().Single(x => x.VendorID == id2);
       string id3 = keys.ElementAt(3);
       this.Supplier3 = Container.Instances<SupplierCompany>().Single(x => x.VendorID == id3);
   }

   #endregion
Coordinator
Jul 1, 2013 at 7:10 PM
You haven't overloaded Equals() and/or GetHashCode() anywhere in your domain code have you ?
Jul 1, 2013 at 7:19 PM
scascarini wrote:
You haven't overloaded Equals() and/or GetHashCode() anywhere in your domain code have you ?
Nope.
Jul 1, 2013 at 7:36 PM
Here it is: I enabled logging using a Common.Logging.Simple.TraceLoggerFactoryAdapter to debug a previous issue. I removed logging (i.e. commented out all of NakedObjectsStart.InitialiseLogging and it works*! Thank you both for your contributions.

*Well, it still throws an exception, but it's from my own code, so I'll deal with it. :)