question about content types / parts / inheritence

Topics: Writing modules
Nov 9, 2011 at 4:04 PM

Hello

I have a question about content types / parts / inheritence. I have a db structure that is  |DESCRIPTOR| 1------*|ELEMENT|

Basically I have different types of descriptor objects, some have descriptors have one element object and others have 3 element objects.

What I want to do is create a content type for each type of descriptor and be able to utilise orchards handlers and drivers to edit content in admin area.

Im not sure if it is possible but I was thinking I could create the DescriptorPartRecord and the ElementPartRecord (which both inherit ContentPartRecord). I then create a PartRecord for each of my descriptors with whatever number properties for Element as they need. Then inherit  DescriptorPartRecord. Then using migrations to create the TypeDefinition for the descriptors.

Basically what I have doesn’t work so either I’m not doing something right or it’s not possible.

I get this error message when I implement the above idea:

An association from the table DescriptorSixOnePartRecord refers to an unmapped class: Spa.Prototype.DataStructure.Models.ElementPart 

Nov 9, 2011 at 6:04 PM

I'm not 100% clear what you're trying to do; but that error just means that some of your model or migration code is incorrect. It could be that you need to name your model "ElementPartRecord" instead of "ElementPart". Failing that, if you post your code (models, migrations) maybe someone can see what's wrong; it's a bit hard to tell from just an error message if we can't see the code causing it ...

Nov 10, 2011 at 10:42 AM

Hello Pete

I will be looking at this today if i get nowhere, how/where do i post the code files?

Cheers 

Nov 10, 2011 at 11:33 AM

 

Sorry but I’m not getting my head around it and I’m sure what I’m trying to do is simple but I can’t work it out. OK so it may be easier if I tell you what I need and if you could please tell me the best way to implement it.

At the DB level I have a 1 to n relationship

Descriptor_Table
Id
Name

Element_Table
Id
Description
Descriptor _Id

I want to create two content parts from this.

DescriptorPartA
- Name
- ElementA

DescriptorPartB
- Name
- Element1
- Element2
- Element3

It is a bit like the Building a 1-N Relationship example but in reverse (so addresses can have multiple states, but states can only have one address)

Nov 10, 2011 at 12:35 PM

Post your code on here, use the button next to HTML to format it properly.

Nov 10, 2011 at 5:21 PM

 

OK so I have overcome the original error, I think it is because I change ElementPart to ElementRecord. I have included the code below.

In orchard admin area I have created the a content type using DescriptorPartA and another content type using DescriptorPartB, but I am now having an issue with saving the content. When I click save I get the following error.

object references an unsaved transient instance - save the transient instance before flushing. Type: Spa.Prototype.DataStructure.Models.ElementRecord, Entity: Spa.Prototype.DataStructure.Models.ElementRecord

Im sure I have to save the Element objects, but I don’t know how.

Thanks for your Help

Charles

 

MIGRATIONS

using System;

using System.Collections.Generic;

using System.Data;

using Orchard.ContentManagement.Drivers;

using Orchard.ContentManagement.MetaData;

using Orchard.ContentManagement.MetaData.Builders;

using Orchard.Core.Contents.Extensions;

using Orchard.Data.Migration;

using Orchard.Environment.Extensions;

 

namespace Spa.Prototype.DataStructure

{

    public class Migrations : DataMigrationImpl

    {

        public int Create()

        {

            SchemaBuilder.CreateTable("DescriptorPartRecord", table => table

                .ContentPartRecord()

                .Column<string>("DesctriptorName")

                .Column<string>("DescriptorType")

            );

 

            SchemaBuilder.CreateTable("ElementRecord", table => table

                //.ContentPartRecord()

                .Column("Id", DbType.Int32, column => column.PrimaryKey().Identity())

                .Column("Description", DbType.String)

                .Column("DescriptorPartRecord_Id", DbType.Int32, column => column.NotNull())

            );

 

            return 1;

        }

 

        public int UpdateFrom1()

