DateTime Field Breaking PartRecord

Topics: Writing modules
Oct 17, 2013 at 11:15 PM
I am creating a module which has a "race" part. It works fine until I add a DateTime field/column to the record part. Actually it works fine having the migration with the DateTime column. It only breaks when I add the DateTime property to the PartRecord. See code below.

Migration.cs
SchemaBuilder.CreateTable( "RacePartRecord", table =>
  table.ContentPartRecord()
         .Column<int>( "Division_Id" )
         .Column<int>( "Event_Id" )
         .Column<DateTime>( "RaceTime" )
);
RacePartRecord.cs
public class RacePartRecord : ContentPartRecord
{
    public virtual string DerbyRaceId { get; set; }
    public virtual ContentItemRecord Division { get; set; }
    public virtual ContentItemRecord Event { get; set; }
    // Adding the line below errors on save/publish
    public virtual DateTime RaceTime { get; set; }
}
RacePart.cs
public class RacePart : ContentPart<RacePartRecord>
{
    private readonly LazyField<IContent> _division = new LazyField<IContent>();
    private readonly LazyField<IContent> _event = new LazyField<IContent>();

    public LazyField<IContent> DivisionField
    {
        get { return _division; }
    }

    public LazyField<IContent> EventField
    {
        get { return _event; }
    }

    [Required]
    public DateTime RaceTime
    {
        get { return Record.RaceTime; }
        set { Record.RaceTime = value; }
    }

    public IContent Division
    {
        get { return _division.Value; }
        set { _division.Value = value; }
    }

    public IContent Event
    {
        get { return _event.Value; }
        set { _event.Value = value; }
    }
}
RaceHandler.cs
public class RaceHandler : ContentHandler
{
    private readonly IContentManager _contentManager;

    public RaceHandler( IRepository<RacePartRecord> repository, IContentManager contentManager )
    {

        Filters.Add(StorageFilter.For(repository));
        _contentManager = contentManager;

        OnInitializing<RacePart>( PropertySetHandlers );
        OnLoaded<RacePart>( LazyLoadHandlers );
    }

    private void LazyLoadHandlers( LoadContentContext context, RacePart part )
    {
        // add handlers that will load content just-in-time
        part.DivisionField.Loader( () =>
            part.Record.Division == null ? null : _contentManager.Get( part.Record.Division.Id ) );

        part.EventField.Loader( () =>
            part.Record.Event == null ? null : _contentManager.Get( part.Record.Event.Id ) );
    }

    private static void PropertySetHandlers(
        InitializingContentContext context, RacePart part )
    {
        // add handlers that will update records when part properties are set
        part.DivisionField.Setter( division =>
        {
            part.Record.Division = division == null ? null : division.ContentItem.Record;
            return division;
        } );

        part.EventField.Setter( e =>
        {
            part.Record.Event = e == null ? null : e.ContentItem.Record;
            return e;
        } );

        // Force call to setter if we had already set a value
        if( part.DivisionField.Value != null )
        { part.DivisionField.Value = part.DivisionField.Value; }

        if( part.EventField.Value != null )
        { part.EventField.Value = part.EventField.Value; }
    }
}
RacePartDriver.cs
public class RacePartDriver : ContentPartDriver<RacePart>
{
    private readonly IDivisionService _divisionService;
    private readonly IEventService _eventService;
    private readonly IRaceService _raceService;

    public RacePartDriver( IDivisionService divisionService, IRaceService raceService, IEventService eventService )
    {
        _divisionService = divisionService;
        _raceService = raceService;
        _eventService = eventService;
    }

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

    protected override DriverResult Display( RacePart part, string displayType, dynamic shapeHelper )
    {
        return ContentShape( "Parts_Race", () =>
                                            shapeHelper.Parts_Race( RacePart: part ) );
    }

    // GET
    protected override DriverResult Editor( RacePart part, dynamic shapeHelper )
    {
        return ContentShape( "Parts_Race_Edit", () =>
                                                    shapeHelper.EditorTemplate(
                                                        TemplateName: "Parts/Race",
                                                        Model: BuildEditorViewModel( part ),
                                                        Prefix: Prefix ) );
    }

    // POST
    protected override DriverResult Editor( RacePart part, IUpdateModel updater, dynamic shapeHelper )
    {
        var viewModel = new RaceViewModel();

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

        if( part.ContentItem.Id != 0 )
        { _raceService.UpdateRace( viewModel, part ); }

        return Editor( part, shapeHelper );
    }

