Do drivers handle CRUD?

Topics: Troubleshooting
Jan 21, 2012 at 6:42 AM

Hi Guys, being a designer I’m really struggling with Orchard concepts – can anyone point me in the direction of CRUD basics for Orchard? I assume, using http://docs.orchardproject.net/Documentation/Creating-1-n-and-n-n-relations  for example, that the driver is where you would handle such? And if so, could somebody point me in the right area for some documentation that would enable me to edit, for instance, the State table’s records i.e. change ‘Alabama’ to ‘Hippo’ / add a new state altogether etc. Any suggestions will be greatly appreciated, thanks in advance, Cheers Dyr.

Jan 21, 2012 at 11:15 AM

Drivers handle rendering and update (i.e. postback) logic for content parts, nothing more nothing less. (Well they also handle import/export to and from XML but that's something you'll only use occasionally).

CRUD is done either through IContentManager (for content items only) or IRepository<T> (for any other record of type T). You inject these into wherever you need them using dependency injection (covered earlier on in the documentation).

I'm writing a short article on data access because it's a common point of misconception; I'm starting to think that the documentation jumps immediately into Content Parts and leaves people with the impression this is the only data access in Orchard, but of course it would be extremely hard to complete a lot of tasks if it were so ;)

Jan 23, 2012 at 3:49 AM
Edited Jan 23, 2012 at 3:50 AM

Thanks randompete…when do you reckon you’ll have that short article dusted; I’m really struggling with all of this. Currently this is where I’m at:

I wish to create a Donation module that I will eventually attach to the User Content Type that will hold records of all previous donations by a given 'user'.  So I have created two classes – DonationRecord / DonationDetailsRecord which models the DonationPart

 

//Models > DonationRecord.cs

using System.Collections.Generic;
using Orchard.ContentManagement.Records;

namespace Donation.Models
{
    public class DonationRecord : ContentPartRecord
    {
        //public virtual int Id { get; set; }
        public virtual int UserId { get; set; }        //Orchard 'User' Id
        public virtual bool Lifetime { get; set; }     //Lifetime Member or not
        public virtual DonationDetailRecord DonationDetailRecord { get; set; }  //IList<DonationDetailRecord>?
    }
}

 

*Note: Not sure if I need UserId or not? Keeping in mind I’ll be attaching to the User content type.

 

//Models > DonationDetailRecord.cs

using System;

namespace Donation.Models
{
    public class DonationDetailRecord
    {
        public virtual int Id { get; set; }
        public virtual byte Code { get; set; } //0 - Donation, 1 - Membership, 2 - Sponsorship
        public virtual int ProjectId { get; set; }
        public virtual string TransactionId { get; set; }
        public virtual decimal Amount { get; set; }
        public virtual DateTime? PaidDate { get; set; }
    }
}


//Models > DonationPart.cs

using Orchard.ContentManagement;

namespace Donation.Models
{
    public class DonationPart : ContentPart<DonationRecord>
    {
        public int UserId
        {
            get { return Record.UserId; }
            set { Record.UserId = value; }
        }

        public bool Lifetime
        {
            get { return Record.Lifetime; }
            set { Record.Lifetime = value; }
        }
        public DonationDetailRecord Donation
        {
            get { return Record.DonationDetailRecord; }
            set { Record.DonationDetailRecord = value; }
        }
    }
}

 

Migration file:

 

using System;
using System.Collections.Generic;
using Orchard.Data.Migration;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;


namespace Donation
{
    public class DonationMigration : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .ContentPartRecord()
                    .Column<int>("UserId")
                    .Column<bool>("Lifetime")
                    .Column<int>("DonationDetailRecord_Id")
                );

            SchemaBuilder.CreateTable("DonationDetailRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("Date")
                );

            ContentDefinitionManager.AlterPartDefinition("DonationPart",
                builder => builder.Attachable());

            return 1;
        }
    }
}

The Donation Part Handler:

 

 

using Orchard.ContentManagement.Handlers;
using Orchard.Data;
using Donation.Models;

namespace Donation.Handlers
{
    public class DonationPartHandler : ContentHandler
    {
        public DonationPartHandler(IRepository<DonationRecord> repository)
        {
            Filters.Add(StorageFilter.For(repository));
        }
    }
}