        {

            ContentDefinitionManager.AlterPartDefinition("DescriptorAPart",

               builder => builder

                   .Attachable());

             

            ContentDefinitionManager.AlterPartDefinition("DescriptorBPart",

               builder => builder

                   .Attachable());

 

            ContentDefinitionManager.AlterTypeDefinition("DescriptorA",

                cfg => cfg

                    .WithPart("CommonPart")

                    .WithPart("DescriptorAPart")

                );

 

            ContentDefinitionManager.AlterTypeDefinition("DescriptorB",

                cfg => cfg

                    .WithPart("CommonPart")

                    .WithPart("DescriptorBPart")

                );

            return 2;

        }

 

    }

}

 

 

MODELS

 

using System.ComponentModel.DataAnnotations;

using Orchard.ContentManagement;

using Orchard.ContentManagement.Records;

using System.Collections.Generic;

 

namespace Spa.Prototype.DataStructure.Models

{

    public class DescriptorPartRecord : ContentPartRecord

    {

        public virtual string DescriptionName { get; set; }

    }

}

 

 

 

using System.ComponentModel.DataAnnotations;

using Orchard.ContentManagement;

using Orchard.ContentManagement.Records;

using System.Collections.Generic;

 

namespace Spa.Prototype.DataStructure.Models

{

    public class DescriptorPart : ContentPart<DescriptorPartRecord>

    {

        public string DescriptionName

        {

            get { return Record.DescriptionName; }

            set { Record.DescriptionName = value; }

        }

    }

}

 

 

using System.ComponentModel.DataAnnotations;

using Orchard.ContentManagement;

using Orchard.ContentManagement.Records;

 

namespace Spa.Prototype.DataStructure.Models

{

    public class ElementRecord

    {

        public virtual int Id { get; set; }

        public virtual string ElementName { get; set; }

        //public virtual int DescriptorPartRecord_Id { get; set; }

    }

}

 

 

using System.ComponentModel.DataAnnotations;

using Orchard.ContentManagement;

using Orchard.ContentManagement.Records;

using Spa.Prototype.DataStructure.Models;

using System.Collections.Generic;

namespace Spa.Prototype.DataStructure.Models

{

    public class DescriptorAPartRecord : DescriptorPartRecord

    {

        public virtual ElementRecord ElementA { get; set; }

        public virtual ElementRecord ElementB { get; set; }

        public virtual ElementRecord ElementC { get; set; }

    }

 

    public class DescriptorAPart : ContentPart<DescriptorAPartRecord>

    {

 

        public virtual string DescriptionName

        {

            get { return Record.DescriptionName; }

            set { Record.DescriptionName = value; }

        }

 

        public virtual ElementRecord ElementA

        {

            get { return Record.ElementA; }

            set { Record.ElementA = value; }

        }

 

        public virtual ElementRecord ElementB

        {

            get { return Record.ElementB; }

            set { Record.ElementB = value; }

        }

 

        public virtual ElementRecord ElementC

        {

            get { return Record.ElementC; }

            set { Record.ElementC = value; }

        }

 

    }

}

 

 

 

using System.ComponentModel.DataAnnotations;

using Orchard.ContentManagement;

using Orchard.ContentManagement.Records;

using Spa.Prototype.DataStructure.Models;

using System.Collections.Generic;

using Orchard.Data.Conventions;

 

namespace Spa.Prototype.DataStructure.Models

{

    public class DescriptorBPartRecord : DescriptorPartRecord

    {

        public virtual ElementRecord ElementZ { get; set; }

    }

 

    public class DescriptorBPart : ContentPart<DescriptorBPartRecord>

    {

 

        public virtual string DescriptionName

        {

            get { return Record.DescriptionName; }

            set { Record.DescriptionName = value; }

        }

 

        public virtual ElementRecord ElementZ

        {

            get { return Record.ElementZ; }

            set { Record.ElementZ = value; }

        }

    }

}

 

 

 

DRIVERS

 

using Spa.Prototype.DataStructure.Models;

using Orchard.ContentManagement.Drivers;

using Orchard.ContentManagement;

 

namespace Spa.Prototype.DataStructure.Drivers

{

    public class DescriptorADriver : ContentPartDriver<DescriptorAPart>

