How to make a content part Versionable

Topics: Customizing Orchard
Sep 30, 2014 at 10:26 PM
How do you go about making a content part versionable? I've tried looking at the bodypart to see what needs to be done to accomplish this but am unable to recreate it thusfar. Any help would be appreciated.
Developer
Sep 30, 2014 at 11:51 PM
If you are using a part record as the underlying storage:
  1. Derive your contentpart record class from ContentPartVersionRecord (see BodyPartRecord: public class BodyPartRecord : ContentPartVersionRecord)
  2. When creating the table in your migration, create not only the PK column, but also the FK to the content item record ID. Essentially, use .ContentPartVersionRecord() instead of .ContentPartRecord(). See the Create method of the Orchard.Core.Common.Migrations class to see how the BodyPartRecord table is created.
If you are using InfoSet as the underlying storage:
  1. Implement your part properties' getter and setter by specifying "true" for the last argument (named "versioned") of the Retrieve and Store extension methods.
Oct 1, 2014 at 6:34 PM
I tried copying the way the BodyPartRecord is set up, but now whenever I create a content item with that content part in it I get a "Not found The page you are looking for does not exist." error upon creation/publish and the item isn't created.

Migrations.cs

namespace Deck
{
public class Migrations : DataMigrationImpl
{

    public int Create()
    {
        // Creating table DeckRecord
        SchemaBuilder.CreateTable("DeckPartRecord",
             table => table
                 .ContentPartVersionRecord()
                 .Column<string>("DeckMessage", column => column.Unlimited())
             );

        ContentDefinitionManager.AlterPartDefinition("DeckPart", builder => builder
            .Attachable()
            .WithDescription("Custom alterable header to be used in content items."));

        return 1;
    }

    public int UpdateFrom1()
    {
        ContentDefinitionManager.AlterPartDefinition("DeckPart", builder => builder
            .WithDescription("Custom alterable header to be used in content items."));
        return 2;
    }
}
}

Deck.cs

namespace Deck.Models
{
public class DeckRecord : ContentPartVersionRecord
{
    public virtual string DeckMessage { get; set; }
}

public class DeckPart : ContentPart<DeckRecord>
{
    public string DeckMessage
    {
        get { return Retrieve(x => x.DeckMessage); }
        set { Store(x => x.DeckMessage, value); }
    }
}
}
Developer
Oct 1, 2014 at 10:06 PM
Your code seems fine, although I think you should include an additional parameter with the Retrieve and Store calls, indicating that you want the store the values using the versioned info set.

To figure out the actual cause of the error, attach a debugger (make sure to break on ALL exceptions, including the ones for which there is a try/catch block), and observe. I suspect you'll see the root cause very clearly.
Oct 2, 2014 at 5:07 PM
Edited Oct 2, 2014 at 5:11 PM
I'm getting the following exception when debugging the code for my Deck Content Part:

An exception of type 'NHibernate.Exceptions.GenericADOException' occurred in NHibernate.dll but was not handled in user code