The viewModel:

 

 

using System.Collections.Generic;
using Donation.Models;
using System;


namespace Donation.ViewModels
{
    public class EditDonationViewModel
    {
        public int UserId { get; set; }
        public bool Lifetime { get; set; }

        //public int Id { get; set; }
        public byte Code { get; set; } 
        public int ProjectId { get; set; }
        public string TransactionId { get; set; }
        public decimal Amount { get; set; }
        public DateTime? PaidDate { get; set; }

        public IEnumerable<DonationRecord> Donations { get; set; }
    }
}

The Donation Driver:

 

 

using JetBrains.Annotations;
using Orchard.ContentManagement.Drivers;
using Donation.Models;
using Donation.ViewModels;
using Orchard.ContentManagement;
using Donation.Services;


namespace Donation.Drivers
{
    [UsedImplicitly]
    public class DonationPartDriver : ContentPartDriver<DonationPart>
    {
        private readonly IDonationService _donationService;

        private const string TemplateName = "Parts/Donation";

        public DonationPartDriver(IDonationService donationService)
        {
            _donationService = donationService;
        }

        protected override string Prefix
        {
            get { return "Donation"; }
        }

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

            return ContentShape("Parts_Donation",
                            () => shapeHelper.Parts_Donation(
                                ContentPart: part,
                                Id: part.Id,
                                UserId: part.UserId,
                                Lifetime: part.Lifetime,
                                Code: part.Donation.Code,
                                TransactionId: part.Donation.TransactionId,
                                Date: part.Donation.PaidDate,
                                Amount: part.Donation.Amount,
                                ProjectId: part.Donation.ProjectId));
        }

        protected override DriverResult Editor(DonationPart part, dynamic shapeHelper)
        {
            return ContentShape("Parts_Donation_Edit",
                    () => shapeHelper.EditorTemplate(
                        TemplateName: TemplateName,
                        Model: BuildEditorViewModel(part),
                        Prefix: Prefix));
        }

        protected override DriverResult Editor(DonationPart part, IUpdateModel updater, dynamic shapeHelper)
        {
            var model = new EditDonationViewModel();
            updater.TryUpdateModel(model, Prefix, null, null);

            if (part.ContentItem.Id != 0)
            {
                _donationService.UpdateDonationForContentItem(
                    part.ContentItem, model);
            }

            return Editor(part, shapeHelper);
        }

        private EditDonationViewModel BuildEditorViewModel(DonationPart part)
        {
            
            var dvm = new EditDonationViewModel
            {
                UserId = part.UserId,
                Lifetime = part.Lifetime,
                Donations = _donationService.GetDonations()
            };

            if (part.Donation != null)
            {
                dvm.Code = part.Donation.Code;
                dvm.ProjectId = part.Donation.ProjectId;
                dvm.TransactionId = part.Donation.TransactionId;
                dvm.Amount = part.Donation.Amount;
                dvm.PaidDate = part.Donation.PaidDate;
            }
            return dvm;
        }
    }
}

The Donation Service:

 

 

The Donation Service:
using System.Collections.Generic;
using System.Linq;
using Orchard;
using Orchard.ContentManagement;
using Donation.ViewModels;
using Donation.Models;
using Orchard.Data;
using System;

namespace Donation.Services
{
    public interface IDonationService : IDependency
    {
        void UpdateDonationForContentItem(ContentItem item, EditDonationViewModel model);
        IEnumerable<DonationRecord> GetDonations();

        //DonationRecord CreateDonation(int uId, IEnumerable<DonationDetailRecord> items);
    }

    public class DonationService : IDonationService
    {
        private readonly IRepository<DonationRecord> _donationRepository;

        public DonationService(IRepository<DonationRecord> donationRepository)
        {
            _donationRepository = donationRepository;
        }

        public void UpdateDonationForContentItem(ContentItem item, EditDonationViewModel model)
        {
            var DonationPart = item.As<DonationPart>();
            //DonationPart.UserId = model.UserId;
            DonationPart.Lifetime = model.Lifetime;

            //Need to correct this
            DonationPart.Donation = _donationRepository.Get(
                d => d.Id == 1);
        }