    private RaceViewModel BuildEditorViewModel( RacePart part )
    {
        var item = new RaceViewModel
                    {
                        Title = part.ContentItem.As<TitlePart>().Title,
                        RaceId = part.ContentItem.Id,
                        RaceTime = part.RaceTime,
                        Divisions = _divisionService.GetDivisions(),
                        Events = _eventService.GetUpcomingEvents(),
                    };
        if( part.Division != null )
        {
            item.DivisionId = part.Division.Id;
        }
        if( part.Event != null )
        {
            item.EventId = part.Event.Id;
        }
        return item;
    }
}
RaceService.cs
public class RaceService : IRaceService
{
    private readonly IContentManager _contentManager;

    public RaceService( IContentManager contentManager )
    {
        _contentManager = contentManager;
    }

    public void UpdateRace( RaceViewModel viewModel, RacePart part )
    {
        part.Division = _contentManager.Get<DivisionPart>( viewModel.DivisionId );
        part.Event = _contentManager.Get<EventPart>( viewModel.EventId );
    }
}
RaceViewModel.cs
public class RaceViewModel
{
public virtual int RaceId { get; set; }
    
public virtual string Title { get; set; }

[Required( ErrorMessage = "Estimated Race Start Time is required." )]
[DisplayName( "Estimated Race Start Time" )]
public virtual DateTime RaceTime { get; set; }

[Required( ErrorMessage = "Division is required." )]
[DisplayName( "Division" )]
public virtual int DivisionId { get; set; }

[Required( ErrorMessage = "Event is required." )]
[DisplayName( "Event" )]
public virtual int EventId { get; set; }

public IEnumerable<DivisionViewModel> Divisions { get; set; }
public IEnumerable<EventViewModel> Events { get; set; }
}
If I save or publish with the "RaceTime" property on the RacePartRecord, I get a .NET error stating, "null id in Orchard.ContentManagement.Records.ContentTypeRecord entry (don't flush the Session after an exception occurs)".

If I debug, it breaks on Orchard.Data.Repository.Create with this error, "SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM." It doesn't matter what date I put in there.

Any help would be greatly appreciated.
Oct 18, 2013 at 9:19 AM
Edited Oct 18, 2013 at 9:20 AM
May be try with a datetime? better supported by the NH flow .
Nov 6, 2013 at 4:33 AM
I'm seeing pretty much the same error message. But after removing the DateTime column in my table, commenting out the properties in the respective classes, and rerunning the Migrations Update, I'm still seeing the error.

The error messages certainly could be more informative.

Did you ever get this resolved to your satisfaction?
Nov 7, 2013 at 12:08 AM
Edited Nov 7, 2013 at 12:09 AM
Yes Gregory, I was able to fix the problem. Thanks for reminding me to post a solution. I did end up using a nullable DateTime property but that wasn't the solution.

Anyway, the problem from what I can tell is that Orchard or NHibernate (I don't know which) initially sets the DateTime property to something like "01/01/0001". Obviously, this is an invalid date and it will throw an exception down the stack. To solve this problem, I set the DateTime property to null (though if you don't want to use the nullable DateTime datatype, you can set the DateTime property to a valid date).

So inside my controller for the Create method, I now explicitly set the DateTime property after getting the new ContentPart. This insures that the DateTime isn't initialized to some bogus date.
var Race = Services.ContentManager.New<RacePart>("Race");
Race.RaceTime = null;
This alleviated the problem for me. Like I said, you could also do the following with out using the nullable DateTime.
var Race = Services.ContentManager.New<RacePart>("Race");
Race.RaceTime = new DateTime( 1900, 1, 1 );
Hope this helps you and anyone else with a similar problem.
Nov 11, 2013 at 8:38 PM
@elmojo - Well, I still can't get it to work. Here's my table creation code, in Migrations:
            SchemaBuilder.CreateTable("CourseLeaderPartRecord",
                t => t
                    .ContentPartRecord()
                    .Column<string>("FirstName", c => c.WithLength(50))
                    .Column<string>("LastName", c => c.WithLength(50))
                    .Column<string>("Title", c => c.WithLength(10))
                    .Column<string>("Biography", c => c.Unlimited().NotNull())
                    .Column<bool>("IsActive", c => c.NotNull())
                    .Column<int>("AdminId", c => c.NotNull())
                    .Column<DateTime>("CreatedAt", c => c.Nullable())
            );

            ContentDefinitionManager.AlterPartDefinition("CourseLeaderPart",
                part => part
                    .Attachable()
            );

            ContentDefinitionManager.AlterTypeDefinition("CourseLeader",
                type => type
                    .WithPart("CourseLeaderPart")
                    .WithPart("UserPart")
                //.WithPart("AddressPart")
            );
