【问题标题】:Specified Cast is not valid when DataBind to Nullable DateTimeOffset and field is NULL当 DataBind 到 Nullable DateTimeOffset 并且字段为 NULL 时,指定的强制转换无效
【发布时间】:2024-05-18 16:25:02
【问题描述】:

我创建了一个简单的 CompositeControl 并公开了一个 Nullable DateTimeOffset 属性。 我正在使用

将控件绑定到 SQL Server DateTimeOffset 字段
DateTimeOffset='<%# Bind("myDateTimeOffsetField") %>'

当 DateTimeOffset 字段有值时,这很有效。 但是当该字段为 NULL 时,我会收到“Specified Cast is not valid”错误。

当字段为 NULL 时,如何停止此错误并将我的属性设置为 Nothing?

我认为这将是默认行为!

属性定义为:

Public Property DateTimeOffset As DateTimeOffset?

稍后评论:

我发现如果我从使用 Bind 更改为:

DateTimeOffset='<%# iif(IsDbNull(Eval("myDateTimeOffsetField")), Nothing, Eval("myDateTimeOffsetField")) %>'

但是我没有在 FormView.ItemUpdating 事件中将“myDateTimeOffsetField”作为参数传递(是的,这是在 FormView 控件中),因为 ASP.NET 假定我没有绑定回数据库。

实际代码(按要求添加)

这是我尝试绑定到的复合控件中的属性:

Public Property DateTimeOffset As DateTimeOffset?
Get
    Return CType(ViewState("DTO"), DateTimeOffset?)
End Get
Set(value As DateTimeOffset?)
    ViewState("DTO") = value
End Set
End Property

这是绑定的标记。控件位于 FormView 的 EditItemTemplate 中,它绑定到 SQL 数据源,返回一个名为 [dtoMldRejOn] 的字段,其中包含可选的 DateTimeOffset 值。

<APS:DateTimeOffsetControl runat="server" id="dtocMldRejOn" TextBoxCssClass="inputdatetime" ValidationGroup="vw1" FieldName="<%$ Resources: Resource, rxgFrom %>" DateTimeOffset='<%# Bind("dtoMldRejOn") %>' WindowsTimeZoneID="<%# me.WindowsTimeZoneID %>" IsRequired="false" />

如您所见,我的 Composite 控件用于处理 DateTimeOffset 值。除非数据库中的 DateTimeOffset 字段 [dtoMldRejOn] 为 NULL,否则一切正常,然后我得到异常。

【问题讨论】:

  • 可能是一个愚蠢的问题,但是如果你尝试DateTimeOffset=Nothing,你会得到同样的错误吗?
  • 不,我可以毫无问题地将其设置为 Nothing。

标签: asp.net vb.net data-binding nullable datetimeoffset


【解决方案1】:

我以前从未创建过可绑定控件,但我想提出建议。如何将您的DateTimeOffset 属性设置为Object 类型。这样,该属性将接受任何数据类型,包括 DBNull。

一旦进入Set 代码,检查传递的值是否为 DBNull.Value。如果是这样,创建一个新的空 DataTimeOffset?对象并将其保存在 ViewState 中。

如果不是 DBNull 值,如果不能转换为日期时间,则抛出错误。

虽然我没有尝试过,所以我不知道这是否有效。

################ 更新答案################

我的建议是,您创建 2 个属性如下:

Public Property DateTimeOffset() As DateTimeOffset?
    Get
        Return DirectCast(ViewState("DTO"), DateTimeOffset?)
    End Get
    Set(ByVal Value As DateTimeOffset?)
        ViewState("DTO") = Value
    End Set
End Property

<Bindable(True, BindingDirection.TwoWay)>
Public Property DbDateTimeOffset As Object
    Get
        Return Me.DateTimeOffset
    End Get
    Set(value As Object)
        If IsDBNull(value) OrElse value Is Nothing Then
            Me.DateTimeOffset = New DateTimeOffset?
        Else
            Me.DateTimeOffset = DirectCast(value, DateTimeOffset?)
        End If
    End Set