        public IEnumerable<DonationRecord> GetDonations()
        {
            return _donationRepository.Table.ToList();
        }

        /*
        public DonationRecord CreateDonation(int uId, IEnumerable<DonationDetailRecord> items)
        {
            //testing
            var donation = new DonationDetailRecord
            {
                PaidDate = Convert.ToDateTime("12/12/2012"),
                Id = uId,
                TransactionId = "Asp",
                ProjectId = 1,
                Code = 0,
                Amount = 10

            };

            _donationRepository.Create(donation);

            return donation;
        }
         */
        
    }
}

 

*It’s at this stage I receive [relating to the above code's [//Need to correct this] staement:

Error  1      Cannot implicitly convert type 'Donation.Models.DonationRecord' to 'Donation.Models.DonationDetailRecord'       C:\orchard\src\Orchard.Web\Modules\Donation\Services\DonationService.cs    36     37     Donation

Second to this error, am I on the right path -- as I feel this is very complicated [for me] and I hope i'm not drowning for something that should be easily achieved? Thanks for any pointers, Cheers Dyr

PS> Sorry about the code volume.

Jan 23, 2012 at 8:39 AM

Creating a Content Part is fairly complicated, at least if you want non-trivial functionality which I think you do. But there are good reasons for the complexity as you'll realise further down the line.

That's a compilation error, if you compile in Visual Studio it should tell you the exact line of code that's at fault. You're trying to assign a DonationRecord to a variable that's a DonationDetailRecord.

Secondly, for clarity, it's best to name your ContentPartRecord "DonationPartRecord" instead of "DonationRecord". Actually it's kind of a convention within Orchard to do so and some things might not work properly otherwise. But it also means that every time you see that class you know it's Part data you're dealing with, not just a normal record. This might help clear up why you're hitting that error (hint: only one Part should exist per user...)

Jan 24, 2012 at 1:08 AM

Thanks tremendously randompete, your support to me/everyone is beyond exemplary.  I overhauled my module to suit your suggested orchard convention, and it worked straightaway.

Now I’m trying to get the BuildEditorViewModel sorted out [currently it’s returning a list of all donations, not user specific]. So, I need to create a service, such as – GetMemberDonations() and change my driver to suit – ‘right’? So my question is [understanding my knowledge is severely limited, as I imagine you’ve already guessed] – can I query a repository / IEnumerable (returning all records with the same id) as an enumerable? Something like:

public IEnumerable<DonationRecord> GetMemberDonations()
{
return _donationRepository.Get(d => d.Id == 1);
}
Which doesn’t work. Or should I’ve used an IContentManager? Thanks in advance, Cheers Dyr

Jan 24, 2012 at 2:38 AM

Well, I went with:

        public IEnumerable<DonationRecord> GetDonations()
        {
            return _donationRepository.Table.ToList();
        }
        
        public IEnumerable<DonationRecord> GetMemberDonations()
        {
            return this.GetDonations().Where(d => d.Id == 1);
        }
...is this a sound approach? Cheers Peter

Jan 24, 2012 at 3:49 AM
Edited Jan 24, 2012 at 6:09 AM

In respect to my two last posts [really need to stop diverting from the original topic; 'last time', promise!] -- so far, I’ve been intending to store the UserId [FK] as an int within my DonationRecord model. Can I reference the 'UserPartRecord' directly; if so, how do I go about it and is it beneficial...I mean, can I setup a FK relationship [...migration restructure]?

Eg.

 

namespace Donation.Models
{
   public class DonationRecord
   {
       public virtual int Id { get; set; }
       public virtual byte Code { get; set; } //0 - Donation, 1 - Membership, 2 - Sponsorship
       public virtual int ProjectId { get; set; }
       public virtual string TransactionId { get; set; }
       public virtual decimal Amount { get; set; }
       public virtual DateTime? PaidDate { get; set; }
       public virtual UserPartRecord User { get; set; }
   }
}

 

Or should I go along as planned with a UserId FK column, what is the best / recommended approach for such? Thanks Dyr

PS> This is what I mean by changing the migration file:

namespace Donation
{
    public class DonationMigration : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("MemberPartRecord",
                table => table
                    .ContentPartRecord()
                    .Column<bool>("Lifetime")
                    .Column<int>("DonationRecord_UserId")
                );

            SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("UserId", �FK�)
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("PaidDate")
                );

            ContentDefinitionManager.AlterPartDefinition("MemberPart",
                builder => builder.Attachable());

            return 1;
        }

    }
}

 