I have created it with and without the "CreatedAt" column (and both as Nullable and NotNull); I have created it both with and without "UserPart". I have created it as a truly bare-bones table. With the corresponding Part and PartRecord classes.

Here is the LeaderService code that eventually crashes when I add a new Leader:
            var leader = _orchardServices.ContentManager.New("CourseLeader");

            var userPart = leader.As<UserPart>();

            // Cast the customer to a CustomerPart
            var leaderPart = leader.As<CourseLeaderPart>();

            leaderPart.Title = leaderVM.Title;
            leaderPart.FirstName = leaderVM.FirstName;
            leaderPart.LastName = leaderVM.LastName;
            leaderPart.Biography = leaderVM.Biography;

            leaderPart.AdminId = adminId;
            leaderPart.IsActive = leaderVM.IsActive;

            // Use IClock to get the current date instead of using DateTime.Now (see http://skywalkersoftwaredevelopment.net/orchard-development/api/iclock)
            leaderPart.CreatedAt = DateTime.Now;// _clock.UtcNow;

            // Set some properties of the customer content item (via UserPart and CustomerPart)
            userPart.UserName = leaderVM.EmailAddress;
            userPart.Email = leaderVM.EmailAddress;
            userPart.NormalizedUserName = leaderVM.EmailAddress.ToLowerInvariant();
            userPart.Record.HashAlgorithm = "SHA1";
            userPart.Record.RegistrationStatus = UserStatus.Approved;
            userPart.Record.EmailStatus = UserStatus.Approved;

            //// Use Ochard's MembershipService to set the password of our new user
            _membershipService.SetPassword(userPart, leaderVM.Password);

            // Store the new user into the database
            _orchardServices.ContentManager.Create(leader);

            return leaderPart;
Again, I've done this barebones, without a "CreatedAt" field, and without a UserPart. It still crashes on _orchardServices.ContentManager.Create(leader). When I look at at the code in the debugger, "CreatedAt" shows up as a valid date-time value (e.g., "11/11/2013 03:36:25pm").

I have other code that does almost exactly the same thing, and it works flawlessly. I really can't figure out what the hell the problem is.

Truly frustrating. If anything jumps out at you, please let me know. I'm about ready to claw my eyes out as it is! :-/

Thanks for getting back to me. Much appreciated.
Coordinator
Nov 12, 2013 at 12:51 AM
Why do you need your own CreatedAt, when there is already one on the Common part?
Nov 12, 2013 at 1:59 PM
BertrandLeRoy wrote:
Why do you need your own CreatedAt, when there is already one on the Common part?
First, because I read in the Skywalker tutorial that using CommonPart and UserPart created an infinite loop bug. Second, that doesn't seem to be relevant to the current issue at all. The behavior that I'm seeing is occurring regardless of whether or not I have a CreatedAt column in the table. Using the code above (minus the CreatedAt property/column) still crashes the app with the date time out-of-range exception.

And the debugger does not say which property is having the issue, which is part of why this is so freaking frustrating. The best I can get is that it is related to "CourseLeader". But that's as far as it gets.
Nov 12, 2013 at 6:48 PM
Well, I figured it out. But for the life of me, I don't understand what's happening.

I have a CustomerPart/CustomerPartRecord table and class structure (based on the Skywalker tutorials). And somehow, someway, CourseLeaderPart/Record seems to have inherited this structure. It's expecting a "CreatedUtc" property to be set before the new record is saved.

At one point, I actually explicitly wanted this relationship, because I wanted the CourseLeader class to pick up some of the Customer properties. But I was running into some issues, so I killed that relationship. I obviously have something still attached somewhere, but I flat-out can't find it.

Oh, well. I'll use the work-around for now, and I'll see if I can track this down later. Sure was a pain in the ass to solve.

Thanks for the feedback, guys. I had to check some assumptions, which got me to dig more deeply into the debugger. That's what led me to the answer.