End Property

所以在您的标记中,绑定将是DbDateTimeOffset 属性:

DbDateTimeOffset='<%# Bind("myDateTimeOffsetField") %>'

在后面的代码中,您可以使用其他属性来读取属性而无需强制转换。

【讨论】:

  • 嗨,是的,我已经尝试过了,它可以工作。我在对象属性设置器中检查 IsDbNull,如果为 True,我将“真实”DateTimeOffset 属性设置为 Nothing。这一切都有效,但不幸的是,这意味着每当我将对象属性读回时,我总是必须将其转换为可为空的 DateTimeOffset - 否则总是从“真实”属性中读取并写入“对象”属性。我对这个解决方案并不满意,因为我正在设计控件供其他人使用,如果可能的话,我宁愿避免这种复杂性。如果我找不到解决方案,这可能是最好的解决方法。
  • 评论您的更新答案:谢谢 ajakblackgoat。我不喜欢这个凌乱的对象想法(!),但像你一样,我得出了同样的结论——看起来我必须坚持下去,直到有更好的事情出现。我会接受你的回答,因为它比我自己的实现更整洁,而且赏金即将到期!再次感谢。
  • 疯了,真的,这里的唯一目的是检查属性 Getter 中的 DbNull 并在 True 时将其转换为 Nothing!我怀疑有更好的方法,但现在就可以了。请注意,在另一个方向是 DateTimeOffset? of Nothing 自动转换为 NULL 没有问题!
  • 谢谢你的积分。供您参考,虽然这个想法很混乱,但(至少)最流行的商业日期选择器控件之一就是这样做的。
  • 哈 - 知道 ajak 很有趣!再次感谢,当之无愧的分数。
【解决方案2】:

基于this post

我认为您只需要使用Bindable 属性标记您的属性:

<System.ComponentModel.Bindable(True)> _
Public Property DateTimeOffset As DateTimeOffset?

【讨论】:

  • Dang,当我阅读那篇文章时,我以为你有它,但不,在 CompositeControl.DataBind() 中出现完全相同的错误“指定的转换无效”。我想知道 ASP.NET 是否不知道如何绑定 NULL DateTimeOffset
  • @PapillonUK - 问题不是 DTO。它只是绑定一个可以为空的值,这就是问题所在。它必须了解DBNull.Value 与null 相同(VB 中的Nothing)。它应该可以工作。您能否更新您的答案并发布更多代码,以便我可以准确运行您正在运行的内容?
  • 如果我使我的 DateTimeOffset 属性不可为空,我会遇到完全相同的问题。我真的认为这是它正在努力解决的 DateTimeOffset 问题。我会试着放一些代码!
【解决方案3】:

问题在于DbNullNothing 不同,您必须在代码中的某处明确地写出它。我的第一个想法是使用绑定事件来添加值。所以如果你保持这样的代码:

DateTimeOffset='<%# iif(IsDbNull(Eval("myDateTimeOffsetField")), Nothing, Eval("myDateTimeOffsetField")) %>'

您可以在绑定继续之前在Updating 事件中手动添加DateTimeOffset 参数(如果需要,我可以稍后更新答案,提供更多详细信息)

无论如何,在更仔细地阅读了您的代码之后,我认为CType 可能没有正确转换。你试过用这样的东西替换你的Get吗?

Get
    Return If(IsDbNull(ViewState("DTO")), Nothing, CType(ViewState("DTO"), DateTimeOffset?))
End Get

【讨论】:

  • Get 命令的更改在初始绑定期间导致相同的“强制转换无效错误”,因此我怀疑错误是在 Set 期间而不是在 get 期间。是的,我知道我可以在更新事件期间使用 FindControl 添加参数。这实际上是我正在做的事情,但我不喜欢它,因为控件也是供其他人使用的!不过谢谢你的想法!
  • @PapillonUK 如果它在片场,请反其道而行之。 If(string.IsNullOrEmpty(value))) Then ViewState("DTO") = DbNull.Value
  • Hi nmat - 在 Getter 中测试 DbNull 的代码永远不会被命中。强制转换异常发生在 Getter 甚至被调用之前,这似乎是由于 .NET 在数据绑定期间无法处理 DbNull 到 DateTimeOffset 的转换。
