Decimal with RangeAttribute causes Overflow

Topics: Customizing Orchard, Troubleshooting
Nov 8, 2013 at 1:59 PM
Edited Nov 8, 2013 at 2:06 PM
We implemented a custom ContentPart which has a property
[Range(typeof(decimal),"-90", "90", ErrorMessage = "Breitengrad muss zwischen -90 und 90 sein.")]
public decimal? Latitude {
    get { return Record.Latitude; }
    set { Record.Latitude = value; }
}
We created a column with only the needed number of digits:
table.AddColumn<Decimal>("Latitude", col => col.WithPrecision(8).WithScale(6));
We build a driver by the book, added a custom Controller and View for editing puposes. When editing or creating the ContentPart, and typing in "91" as latitude, it works as expected - showing the error message. But when typing in "100" or higher - i.e. 3 or more digits - we get the following exception.

We would have expected that the RangeAttribute would be applied before updating the record in the database, and thus causing the error. Yet it seems that is not the case. When we check the ModelState for validity, it seems already to late. The expception is caused by the _contentManager.UpdateEditor() call.

What can we do to avoid this error? Is there something we can change in the action? We implemented the action just like Orchard.Core.Contents.Controllers.AdminController does. Or is that even a bug in Orchard?
Exception Details: System.Data.SqlServerCe.SqlCeException: Expression evaluation caused an overflow. [ Name of function (if known) =  ]

Source Error:


Line 160:            }
Line 161:
Line 162:            return criteria
Line 163:                .List<ContentItemVersionRecord>()
Line 164:                .Select(x => ContentManager.Get(x.ContentItemRecord.Id, _versionOptions != null && _versionOptions.IsDraftRequired ? _versionOptions : VersionOptions.VersionRecord(x.Id)))


Source File: c:\Projects\discoverize\src\Orchard\ContentManagement\DefaultContentQuery.cs    Line: 162

Stack Trace:


[SqlCeException (0x80004005): Expression evaluation caused an overflow. [ Name of function (if known) =  ]]
   System.Data.SqlServerCe.SqlCeCommand.ProcessResults(Int32 hr) +328
   System.Data.SqlServerCe.SqlCeCommand.ExecuteCommandText(IntPtr& pCursor, Boolean& isBaseTableCursor) +723
   System.Data.SqlServerCe.SqlCeCommand.ExecuteCommand(CommandBehavior behavior, String method, ResultSetOptions options) +490
   System.Data.SqlServerCe.SqlCeCommand.ExecuteNonQuery() +44
   NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd) +616
   NHibernate.AdoNet.NonBatchingBatcher.AddToBatch(IExpectation expectation) +89
   NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session) +2210

[GenericADOException: could not update: [Discoverize.Entry.Models.EntryPartRecord#193][SQL: UPDATE Discoverize_Entry_EntryPartRecord SET Country = ?, Description = ?, Directions = ?, Email = ?, Fax = ?, FederalState = ?, Latitude = ?, Longitude = ?, Name = ?, Phone = ?, PostalCode = ?, Region = ?, Street = ?, Street2 = ?, Town = ?, Website = ?, Bool = ?, MinZahl = ?, Nonsearchable = ?, Testeigenschaft = ?, Testet = ?, Textttt = ?, Texty = ? WHERE Id = ?]]
   NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session) +2836
   NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session) +349
   NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Int32[] dirtyFields, Boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object obj, Object rowId, ISessionImplementor session) +1001
   NHibernate.Action.EntityUpdateAction.Execute() +607
   NHibernate.Engine.ActionQueue.Execute(IExecutable executable) +46
   NHibernate.Engine.ActionQueue.ExecuteActions(IList list) +92
   NHibernate.Engine.ActionQueue.ExecuteActions() +32
   NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session) +485
   NHibernate.Event.Default.DefaultAutoFlushEventListener.OnAutoFlush(AutoFlushEvent event) +268
   NHibernate.Impl.SessionImpl.AutoFlushIfRequired(ISet`1 querySpaces) +475
   NHibernate.Impl.SessionImpl.List(CriteriaImpl criteria, IList results) +783
   NHibernate.Impl.CriteriaImpl.List(IList results) +63
   NHibernate.Impl.CriteriaImpl.List() +79
   Orchard.ContentManagement.DefaultContentQuery.Slice(Int32 skip, Int32 count) in c:\Projects\discoverize\src\Orchard\ContentManagement\DefaultContentQuery.cs:162
   Orchard.ContentManagement.ContentQuery`1.Orchard.ContentManagement.IContentQuery<T>.List() in c:\Projects\discoverize\src\Orchard\ContentManagement\DefaultContentQuery.cs:212
   Orchard.Widgets.Services.WidgetsService.GetWidgets(Int32[] layerIds) +1008
   Orchard.Widgets.Filters.WidgetFilter.OnResultExecuting(ResultExecutingContext filterContext) +4540
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +70
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +977620
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +977620
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +977620
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +977620
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultFilter(IResultFilter filter, ResultExecutingContext preContext, Func`1 continuation) +977620
   System.Web.Mvc.ControllerActionInvoker.InvokeActionResultWithFilters(ControllerContext controllerContext, IList`1 filters, ActionResult actionResult) +265
   System.Web.Mvc.ControllerActionInvoker.InvokeAction(ControllerContext controllerContext, String actionName) +978672
   System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__19() +40
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidDelegate>b__0() +20
   System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult) +67
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +20
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +53
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__3(IAsyncResult asyncResult) +42
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>b__3(IAsyncResult ar) +20
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult) +53
   Orchard.Mvc.Routes.HttpAsyncHandler.EndProcessRequest(IAsyncResult result) in c:\Projects\discoverize\src\Orchard\Mvc\Routes\ShellRoute.cs:159
   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.1016 