�FK� = not sure of the syntax - if there is any?
Jan 24, 2012 at 10:45 AM

You can reference foreign tables, unfortunately in the case of UserPartRecord it doesn't give you much benefit. The thing is UserPartRecord is actually part of a content item, so to get the rest of the data attached to that user you have to go off and do IContentManager.Get(id) anyway.

There's documentation here on 1-n and n-n relationships: http://docs.orchardproject.net/Documentation/Creating-1-n-and-n-n-relations

Jan 29, 2012 at 11:26 PM
Edited Jan 29, 2012 at 11:31 PM

It appears the code I posted above:

 

namespace Donation
{
    public class DonationMigration : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("MemberPartRecord",
                table => table
                    .ContentPartRecord()
                    .Column<bool>("Lifetime")
                    .Column<int>("DonationRecord_MemberId")
                );

            SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("MemberId")
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("PaidDate")
                );

            ContentDefinitionManager.AlterPartDefinition("MemberPart",
                builder => builder.Attachable());

            return 1;
        }

    }
}

 

…doesn’t work, Orchard seems to expect ‘Donation_Id’ by convention; is this the case?

I also tried adding:

 

        public int UpdateFrom1()
        {
            SchemaBuilder.CreateForeignKey("FK_Attribute",
                "DonationRecord", new string[] { "MemberId" },
                "MemberPartRecord", new string[] { "Id" });

            return 2;
        }

 

Please explain what I’m doing wrong, as I’ve tried many futile alterations – I’ve already read through the link you recommended; I just can’t get my head around ‘the Orchard way’?

If I’ve explained my desired result in the past erroneously, here is what I’m trying to achieve.

 

http://www.powsow.com/result.jpg

Thanks again for all your help, Dyr

Coordinator
Jan 30, 2012 at 8:24 PM

The doc topic linked above is pretty clear on this: "It is important here that the name of the column that will represent the relation is the name of the column on the "1" end of the relation, followed by an underscore and the name of the column of the "n" end of the relation."

So yes, DonationRecord_Id.

Jan 31, 2012 at 4:23 AM

Yeah, I did read that and had a module functioning as such [migration file found much earlier in this topic] - but It did not create the relationship I was after, nor enforce integrity like it should [basically, it just created a DonationRecord_Id column that was always NULL.

The migration i'm using creates the tables and relationship i'm after [i.e. the picture above]:

 

using System;
using System.Collections.Generic;
using Orchard.Data.Migration;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;


namespace Donation
{
    public class DonationMigration : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("MemberPartRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<bool>("Lifetime")
                );

            SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("MemberId")
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("PaidDate")
                );

           ContentDefinitionManager.AlterPartDefinition("MemberPart",
                builder => builder.Attachable());

            return 1;
        }

        public int UpdateFrom1()
        {
            SchemaBuilder.CreateForeignKey("FK_DonationRecord_MemberPartRecord",
                "DonationRecord", new string[] { "MemberId" },
                "MemberPartRecord", new string[] { "Id" });

            return 2;
        }
    }
}

 

...but when it comes to insering a record, I always get:

Server Error in '/Orc' Application.

Invalid column name 'DonationRecord_id'.

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: System.Data.SqlClient.SqlException: Invalid column name 'DonationRecord_id'.

Source Error:

Line 45: 
Line 46:                 Logger.Debug("Final work for transaction being performed");
Line 47:                 _scope.Dispose();
Line 48:                 Logger.Debug("Transaction disposed");
Line 49:             }


Source File: C:\orchard\src\Orchard\Data\TransactionManager.cs    Line: 47

Stack Trace:

[SqlException (0x80131904): Invalid column name 'DonationRecord_id'.]
   System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection) +404
   System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning() +412
   System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj) +1363
   System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString) +6387741
   System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async) +6389442
   System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result) +538
   System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe) +689
   System.Data.SqlClient.SqlCommand.ExecuteNonQuery() +327
   NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd) +883
   NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation) +92
   NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session) +2052