    {

        protected override DriverResult Display(DescriptorAPart part, string displayType, dynamic shapeHelper)

        {

            return ContentShape("Parts_DescriptorA",

                () => shapeHelper.Parts_DescriptorA(

                    DescriptionName: part.DescriptionName,

                    ElementA: part.ElementA,

                    ElementB: part.ElementB,

                    ElementC: part.ElementC));

        }

 

        //GET

        protected override DriverResult Editor(DescriptorAPart part, dynamic shapeHelper)

        {

            return ContentShape("Parts_DescriptorA_Edit",

                () => shapeHelper.EditorTemplate(

                    TemplateName: "Parts/DescriptorA",

                    Model: part,

                    Prefix: Prefix));

        }

 

        //POST

        protected override DriverResult Editor(DescriptorAPart part, IUpdateModel updater, dynamic shapeHelper)

        {

            updater.TryUpdateModel(part, Prefix, null, null);

            return Editor(part, shapeHelper);

        }

    }

}

 

 

using Spa.Prototype.DataStructure.Models;

using Orchard.ContentManagement.Drivers;

using Orchard.ContentManagement;

 

namespace Spa.Prototype.DataStructure.Drivers

{

    public class DescriptorBDriver : ContentPartDriver<DescriptorBPart>

    {

        protected override DriverResult Display(DescriptorBPart part, string displayType, dynamic shapeHelper)

        {

            return ContentShape("Parts_DescriptorB",

                () => shapeHelper.Parts_DescriptorA(

                    DescriptionName: part.DescriptionName,

                    ElementZ: part.ElementZ));

        }

 

        //GET

        protected override DriverResult Editor(DescriptorBPart part, dynamic shapeHelper)

        {

            return ContentShape("Parts_DescriptorB_Edit",

                () => shapeHelper.EditorTemplate(

                    TemplateName: "Parts/DescriptorB",

                    Model: part,

                    Prefix: Prefix));

        }

 

        //POST

        protected override DriverResult Editor(DescriptorBPart part, IUpdateModel updater, dynamic shapeHelper)

        {

            updater.TryUpdateModel(part, Prefix, null, null);

            return Editor(part, shapeHelper);

        }

    }

}

 

 

HANDLERS

 

using Orchard.ContentManagement.Handlers;

using Spa.Prototype.DataStructure.Models;

using Orchard.Data;

 

namespace Spa.Prototype.DataStructure.Handlers

{

    public class DescriptorAHandler : ContentHandler

    {

        public DescriptorAHandler(IRepository<DescriptorAPartRecord> repository)

        {

            Filters.Add(StorageFilter.For(repository));

        }

    }

}

 

 

using Orchard.ContentManagement.Handlers;

using Spa.Prototype.DataStructure.Models;

using Orchard.Data;

 

namespace Spa.Prototype.DataStructure.Handlers

{

    public class DescriptorBHandler : ContentHandler

    {

        public DescriptorBHandler(IRepository<DescriptorBPartRecord> repository)

        {

            Filters.Add(StorageFilter.For(repository));

        }

    }

}

 

 

VIEWS > EDITORTEMPLATES > PARTS

 

DescriptorA.schtml

 

@model Spa.Prototype.DataStructure.Models.DescriptorAPart

<fieldset>

    @Html.LabelFor(m => m.DescriptionName, T("Description Name"))

    @Html.TextBoxFor(m => m.DescriptionName, new { @class = "textMedium" })

    @Html.ValidationMessageFor(m => m.DescriptionName, "*")

</fieldset>

<fieldset>

    @Html.LabelFor(m => m.ElementA.ElementName, T("Element Name"))

    @Html.TextBoxFor(m => m.ElementA.ElementName, new { @class = "textMedium" })

    @Html.ValidationMessageFor(m => m.ElementA.ElementName, "*")

</fieldset>

<fieldset>

    @Html.LabelFor(m => m.ElementB.ElementName, T("Element Name"))

    @Html.TextBoxFor(m => m.ElementB.ElementName, new { @class = "textMedium" })

    @Html.ValidationMessageFor(m => m.ElementB.ElementName, "*")