【解决方案4】:

您的 iif ... 代码适合我。
我创建了一个测试控件和一个带有页面代码的页面版本(我也测试了版本后面的代码,但这个更容易发布)。我的VS2010目标框架是4.0。 首先是控件(TstNullableCtrl.ascx):

<%@ Control Language="vb" AutoEventWireup="false" %>
<script runat="server">
    Public Property DateTimeOffset As DateTimeOffset?
</script>
<div ID="Label1" runat="server" >
 <%= If(DateTimeOffset.HasValue, DateTimeOffset.ToString, "Empty")%>
</div>

和页面 - 一个两行两列绑定到数据网格的表格(TstNullablePage.aspx):

<%@ Page Language="vb" AutoEventWireup="false"  %>
<%@ Import Namespace="System.Data"%>
<%@ Register src="TstNullableCtrl.ascx" tagname="TstNullableCtrl" tagprefix="uc1" %>
<script runat="server">
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        Dim tbl As New DataTable
        tbl.Columns.Add(New DataColumn("id", GetType(Integer)) With {.AutoIncrement = True})
        tbl.Columns.Add(New DataColumn("myDateTimeOffsetField", GetType(DateTimeOffset)))
        Dim row As DataRow
        row = tbl.NewRow : row("myDateTimeOffsetField") = DateTimeOffset.Now
        tbl.Rows.Add(row)
        row = tbl.NewRow : row("myDateTimeOffsetField") = DBNull.Value
        tbl.Rows.Add(row)
        tstgrd.DataSource = tbl : tstgrd.DataBind()
    End Sub
</script>
<html>
<body>
    <form id="form1" runat="server">
      <asp:datagrid ID="tstgrd" runat="server">
        <Columns>
         <asp:TemplateColumn HeaderText="Offset">
           <itemtemplate>    
             <uc1:TstNullableCtrl ID="WithNullableDate1" runat="server" 
               DateTimeOffset='<%# iif(IsDbNull(Eval("myDateTimeOffsetField")), Nothing, Eval("myDateTimeOffsetField")) %>' />
            </itemtemplate>
         </asp:TemplateColumn>
         </Columns>
        </asp:datagrid>
    </form>
</body>
</html>

和预期的结果(有时为值,为空时为'Empty')

编辑

但我认为“避免复杂化”的最佳解决方案如下:

Public _DateTimeOffset As Object
Public Property DateTimeOffset As Object
Get
    If IsDBnull(_DateTimeOffset) then Return Nothing
    Return Ctype(_DateTimeOffset, DateTimeOffset?) 
End Get
Set(value As Object)
    _DateTimeOffset = value
End Set
End Property 

【讨论】:

  • 谢谢。是的,复杂性是我要避免的,而您已经找到了相同的解决方案 - 即我必须为属性使用对象而不是 DateTimeOffset,因为 ASP.NET 无法将 dbNull 绑定到 DateTimeOffset 结构。我认为 ajakblackgoat 为这个解决方法提供了最完整的解决方案,所以我很欣赏这里的工作,但我必须给他点!
【解决方案5】:

我知道这个问题已经得到解答我只想记录我的解决方案,因为我今天遇到了这两个答案:

Private _AssetId As Object
<Bindable(True, BindingDirection.TwoWay)>
Public Property AssetId() As Object
    Get
        If _AssetId Is Nothing Then                
             Return Nothing                
        Else
            Return CType(_AssetId, Integer)
        End If
    End Get
    Set(ByVal value As Object)
        If value Is Nothing OrElse IsDBNull(value) OrElse CType(value, String) = "" Then
            _AssetId = Nothing                
        Else
            _AssetId = CType(value, Integer)                
        End If
    End Set
End Property

【讨论】:

    最近更新 更多