[GenericADOException: could not insert: [Donation.Models.MemberPartRecord#13][SQL: INSERT INTO Donation_MemberPartRecord (Lifetime, DonationRecord_id, Id) VALUES (?, ?, ?)]]
   NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Boolean[] notNull, Int32 j, SqlCommandInfo sql, Object obj, ISessionImplementor session) +2710
   NHibernate.Persister.Entity.AbstractEntityPersister.Insert(Object id, Object[] fields, Object obj, ISessionImplementor session) +711
   NHibernate.Action.EntityInsertAction.Execute() +713
   NHibernate.Engine.ActionQueue.Execute(IExecutable executable) +562
   NHibernate.Engine.ActionQueue.ExecuteActions(IList list) +196
   NHibernate.Engine.ActionQueue.ExecuteActions() +49
   NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) +641
   NHibernate.Event.Default.DefaultFlushEventListener.OnFlush(FlushEvent event) +247
   NHibernate.Impl.SessionImpl.Flush() +522
   NHibernate.Transaction.DistributedTransactionContext.System.Transactions.IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment) +927

[TransactionAbortedException: The transaction has aborted.]
   System.Transactions.TransactionStateAborted.EndCommit(InternalTransaction tx) +17
   System.Transactions.CommittableTransaction.Commit() +453
   System.Transactions.TransactionScope.InternalDispose() +638
   System.Transactions.TransactionScope.Dispose() +2492
   Orchard.Data.TransactionManager.System.IDisposable.Dispose() in C:\orchard\src\Orchard\Data\TransactionManager.cs:47
   Autofac.Core.Disposer.Dispose(Boolean disposing) +82
   Autofac.Util.Disposable.Dispose() +43
   Autofac.Core.Lifetime.LifetimeScope.Dispose(Boolean disposing) +31
   Autofac.Util.Disposable.Dispose() +43
   Orchard.Environment.<>c__DisplayClass2.<.ctor>b__0() in C:\orchard\src\Orchard\Environment\WorkContextAccessor.cs:75
   Orchard.Environment.HttpContextScopeImplementation.System.IDisposable.Dispose() in C:\orchard\src\Orchard\Environment\WorkContextAccessor.cs:80
   Orchard.Mvc.Routes.HttpAsyncHandler.EndProcessRequest(IAsyncResult result) in C:\orchard\src\Orchard\Mvc\Routes\ShellRoute.cs:151
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +469
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +375



Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.0.30319.237

...which as I understand is by convention?  So how does one create such a relationship like 'that' of which I want?  Thanks for your patience, Dyr

Coordinator
Jan 31, 2012 at 4:30 AM

What is the cardinality supposed to be between Donation and DonationDetails?

Jan 31, 2012 at 4:41 AM

thanks for the quick reply. It's meant to be '1 to many'. Cheers

Coordinator
Jan 31, 2012 at 4:42 AM

In which direction?

Jan 31, 2012 at 4:53 AM

A Donation can have many DonationDetails. This was confusing, so as randompete recommended I changed the models in my latest schema to ‘MemberPartRecord’ [formerly Donation] and ‘DonationRecord’ [formerly DonationDetails], again, a Member can make/have many Donations. Thanks Dyr

Coordinator
Jan 31, 2012 at 5:06 AM

So the migration in your message from Jan 22 at 8:49 PM is backwards, right?

Jan 31, 2012 at 5:31 AM

Re: this one [my times are off probably due to location]

using System;
using System.Collections.Generic;
using Orchard.Data.Migration;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;


namespace Donation
{
    public class DonationMigration : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .ContentPartRecord()
                    .Column<int>("UserId")
                    .Column<bool>("Lifetime")
                    .Column<int>("DonationDetailRecord_Id")
                );

            SchemaBuilder.CreateTable("DonationDetailRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("Date")
                );

            ContentDefinitionManager.AlterPartDefinition("DonationPart",
                builder => builder.Attachable());

            return 1;
        }
    }
}

...I guess so if you mean:

.Column<int>("DonationDetailRecord_Id")

//should be:

.Column<int>("Id_DonationDetailRecord")

Or am I completely missing the point? Sorry for most likely frustrating that hell out of you, cheers Dyr