</fieldset>

<fieldset>

    @Html.LabelFor(m => m.ElementB.ElementName, T("Element Name"))

    @Html.TextBoxFor(m => m.ElementB.ElementName, new { @class = "textMedium" })

    @Html.ValidationMessageFor(m => m.ElementB.ElementName, "*")

</fieldset>

 

DescriptorB.schtml

 

@model Spa.Prototype.DataStructure.Models.DescriptorBPart

<fieldset>

    @Html.LabelFor(m => m.DescriptionName, T("Description Name"))

    @Html.TextBoxFor(m => m.DescriptionName, new { @class = "textMedium" })

    @Html.ValidationMessageFor(m => m.DescriptionName, "*")

</fieldset>

<fieldset>

    @Html.LabelFor(m => m.ElementZ.ElementName, T("Element Name"))

    @Html.TextBoxFor(m => m.ElementZ.ElementName, new { @class = "textMedium" })

    @Html.ValidationMessageFor(m => m.ElementZ.ElementName, "*")

</fieldset>

 

 

VIEWS > PARTS

 

DescriptorA.schtml

 

@model Spa.Prototype.DataStructure.Models.DescriptorAPart

 

@T("Description Name: "): @Model.DescriptionName<br />

@T("ElementA Name: "): @Model.ElementA.ElementName<br />

@T("ElementB Name: "): @Model.ElementB.ElementName<br />

@T("ElementC Name: "): @Model.ElementC.ElementName<br />

 

DescriptorB.schtml

 

@model Spa.Prototype.DataStructure.Models.DescriptorBPart

 

@T("Description Name: "): @Model.DescriptionName<br />

@T("ElementA Name: "): @Model.ElementZ.ElementName<br />

Nov 10, 2011 at 5:52 PM
Edited Nov 10, 2011 at 5:52 PM

You're not creating tables for DescriptorAPartRecord or DescriptorBPartRecord - I think that's the problem.

Nov 10, 2011 at 10:16 PM

OK, it may be that its not possible or I'm doing something wrong but DescriptorAPartRecord and DescriptorBPartRecord inherit from DescriptorPartRecord so I was thinking the DescriptorPartRecord Table should be used

Nov 11, 2011 at 12:25 AM

Unfortunately that assumption isn't true :) you need to tell the system what to do (it can't possibly work out your intentions if you start inheriting from models but don't tell it how to handle them)

Nov 11, 2011 at 8:24 AM

OK now I'm stuck. , Can you please show me an example, or point me in the right direction, of how to have two parts (DescriptorAPart and DescriptorBPart as above) and have them write back to the same table

Thanks again for you help

Nov 11, 2011 at 11:52 AM

It isn't something I've tried, and it sounds like a slightly strange design! You could try just declaring both parts as ContentPart<DescriptorPartRecord>, which is the model and table that actually exists. But if you want both writing to the same table, why not just create a single part and enable the different behaviours using content part settings?

Nov 11, 2011 at 12:36 PM

Hmm yes, I think it may be strange how I'm trying to implement it because I don't know how to achieve what I want in orchard, but the design is quite simple, i just want parent objects that are assoiated to a fixed number of child objects. in my scenario i have two types of parents one who has a single child and another that has three children. I could have a generic parent that has a List<> of children but then I would have to manage the list. that is why I wanted to see if could use inheritance to have parent objects that have a property for each child they own, this way it is impossible to associate any more children to a parent than they can own. 

Thanks for having a look, I would be grateful if you do have any ideas, but in the mean time I'm going have to go with creating a generic parent

Nov 11, 2011 at 1:25 PM

A typical way to represent this kind of structure in a database is with a 1:1 join. So you have a base record, and it can be 1:1 joined with either a "single child" table or a "triple child" table.

But, 1:1 joins like this are already what ContentParts give you (all ContentPartRecords are 1:1 joined to ContentItems) - so what I think you actually want to do is create 3 separate parts. One for your common (shared) fields, one for the single child, one for the triple child. Then you can create two different content types - one has the SingleChildPart, the other has the TripleChildPart. Does that make sense?