-greg
Coordinator
Nov 12, 2013 at 6:55 PM
Am I allowed to ask questions and/or try to help? ;) If you're getting a data out of range exception, it looks like the only DateTime column that you have would warrant closer inspection. You may be missing something like thinking you removed the column but didn't, because of the way migrations work... That seems to be confirmed by your latest message. I would look at the database table definition as it is in the database and compare with your migration. You read that using CommonPart and UserPart caused an infinite loop? Where? Did somebody file a bug for that?
Nov 13, 2013 at 3:43 AM
I remember reading that as well. Here:
http://skywalkersoftwaredevelopment.net/blog/writing-an-orchard-webshop-module-from-scratch-part-8

Aside: Whenever you create a new content type that has a UserPart, don't attach the CommonPart as well. Doing so will cause a StackOverflowException when you sign in with that new user type. This happens because whenever Orchard news up a content item, it invokes all content handlers, including the CommonPartHandler. The CommonPartHandler will try to assign the currently loggedin user, but in doing so it will have to load that user. Loading that user will again invoke the CommonPartHandler, which in turn will invoke the AuthenticationService to get the current user, and so on.


Coordinator
Nov 13, 2013 at 8:54 AM
Somebody ought to open a bug for that, if it hasn't been fixed this this article was written.
Nov 15, 2013 at 3:38 PM
BertrandLeRoy wrote:
Somebody ought to open a bug for that, if it hasn't been fixed this this article was written.
I think this bug has been fixed. I've attached both UserPart and CommonPart to a new content type and have not encountered this issue.

I'd consider the issue closed, unless someone else runs into it.
Nov 15, 2013 at 3:48 PM
Edited Nov 15, 2013 at 3:54 PM
BertrandLeRoy wrote:
Am I allowed to ask questions and/or try to help? ;) If you're getting a data out of range exception, it looks like the only DateTime column that you have would warrant closer inspection. You may be missing something like thinking you removed the column but didn't, because of the way migrations work... That seems to be confirmed by your latest message. I would look at the database table definition as it is in the database and compare with your migration. You read that using CommonPart and UserPart caused an infinite loop? Where? Did somebody file a bug for that?
As I reported just above your offer to help, I seem to have resolved the issue, but the behavior I'm encountering is more than a little odd. As I say:
I have a CustomerPart/CustomerPartRecord table and class structure (based on the Skywalker tutorials). And somehow, someway, CourseLeaderPart/Record seems to have inherited this structure. It's expecting a "CreatedUtc" property to be set before the new record is saved.

At one point, I actually explicitly wanted this relationship, because I wanted the CourseLeader class to pick up some of the Customer properties. But I was running into some issues, so I killed that relationship. I obviously have something still attached somewhere, but I flat-out can't find it.
[Edit: D'oh!!! Just figured the above out. Since I'm now attaching CommonPart, that CreatedUtc field needs to be set.
* sigh *
Slowly but surely, I'm developing a deeper understanding of how things work. But there sure is a ton to grok....]


I'm seeing even more unusual behavior. A field I have added to CourseLeaderPart is Biography (not null). Now, when I edit a user that is a course leader in the default User module (through the admin editor), I encounter an exception when I try to save the data. It tells me that I can't write a null value to the Biography field/column in the database.

Why in the world would that be happening? Why is the UserPart being modified inside of the default editor now somehow dependent on the CourseLeaderPart that I created? This is beyond bizarre behavior to me.

Am I somehow creating two-way dependencies (foreign key relationships) when I create the new tables in Migrations? Or is there something I'm doing with the Drivers/Handlers code that creates this dependency?

I'm really baffled by this. If you have any insights, I'd love to hear them. I can work around this, kinda, for now. But I'm worried about what happens when I push this live and the people who will be administering the site start using it. I really don't want to run into any corruption issues, and the admin needs to be as brain-dead simple as possible.

Thanks for jumping into the thread. Much appreciated.
Coordinator
Nov 16, 2013 at 6:10 AM
I think it's because the driver is going to be called into no matter what, but there is no data, so your not null property will fail. You need to write your driver so that it knows what to do even when there is no data to persist.

Yes, when you add a part to a type, you are adding a 1:1 relationship between all items of that type and the new part. It doesn't mean existing items should fail (if the driver is properly written). The new part should be added when the item is saved (provided the driver does set the data properly).