Help - How to programmatically adding people / users

Topics: Administration, General
Dec 28, 2013 at 1:33 PM
I've decided to start playing with with Orchard over the Christmas break and I've come across a problem that you guys can no doubt help me with....

Background:

I want to store details of people in the database (Person), with the ability for that person to be / or not to be a system user. How would you recommend I go about this ???


So far: -

a) I have created a PersonPartRecord, a content part called PersonPart, and a content type that has the PersonPart and the UserPart attached to it.

b) I have a driver that contains Editor methods for the GET / POST

c) I have a handler that has a StorageFilter and an ActivatingFilter (Filters.Add(new ActivatingFilter<UserPart>("Person"));)

d) I have a edit view that contains the person fields


My classes are all named correctly using the standard naming conventions, however whenever I try to add a Person content type, I'm getting the error message : -

null id in Orchard.ContentManagement.Records.ContentTypeRecord entry (don't flush the Session after an exception occurs) . 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.


Source Error:

Line 46:
Line 47: public IEnumerable<string> GetFeaturesThatNeedUpdate() {
Line 48: var currentVersions = _dataMigrationRepository.Table.ToDictionary(r => r.DataMigrationClass);
Line 49:
Line 50: var outOfDateMigrations = _dataMigrations.Where(dataMigration => {

Source File: c:\Users\Terry\Documents\Visual Studio 2012\Projects\eFlight\src\Orchard\Data\Migration\DataMigrationManager.cs Line: 48


First of all...... is my approach right, or do I have to do something to initialize a UserPart?

Secondly, this cannot be an unusual requirement, so any idea where I can see a sample of something similar?
Dec 28, 2013 at 3:56 PM
Do you have a migration class in your module to create the DB tables for records and Person content type?

Something very similar is explained here: http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-8


Dec 28, 2013 at 5:42 PM
Yes....

a) Record.......
public class PersonPartRecord : ContentPartRecord
{
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
    public virtual string Title { get; set; }
    public virtual Gender Gender { get; set; }
    public virtual DateTime? BirthDateUtc { get; set; }
    public virtual string Biography { get; set; }
    public virtual string ARN { get; set; }
    public virtual DateTime CreatedUtc { get; set; }
}
b) Part to record........
public class PersonPart : ContentPart<PersonPartRecord>
{
    public string FirstName
    {
        get { return Record.FirstName; }
        set { Record.FirstName = value; }
    }

    public string LastName
    {
        get { return Record.LastName; }
        set { Record.LastName = value; }
    }

    public string Title
    {
        get { return Record.Title; }
        set { Record.Title = value; }
    }

    public Gender Gender
    {
        get { return Record.Gender; }
        set { Record.Gender = value; }
    }

    public DateTime? BirthDateUtc
    {
        get { return Record.BirthDateUtc; }
        set { Record.BirthDateUtc = value; }
    }

    public string Biography
    {
        get { return Record.Biography; }
        set { Record.Biography = value; }
    }

    public string ARN
    {
        get { return Record.ARN; }
        set { Record.ARN = value; }
    }

    public DateTime CreatedUtc
    {
        get { return Record.CreatedUtc; }
        set { Record.CreatedUtc = value; }
    }

    public IUser User
    {
        get { return this.As<UserPart>(); }
    }
}

c) Migrations.........
    public int UpdateFrom1()
    {
        // Step 1: Create our Person table
        SchemaBuilder.CreateTable("PersonPartRecord", table => table
             .ContentPartRecord()
             .Column<string>("FirstName", c => c.WithLength(50))
             .Column<string>("LastName", c => c.WithLength(50))
             .Column<string>("Title", c => c.WithLength(20))
             .Column<string>("Gender", c => c.WithLength(10))
             .Column<DateTime>("BirthDateUtc", c => c.Nullable())
             .Column<string>("Biography", c => c.WithLength(2000))
             .Column<string>("ARN", c=> c.WithLength(20))
             .Column<DateTime>("CreatedUtc")
        );


        // Step 2: Create the Address table
        SchemaBuilder.CreateTable("AddressPartRecord", table => table
            .ContentPartRecord()
            .Column<int>("PersonId")
            .Column<string>("Type", c => c.WithLength(50))
            .Column<string>("AddressLine1", c => c.WithLength(100))
            .Column<string>("AddressLine2", c => c.WithLength(100))
            .Column<string>("Zipcode", c => c.WithLength(20))
            .Column<string>("City", c => c.WithLength(50))
            .Column<int>("Country_Id")
         );

        // Step 3: Create the Contact table
        SchemaBuilder.CreateTable("ContactPartRecord", table => table
            .ContentPartRecord()
            .Column<int>("PersonId")
            .Column<string>("Type", c => c.WithLength(50))
            .Column<string>("TelephoneNo", c => c.WithLength(50))
            .Column<string>("MobileNo", c => c.WithLength(50))
            .Column<string>("EmailAddress", c => c.WithLength(255))
            .Column<string>("SkypeName", c => c.WithLength(50))
         );

        // Step 4: Prevent PersonPart from being attached to other content types via UI
        ContentDefinitionManager.AlterPartDefinition("PersonPart", part => part
                .Attachable(false)
        );


        // Step 5: Create Person content type for use in our module
        ContentDefinitionManager.AlterTypeDefinition("Person", type => type
            .WithPart("PersonPart")
            .WithPart("UserPart")
        );

        // Step 6: Prevent AddressPart from being attached to other content types via UI
        ContentDefinitionManager.AlterPartDefinition("AddressPart", part => part
                .Attachable(false)
        );

        // Step 7: Create Address content type for use in our module
        ContentDefinitionManager.AlterTypeDefinition("Address", type => type
            .WithPart("CommonPart")
            .WithPart("AddressPart")
        );

        // Step 8: Prevent ContactPart from being attached to other content types via UI
        ContentDefinitionManager.AlterPartDefinition("ContactPart", part => part
            .Attachable(false)
        );

        // Step 9: Create Address content type for use in our module
        ContentDefinitionManager.AlterTypeDefinition("Contact", type => type
            .WithPart("CommonPart")
            .WithPart("ContactPart")
        );

        // Step 10: Create foriegn key from address back to person
        SchemaBuilder.CreateForeignKey("Address_Person",
                                        "AddressPartRecord", new[] { "PersonId" },
                                        "PersonPartRecord", new[] { "Id" });


        // Step 11: Create foriegn key from address back to person
        SchemaBuilder.CreateForeignKey("Contact_Person",
                                        "ContactPartRecord", new[] { "PersonId" },
                                        "PersonPartRecord", new[] { "Id" });

        return 2;
    }

d) handler.....