Coordinator
Jan 31, 2012 at 5:25 PM

No, the column should be on the other table: it should be details pointing at domations. This way, multiple details records can point at the same donation record. One donation record, multiple details. Makes sense?

Jan 31, 2012 at 7:09 PM

Very much so bertrandleroy, thanks for all your efforts - they're much appreciated...'I clearly have a long way to go'. Thanks again, Dyr

Jan 31, 2012 at 9:32 PM

Hi, I do not get it, how will the amended migration wire up properly?

Are you saying Dyr's migration should look like this?

namespace Donation
{
    public class DonationMigration : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .ContentPartRecord()
                    .Column<int>("UserId")
                    .Column<bool>("Lifetime")

                );

            SchemaBuilder.CreateTable("DonationDetailRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("DonationDetailRecord_Id")
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("Date")
                );

            ContentDefinitionManager.AlterPartDefinition("DonationPart",
                builder => builder.Attachable());

            return 1;
        }
    }
}

Coordinator
Jan 31, 2012 at 9:45 PM

No. It should be DonationRecord_Id. and of course the records and parts should reflect that exactly.

Jan 31, 2012 at 10:47 PM

Ok that makes sense with "It is important here that the name of the column that will represent the relation is the name of the column on the "1" end of the relation, followed by an underscore and the name of the column of the "n" end of the relation."

and then in Dyr's example, completely swap DonationPart.cs around with DonationRecord.cs?

Feb 2, 2012 at 1:39 AM

Hey Newnoob, using my post 'Jan 30 at 12:26 AM' for reference, if this is what you mean by swapping around then I believe so; note

.Column<int>("MemberPartRecord_id")

hopefully bertrandleroy can confirm this. Cheers Dyr

using System;

namespace Donation.Models {
    public class DonationRecord
    {
        public virtual int Id { get; set; }
        public virtual byte Code { get; set; } 
        public virtual int ProjectId { get; set; }
        public virtual string TransactionId { get; set; }
        public virtual decimal Amount { get; set; }
        public virtual DateTime? PaidDate { get; set; }
        public virtual MemberPartRecord MemberPartRecord { get; set; }
    }
}

MemberPartRecord:

using System.Collections.Generic;
using Orchard.ContentManagement;
using Orchard.ContentManagement.Records;


namespace Donation.Models
{
    public class MemberPartRecord : ContentPartRecord
    {
        public virtual int UserId { get; set; }
        public virtual bool Lifetime { get; set; }                   
    }

    
    public class MemberPart : ContentPart<MemberPartRecord>
    {
        public int UserId
        {
            get { return Record.UserId; }
            set { Record.UserId = value; }
        }

        public bool Lifetime
        {
            get { return Record.Lifetime; }
            set { Record.Lifetime = value; }
        }
    }
}

Migration:
using System;
using System.Collections.Generic;
using Orchard.Data.Migration;
using Orchard.ContentManagement.MetaData;
using Orchard.Core.Contents.Extensions;


namespace Donation
{
    public class DonationMigration : DataMigrationImpl
    {
        public int Create()
        {
            SchemaBuilder.CreateTable("DonationRecord",
                table => table
                    .Column<int>("Id", column => column.PrimaryKey().Identity())
                    .Column<int>("MemberPartRecord_id")
                    .Column<byte>("Code")
                    .Column<int>("ProjectId")
                    .Column<string>("TransactionId")
                    .Column<decimal>("Amount")
                    .Column<DateTime>("PaidDate")
                );
            
            SchemaBuilder.CreateTable("MemberPartRecord",
                table => table
                    .ContentPartRecord()
                    .Column<int>("UserId")
                    .Column<bool>("Lifetime")
                );
            ContentDefinitionManager.AlterPartDefinition("MemberPart",
                builder => builder.Attachable());

            return 1;
        }
    }
}

Feb 2, 2012 at 7:19 AM

Thanks, that is what I thought...so then how does orchard handle this foreign relationships integrity? Do you manually insert the fk or is it replicated?

Coordinator
Feb 2, 2012 at 6:24 PM

What do you mean replicated? If you set-up everything correctly and according to the conventions in place, you just have to set properties on your parts and the framework takes care of the rest.