Adding Record class without persistance

Topics: General
Oct 25, 2013 at 2:40 PM
Edited Oct 25, 2013 at 2:47 PM
Hi,

I'm following along Sipke Schoorstra's module development walkthrough (http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-9), and on part 9, I run into this problem time and time again, and needed some advice.

I'm adding the OrderDetail and OrderRecord and OrderDetailRecord classes where everything is marked as virtual: (code below)
public class OrderRecord
    {
        public virtual int Id { get; set; }
        public virtual int CustomerId { get; set; }
        public virtual DateTime CreatedAt { get; set; }
        public virtual decimal SubTotal { get; set; }
        public virtual decimal Vat { get; set; }
        public virtual OrderStatus Status { get; set; }
        public virtual IList<OrderDetailRecord> Details { get; private set; }
        public virtual string PaymentServiceProviderResponse { get; set; }
        public virtual string PaymentReference { get; set; }
        public virtual DateTime? PaidAt { get; set; }
        public virtual DateTime? CompletedAt { get; set; }
        public virtual DateTime? CancelledAt { get; set; }

        public virtual decimal Total
        {
            get { return SubTotal + Vat; }
        }

        public virtual string Number
        {
            get { return (Id + 1000).ToString(CultureInfo.InvariantCulture); }
        }
        
        public OrderRecord() {
            Details = new List<OrderDetailRecord>();
        }

        public virtual void UpdateTotals()
        {
            var subTotal = 0m;
            var vat = 0m;

            foreach (var detail in Details) {
                subTotal += detail.SubTotal;
                vat += detail.Vat;
            }

            SubTotal = subTotal;
            Vat = vat;
        }
    }

public class OrderDetailRecord
    {
        public virtual int Id { get; set; }
        public virtual int OrderRecord_Id { get; set; }
        public virtual int ProductId { get; set; }
        public virtual int Quantity { get; set; }
        public virtual decimal UnitPrice { get; set; }
        public virtual decimal VatRate { get; set; }

        public virtual decimal UnitVat
        {
            get { return UnitPrice * VatRate; }
        }

        public virtual decimal Vat
        {
            get { return UnitVat * Quantity; }
        }

        public virtual decimal SubTotal
        {
            get { return UnitPrice * Quantity; }
        }

        public virtual decimal Total
        {
            get { return SubTotal + Vat; }
        }
    }
Everything has been marked as virtual and the IList<OrderDetailRecord> is being initialized in the constructor as below:
 public OrderRecord() {
            Details = new List<OrderDetailRecord>();
        }
So my problem, As soon as I add the above classes, I get the following exception from the Logs:

NHibernate.InvalidProxyTypeException: The following types may not be used as proxies:
Orchard.Webshop.Models.OrderRecord: method set_Details should be 'public/protected virtual' or 'protected internal virtual'
at NHibernate.Cfg.Configuration.ValidateEntities()
at NHibernate.Cfg.Configuration.GetObjectData(SerializationInfo info, StreamingContext context)
--- and so on