public class PersonPartHandler : ContentHandler
{
    //private readonly IContentManager _contentManager;
    public PersonPartHandler(IRepository<PersonPartRecord> repository){
        Filters.Add(StorageFilter.For(repository));
        Filters.Add(new ActivatingFilter<UserPart>("Person"));
        OnActivated<PersonPart>(LazyLoadHandlers);
    }

    private void LazyLoadHandlers(ActivatedContentContext context, PersonPart part) {
        // Add Lazy Loaders here should we have any
    }
}
e) Driver.......
public class PersonPartDriver : ContentPartDriver<PersonPart>
{
    protected override string Prefix
    {
        get { return "Person"; }
    }

    protected override DriverResult Editor(PersonPart part, dynamic shapeHelper)
    {
        return ContentShape("Parts_Person_Edit", () => 
                            shapeHelper.EditorTemplate(TemplateName: "Parts/Person", 
                            Model: part, Prefix: Prefix));
    }

    protected override DriverResult Editor(PersonPart part, IUpdateModel updater, dynamic shapeHelper)
    {
        updater.TryUpdateModel(part, Prefix, null, null);

        var user = part.User;
        updater.TryUpdateModel(user, Prefix, new[] { "Email" }, null);

        return Editor(part, shapeHelper);
    }
} 

thanks.....
Dec 28, 2013 at 8:56 PM
The methods in migration class should go like this:

+ Create() { .... return 1;}
+ UpdateFrom1() {.... return 2;}
+ UpdateFrom2() {.... return 3;}

Do you have a Create method?

"Type" might be a reserved keyword.

"PersonId" column name might be named Person_Id if you keep a PersonPartRecord in a property named Person. I can't say for sure since you didn't post ContactPartRecord and AddressPartRecord classes' code.



Dec 28, 2013 at 10:05 PM
In Response...

1) Yes I have a Create...... it is used for a config section and is working fine.

2) Spike uses Type in his WebShop series for Address, so it should be OK

3) Spike also uses CustomerId in the same way for his Address table / content part

For clarity... my AddressPartRecord is : -
public class AddressPartRecord : ContentPartRecord
{
    public virtual int PersonId { get; set; }
    public virtual string Type { get; set; }
    public virtual string AddressLine1 { get; set; }
    public virtual string AddressLine2 { get; set; }
    public virtual string City { get; set; }
    public virtual string AreaCode { get; set; }
    public virtual ListRecord Country { get; set; }
}

public class AddressPart : ContentPart<AddressPartRecord>
{
    public int PersonId
    {
        get { return Record.PersonId; }
        set { Record.PersonId = value; }
    }

    public string Type
    {
        get { return Record.Type; }
        set { Record.Type = value; }
    }

    public string AddressLine1
    {
        get { return Record.AddressLine1; }
        set { Record.AddressLine1 = value; }
    }

    public string AddressLine2
    {
        get { return Record.AddressLine2; }
        set { Record.AddressLine2 = value; }
    }

    public string City
    {
        get { return Record.City; }
        set { Record.City = value; }
    }

    public string AreaCode
    {
        get { return Record.AreaCode; }
        set { Record.AreaCode = value; }
    }

    public ListRecord Country
    {
        get { return Record.Country; }
        set { Record.Country = value; }
    }

    public string Title
    {
        get
        {
            return string.Join(", ", new[]{AddressLine1, AddressLine2, AreaCode, City, 
                Country != null ? Country.Description : ""}
                .Where(x => !string.IsNullOrWhiteSpace(x)));
        }
    }
}

Thanks ....
Dec 28, 2013 at 10:42 PM
I've also removed all the address and contact details code from the solution, leaving only the person code and I still get the same error.
Dec 28, 2013 at 10:51 PM
You can try debugging your migration code on a clean setup. Attach a debugger to iis express (or dev server, whatever you are using) just before enabling your module, it will be faster.


Dec 28, 2013 at 11:03 PM
I did try that, and all tables were created as expected, and the Create() in data migrations also set up a cross reference table and populated it with no problems.

I'll try debugging a clean installation, and will go from there

thanks
Dec 29, 2013 at 11:11 PM
Thanks KassoBasi; rookie error I'm afraid.....I was missing the model declaration in the view
Feb 5, 2014 at 4:33 PM
@marshallt: What was the fix in your case? I didn't get that part.

I've also just encountered the exception you mentioned in your original post:
null id in Orchard.ContentManagement.Records.ContentTypeRecord entry (don't flush the Session after an exception occurs) .
Luckily, I've found the cause of the error (in the logs): a mismatch of Record property name and the appropriate column name string in the Migrations.cs.

So for anyone reading this far, triple-check whether your strings match your properties.