Additional information: could not insert: [Deck.Models.DeckPartRecord#70][SQL: INSERT INTO Deck_DeckPartRecord (DeckMessage, ContentItemRecord_id, Id) VALUES (?, ?, ?)]

NHibernate.Exceptions.GenericADOException was unhandled by user code
HResult=-2146232832
Message=could not insert: [Deck.Models.DeckPartRecord#70][SQL: INSERT INTO Deck_DeckPartRecord (DeckMessage, ContentItemRecord_id, Id) VALUES (?, ?, ?)]
Source=NHibernate
SqlString=INSERT INTO Deck_DeckPartRecord (DeckMessage, ContentItemRecord_id, Id) VALUES (?, ?, ?)
StackTrace:
   at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 2676
   at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Object obj, ISessionImplementor session) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 3062
   at NHibernate.Action.EntityInsertAction.Execute() in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Action\EntityInsertAction.cs:line 59
   at NHibernate.Engine.ActionQueue.Execute(IExecutable executable) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:line 136
   at NHibernate.Engine.ActionQueue.ExecuteActions(IList list) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:line 126
   at NHibernate.Engine.ActionQueue.ExecuteActions() in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Engine\ActionQueue.cs:line 169
   at NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Event\Default\AbstractFlushingEventListener.cs:line 249
   at NHibernate.Event.Default.DefaultAutoFlushEventListener.OnAutoFlush(AutoFlushEvent event) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Event\Default\DefaultAutoFlushEventListener.cs:line 37
   at NHibernate.Impl.SessionImpl.AutoFlushIfRequired(ISet`1 querySpaces) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Impl\SessionImpl.cs:line 1182
   at NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Impl\SessionImpl.cs:line 1959
   at NHibernate.Impl.CriteriaImpl.List(IList results) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Impl\CriteriaImpl.cs:line 265
   at NHibernate.Impl.CriteriaImpl.List[T]() in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Impl\CriteriaImpl.cs:line 276
   at Orchard.ContentManagement.DefaultContentManager.GetManyImplementation(QueryHints hints, Action`2 predicate) in c:\inetpub\MyOrchard\src\Orchard\ContentManagement\DefaultContentManager.cs:line 359
   at Orchard.ContentManagement.DefaultContentManager.Get(Int32 id, VersionOptions options, QueryHints hints) in c:\inetpub\MyOrchard\src\Orchard\ContentManagement\DefaultContentManager.cs:line 160
   at Orchard.ContentManagement.DefaultContentManager.Get(Int32 id, VersionOptions options) in c:\inetpub\MyOrchard\src\Orchard\ContentManagement\DefaultContentManager.cs:line 131
   at Orchard.PublishLater.ViewModels.PublishLaterViewModel.get_HasPublished()
InnerException: System.Data.SqlServerCe.SqlCeException
   HResult=-2147467259
   Message=The specified table does not exist. [ Deck_DeckPartRecord ]
   Source=SQL Server Compact ADO.NET Data Provider
   ErrorCode=-2147467259
   NativeError=0
   StackTrace:
        at System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr)
        at System.Data.SqlServerCe.SqlCeCommand.CompileQueryPlan()
        at System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options)
        at System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery()
        at NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\AdoNet\AbstractBatcher.cs:line 203
        at NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\AdoNet\NonBatchingBatcher.cs:line 40
        at NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session) in c:\Users\sebros\Documents\My Projects\nhibernate-core\src\NHibernate\Persister\Entity\AbstractEntityPersister.cs:line 2656
   InnerException: 
Developer
Oct 2, 2014 at 6:28 PM
So there you go - the exception reveals that your Deck_DeckPartRecord table does not exist.
If this is a development database, I would wipe out your database, setup the site, attach a debugger, then see why your migration is failing. Or maybe it will now run fine and you made a mistake earlier, causing the table to not be there.
Oct 2, 2014 at 10:25 PM
Edited Oct 2, 2014 at 10:35 PM
I'm sorry, one last question. I packaged my Deck module and installed it on a clean build of orchard and it works as expected (it is now versionable). Do I need to delete the old table associated with my Deck module so the new one that has columns for the versioning can be created. I tried to install my newer version on top of the old version to see if that would rewrite the db table but it didn't. Also, noob question I just realized I have no idea how to open the db file to edit it. I'm just using the built-in file that orchard uses instead of my own sql database. I've tried opening the .sdf file located in the App_Data\Sites\Default folder, which I thought was the database file but I can't find a program that will open it.
Oct 3, 2014 at 5:15 PM
I enabled the Update Database module and ran the comman for updating the database which fixed the issue for the default site, but none of the tenant databases were updated. How do I run the update database comman on tenant sites instead of the default site?
Developer
Oct 3, 2014 at 7:24 PM
To manage your SQL CE database, you can use Visual Studio by turning on the 'Show All Files' option on the Orchard.Web project, then expand App_Data/Sites/Default folder, where you'll see your database, then double click that file. Visual Studio will connect to it. Alternatively, you can use WebMatrix (which I sometimes do as I somehow managed to mess up my SQL CE tools).
Developer
Oct 3, 2014 at 9:19 PM
You can execute commands for other tenants by specifying the /Tenant switch (I found this using the Help command :)).