Autoincrement identity field (data migration)

Topics: Writing modules
Jun 1, 2011 at 2:49 PM

I'm attemping to write a simple module that stores two strings in the database. My model has three properties, Id, String1, String2.  when I run the following data migration:

Subsequent Create() actions fail because "Id is required," mainly I think because the primary key column Id isn't set to 
autoincrement.

When I uncomment the Id column above and run the migration, the migration fails because Id is specified twice. I've tried several
combinations
of

 

public int Create(){
            SchemaBuilder.CreateTable("RegistrationRecord", table => table
                .ContentPartRecord()
                //.Column<int>("Id", column => column.PrimaryKey().Identity())
                .Column<string>("String1")
                .Column<string>("String2")
            );

Subsequent Create() actions fail because "Id is required," mainly I think because the primary key column Id isn't set to autoincrement.

When I uncomment the Id column above and run the migration, the migration fails because Id is specified twice. I've tried several combinations of including one or the other with no luck.  I've even compared mine to an existing module (the roles module that comes with Orchard).  Can someone tell me what I might be missing? How do I make a primary key column an identity column?

 

Subsequent Create() actions fail because "Id is required," mainly I think because the primary key column Id isn't set to 
autoincrement.

When I uncomment the Id column above and run the migration, the migration fails because Id is specified twice. I've tried several
combinations
of
Jun 1, 2011 at 4:19 PM

Your ContentPartRecord should be called "RegistrationPartRecord" and your ContentPart should be called "RegistrationPart". You shouldn't specify the Id column for a ContentPartRecord. It should just work if you follow that convention.

Jun 1, 2011 at 4:53 PM

Randompete, thanks for your response. You've answered a few of my questions now, and I appreciate it.

My ContentPartRecord and ContentPart are named as you suggested...it should be said that I'm not actually creating a content part, I'm just creating a simple database record that should be persisted...

Here's what I have with a little more code to clarify what I'm doing:

Migrations.cs

public int Create(){
            SchemaBuilder.CreateTable("RegistrationRecord", table => table
                .ContentPartRecord()
                .Column<string>("String1")
                .Column<string>("String2")
            );
            
            return 1;
        }

RegistrationRecord.cs (model)

public class RegistrationRecord : ContentPartRecord
    {
        public virtual string String1{ get; set; }
        public virtual string String2{ get; set; }
    }

    public class RegistrationPart : ContentPart<RegistrationRecord>
    {
        [Required]
        public string String1        {
            get { return Record.String1; }
            set { Record.String1= value; }
        }

        [Required]
        public string String2        {
            get { return Record.String2; }
            set { Record.String2 = value; }
        }
    }

When the migration is run, it creates a table with 3 columns, Id, String1, and String2.  The problem occurs when I click the "create" button on my view (which basically just posts to another controller action decorated with HttpPost.  In that action, every time, there's a modelstate error stating "The Id field is required."  Shouldn't this get filled in automatically, either via Orchard, or by having the column be an identity field?

Jun 1, 2011 at 5:11 PM

No, you've named your record "RegistrationRecord" not "RegistrationPartRecord" - the difference is important :)

Jun 1, 2011 at 5:19 PM
Edited Jun 1, 2011 at 5:46 PM

Ahh yes, you're correct, I remember removing that "Part" a long time ago...

Unfortunately, I just renamed it back and I'm still getting the "The Id field is required" modelstate error...

Also, I looked at Garside's contact us module, and their ContactUsSettingsRecord doesn't have "part" in the name. Even this page doesn't have part in it:

http://www.orchardproject.net/docs/%28S%282w1znkz4xyn3nd55rxwzrq45%29%29/Understanding-data-access.ashx?Code=1

namespace Map.Models {
    public class MapRecord : ContentPartRecord {
        public virtual double Latitude { get; set; }
        public virtual double Longitude { get; set; }
    }
}

What is the "part" convention supposed to do?

Jun 1, 2011 at 5:43 PM

Could this have something to do with the way I'm saving the record? I'm just injecting an IRespository<RegistrationPartRecord> into the constructor and then calling:

_respository.Create(registration);

I know this should probably go through a service or some other abstraction, but I wanted to get it to work in raw form first...
Coordinator
Jun 1, 2011 at 5:46 PM

If you need this to be a part, then it should be created as *part* of a content-item, i.e. through ContentManager. If you don't, then accessing the repository directly is fine but it will need an Id column.

Jun 1, 2011 at 5:56 PM

Bertrand,

So, since I don't need this to be a part, here's what I think I'll need to do based on what you said:

1.) Get rid of the RegistrationPart wrapper

2.) Stop inheriting ContentPartRecord