And other exceptions (on top of the stack trace include)

Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: The best overloaded method match for 'Orchard.Core.Shapes.CoreShapes.EncodeAlternateElement(string)' has some invalid arguments
at CallSite.Target(Closure , CallSite , CoreShapes , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
at CallSite.Target(Closure , CallSite , CoreShapes , Object )
at Orchard.Core.Shapes.CoreShapes.<Discover>b__95(ShapeDisplayingContext displaying) in c:\myaspfiles\orchard\boom\src\Orchard.Web\Core\Shapes\CoreShapes.cs:line 98
at Orchard.DisplayManagement.Implementation.DefaultDisplayManager.<>c__DisplayClassc.<Execute>b__3(Action1 action) at Orchard.InvokeExtensions.Invoke[TEvents](IEnumerable1 events, Action`1 dispatch, ILogger logger) in e:\myaspfiles\orchard\boom\src\Orchard\InvokeExtensions.cs:line 17
2013-10-25 07:33:01,438 [28]
--- End of logs

Things I suspect might be wrong:
  1. In the tutorial (Or the source code download) there is no Handler for wiring up data access. Can this be the cause?
    1.1 Although the handlers are not present for (OrderRecordHandler/ OrderDetailRecordHandler), Migrations are put in properly (which I can show if someone asks)
  2. Sébastien Ros suggested in a post that Classes with Record/Models namespace (and ending with those names) will be mapped by NHibernate, and we can suppress this by moving them out of those namespaces. Is this necessary? if we don't have Handlers?
Can anyone suggest what might be going wrong and the things I can try?

I've revert my changes (mercurial) many times to this exact spot in time, and the above adding of classes, is what caused those errors.

Also, the error im getting is:
Resource not found (HTTP 404) on the front end when I browse to my site before inspecting logs.

Thanks for the help in advance.
Oct 25, 2013 at 4:50 PM
Have you tried to make the setter of OrderRecord.Details public?
Oct 25, 2013 at 5:04 PM
I tried the following:
  1. Moved those two classes out of .Models folder
    -> This Works and causes no errors (but not sure about why I needed to move these out if I have Migrations enabled.
    -> Also moving those classes out now updates Migrations properly (Verified by seeing tables in Orchard_Framework_DataMigrationRecord)
  2. If I made the property IList<OrderDetailRecord> Details {get; set;} , i.e. public setter with the classes put in .Models namespace
    -> Causes the following exceptions:
     NHibernate.PropertyNotFoundException: Could not find a setter for property 'Total' in class 'Orchard.Webshop.Models.OrderRecord'
     And Other exceptions for UnitVat, SubTotal and Total properties which are : Public, Virtual getters.
    
So with the above criteria, I guess i intend to proceed with option 1, although i don't yet understand what's going on.

Any further advice is appreciated, but I will mark the issue as answered for now.
Coordinator
Oct 25, 2013 at 5:41 PM
Remove 'virtual' on the properties which don't need to be persisted.
use {get; set;} on the properties which have to be persisted.
Oct 28, 2013 at 1:44 AM
I'm still having issues, and here are the changes I made according to the above suggestion:
  1. Made properties that don't require persistence as non-virtual, and virtual if persistence is required.
public class OrderRecord : ContentPartRecord
    {
        // Properties that need to be persisted
        public virtual int CustomerId { get; set; }
        public virtual DateTime CreatedAt { get; set; }
        public virtual decimal SubTotal { get; set; }
        public virtual decimal Vat { get; set; }
        public virtual OrderStatus Status { get; set; }        
        public virtual string PaymentServiceProviderResponse { get; set; }
        public virtual string PaymentReference { get; set; }
        public virtual DateTime? PaidAt { get; set; }
        public virtual DateTime? CompletedAt { get; set; }
        public virtual DateTime? CancelledAt { get; set; }

        // Properties that dont need persistence
        public IList<OrderDetailRecord> Details { get; private set; }

        public decimal Total
        {
            get { return SubTotal + Vat; }
        }

        public string Number
        {
            get { return (Id + 1000).ToString(CultureInfo.InvariantCulture); }
        }
        

        // Construction
        public OrderRecord()
        {
            Details = new List<OrderDetailRecord>();
        }

       
       
        // Methods
        public void UpdateTotals()
        {
            var subTotal = 0m;
            var vat = 0m;

            foreach (var detail in Details) {
                subTotal += detail.SubTotal;
                vat += detail.Vat;
            }

            SubTotal = subTotal;
            Vat = vat;
        }
    }

public class OrderDetailRecord : ContentPartRecord
    {
        // Properties that need persistence
        public virtual int OrderRecord_Id { get; set; }
        public virtual int ProductId { get; set; }
        public virtual int Quantity { get; set; }
        public virtual decimal UnitPrice { get; set; }
        public virtual decimal VatRate { get; set; }

        // Properties that dont need persistence
        public decimal UnitVat
        {
            get { return UnitPrice * VatRate; }
        }

        public decimal Vat
        {
            get { return UnitVat * Quantity; }
        }

        public decimal SubTotal
        {
            get { return UnitPrice * Quantity; }
        }

        public decimal Total
        {
            get { return SubTotal + Vat; }
        }
    }
I'm still getting exceptions as below: (I'm only going to insert the top of the trace to keep things short)
  1. NHibernate.InvalidProxyTypeException: The following types may not be used as proxies:
    Orchard.Webshop.Models.OrderRecord: method set_Details should be 'public/protected virtual' or 'protected internal virtual'
  2. For non virtual properties im getting:
    NHibernate.InvalidProxyTypeException: The following types may not be used as proxies:
    Orchard.Webshop.Models.OrderDetailRecord: method get_UnitVat should be 'public/protected virtual' or 'protected internal virtual'
Also to put things in context, why I need OrderRecord and OrderDetailRecord is that my OrderService takes in a dependency of the following repositories:
public OrderService(... IRepository<OrderRecord> orderRepository, IRepository<OrderDetailRecord> orderDetailRepository)

According to the normal routine, we define a ContentPartRecord, a ContentPart and a Migration. But with the above code (or indeed the source code) we have the ContentPartRecord (with virtual and non virtual properties).

So is there a way to do this? as in, persist data by only having an OrderRecord/OrderDetailRecord without the corresponding "Part" classes. Also note that part about taking in IRepository dependencies. Does this mean, there need to be handlers (mandatorily) for wiring up data access?

Any suggestions are appreciated.
Oct 28, 2013 at 1:52 AM
A quick note: In the above modified code, I had inherited from ContentPartRecord to get the Id field and skip it in Migrations.cs.
But I have taken that inheritance out, and also put in manual migrations for both tables:
.Column<int>("Id", c => c.PrimaryKey().Identity())

Still facing issues (I didn't think this would change anything, but just a note).
Coordinator
Oct 28, 2013 at 5:30 AM
I really really wouldn't recommend putting those computed properties on the record. The record is a technical persistence class. If you want non-persisted properties, I would put them on the model, i.e. the part. There are also very few reasons for anything but the content manager to use IRepository.
Here, you're confusing the mapper by having a setter on details.