Here is the code in our EditPost action of our Controller:
[HttpPost, ActionName("Edit")]
public ActionResult EditPost(int id) {
    var entry = _contentManager.Get(id, VersionOptions.DraftRequired);
    if (entry == null)
        return HttpNotFound();

    var model = _contentManager.UpdateEditor(entry, this);
    if (!ModelState.IsValid) {
        _transactionManager.Cancel();
        return EditInternal(entry, model);
    }

    _contentManager.Publish(entry);
    TempData["Success"] = "true";
    return RedirectToAction("Edit", new { id });
}
The Editor action in the Driver:
protected override DriverResult Editor(EntryPart part, IUpdateModel updater, dynamic shapeHelper) {
    updater.TryUpdateModel(part, Prefix, null, null);
    return Editor(part, shapeHelper);
}
And the TryUpdateModel method in the Controller which implements IUpdateModel:
bool IUpdateModel.TryUpdateModel<TModel>(TModel model, string prefix, string[] includeProperties, string[] excludeProperties) {
    return TryUpdateModel(model, prefix, includeProperties, excludeProperties);
}
Nov 15, 2013 at 10:06 AM
Hm, seems like no one has an answer to this one. Maybe my post was a little confusing? If there is something strange, I could elaborate...

So far I see only one possibilty to circumvent this error: Change the precision of the decimal to higher number. That way people would have to enter a big number for the SqlCE Exception to be thrown. In all other cases the RangeAttribute would kick in.

I know, it is not the best bugfix, it just makes the error more uncommon. If someone knows how to fix this properly, I would be glad to read possible solutions.
Nov 26, 2013 at 10:43 AM
We have the same problem when it comes to the strings. For instance we have
[StringLength(50)]
public string Street {
    get { return Record.Street; }
    set { Record.Street = value; }
}
and a Migration:
table.AddColumn<String>("Street", col => col.WithLength(50));
In the same EditPost action of the controller (see above) we get a truncation error from the database when we enter more than 50 characters into the Street text field:
System.Data.SqlServerCe.SqlCeException: The data was truncated while converting from one data type to another. [ Name of function(if known) =  ]
For now we add a "maxlength" attribute to the text fields, so the user can only enter upto the maximum number of characters.

We probably do not understand Orchard well enough. Yet, it seems strange that AdminControllers use _contentManager.UpdateEditor(ContentItem, this) to update a ContentItem in the database and then check for errors in the FormValueCollection. Of course, if the ModelState is not valid the transaction is cancelled, but that does not prevent the errors above from happening.
I would have expected that first attributes like Range or StringLength of the ContentParts are applied to the ContentItem. If these checks result in failures they are added to the ModelState, and no database interactions are made.

Maybe my understanding of the underlying concepts is wrong. If someone could point me to right path to understanding would be appreciated. Maybe there are different approaches in Orchard to handle these issues which we do not see?