3.) Add the id field to my record

4.) Remove "part" from the object name

So I'll have one class in my model:

 

 public class RegistrationRecord
    {
        public virtual string Id { get; set; }
        public virtual string String1{ get; set; }
        public virtual string String2{ get; set; }
    }

 

And my migration class will be:

 
public int Create(){ 
SchemaBuilder.CreateTable("RegistrationRecord", table => table
.Column<int>("Id", column => column.PrimaryKey().Identity()) 
.Column<string>("String1") .Column<string>("String2") ); }

I'll try this and see if it solves the problem.
 
Coordinator
Jun 1, 2011 at 6:09 PM

Yes, that should work.

Jun 1, 2011 at 7:05 PM

Now we're getting somewhere...

I'm getting:

"ids for this class must be manually assigned before calling save(): xxxx.xxxx.Models.RegistrationRecord"

Is there no way to make this field an autoincrement field?

Jun 1, 2011 at 7:10 PM

I've done this plenty of times myself so it definitely works.

I notice in the code above you have a string for the Id field on RegistrationRecord. Could that be it?

Jun 1, 2011 at 7:27 PM

Yeah, I trust that it does work. It's probably some minor thing I'm doing wrong...the string was a typo on my part...it is in fact set as an int, but it's still saying the Id field is required...

Coordinator
Jun 1, 2011 at 7:30 PM

Are you starting from scratch or could this be a remnant of previous attempts?

Jun 1, 2011 at 7:31 PM

It's getting to that point where I might need to start from scratch...I'll try that and post results...

Jun 1, 2011 at 8:38 PM

I rebuilt the module from scratch, still telling me the Id field is required when trying to save.  Not sure where else to go from here, but I'll keep digging.  I know this has to be one simple thing I'm missing...

Coordinator
Jun 1, 2011 at 8:54 PM

When you look at the table definition, what do you see for the Id column?

Jun 1, 2011 at 8:56 PM

After doing some reading, it seems that I might want a "part" after all.  I'm basically creating a registration module that allows an admin to create a new registration page for a given role and have the user automatically be assigned to that role when they successfully register.  Original I was just storing a rolename and a friendly name for the url (so if I wanted to register a monster, I'd go to mysite.org/monsters/register.

Instead of the two strings, perhaps I should be tying into the existing roles so I'd have an int, a roleid, and a friendly name.  I think I need a part to accomplish this...that being said, I'd like to get it to work my denormalized way first just as a proof that it works in case I ever need to do something like this again.

Jun 1, 2011 at 8:58 PM

I see:

 

Id, int, don't allow nulls, Identity spec. = yes...it's creating it correctly in the database, so it seems.  Furthermore, I know it's getting validated using the data annotation functionality because I'm able to change the error message that comes back using DisplayName.

Does the property need to be a nullable int and not a non-nullable?

Coordinator
Jun 1, 2011 at 9:06 PM

No, what you're doing seems to be perfectly identical to what's being done in countless other places throughout the Orchard code.

Jun 1, 2011 at 9:10 PM

I got it to work using methods from this post:

http://stackoverflow.com/questions/2142990/asp-mvc-the-id-field-is-required-validation-message-on-create-id-not-set-to

 

So, if I include the following in my admin create, it works:

public ActionResult Create([Bind(Exclude = "Id")] RegistrationRecord registration)

 


 

Coordinator
Jun 1, 2011 at 9:19 PM

Ah, I see, so the problem was actually in the controller?

Jun 1, 2011 at 9:23 PM

Yeah, after you guys said several times that everything in my migration/models was correct, it felt like the problem had to be somewhere else.  For some reason I needed to explicitly exclude the Id field from updating when the model was bound to the form values being passed in. I'm able to successfully save records now.

Thanks for all your help, gentlemen.  I'm sure I'll be back with a million more questions as I learn this stuff.