2015-05-27 4 views
4

У меня есть несколько хранимых процедур, которые читают одну и ту же таблицу и возвращают данные в том же макете. Одна из этих хранимых процедур вызывает недопустимое исключение литых в поле, которое хранится как бит. SqlDataReader считает, что это int32.Недопустимое исключение литья из SqlDataReader при чтении бит

Пример таблицы

create table dbo.SampleTable 
(
    Id  INT NOT NULL IDENTITY(1,1), 
    SomeText VARCHAR(40), 
    BoolValue BIT 
) 

Sample хранимая процедура

create proc SampleProcedure 
as 
    select Id, 
      SomeText, 
      BoolValue 
go 

класс расширения Образец

using System; 
using System.Data.SqlClient; 

namespace SampleApp.Extensions{ 
    public static class SampleModelExtension{ 
     private const int INDEX_ID = 0; 
     private const int INDEX_SOMETEXT = 1; 
     private const int INDEX_BOOLVALUE = 2; 

     public static SampleModel ToSampleModel(this SqlDataReader rdr){ 
      SampleModel myModel = new SampleModel(); 

      myModel.Id = !rdr.IsDbNull(INDEX_ID) ? rdr.GetInt32(INDEX_ID) : 0; 
      myModel.SomeText = !rdr.IsDbNull(INDEX_SOMETEXT) ? rdr.GetString(INDEX_SOMETEXT) : String.Empty; 
      myModel.Boolvalue = !rdr.IsDbNull(INDEX_BOOLVALUE) ? rdr.GetBool(INDEX_BOOLVALUE) : false; 

      return myModel; 
     } 
    } 
} 

класс хранилище образцов

using SampleApp.Extensions; 
using System; 
using System.Collections.Generic; 
using System.Data.SqlCient; 

namespace SampleApp { 
    public SampleRepository : BaseDataConnection { 
     public List<SampleModel> GetSampleData(){ 
      SqlCommand cmd = new SqlCommand("SampleProcedure", base.Connection); 

      List<SampleModel> retVal = new List<SampleModel>(); 

      using(SqlDataReader rdr = base.GetDataReader(cmd)){ 
       while(rdr.Read()){ 
        retVal.Add(rdr.ToSampleModel()); 
       } 
      } 

      return retVal; 
     } 

     public List<SampleModel> GetMoreSampleData(){ 
      SqlCommand cmd = new SqlCommand("AnotherSampleProcedure", base.Connection); 

      List<SampleModel> retVal = new List<SampleModel>(); 

      using(SqlDataReader rdr = base.GetDataReader(cmd)){ 
       while(rdr.Read()){ 
        retVal.Add(rdr.ToSampleModel()); 
       } 
      } 

      return retVal; 
     } 

    } 
} 

Это похоже на то, что у меня есть. В моем коде у меня есть метод расширения, который преобразует SqlDataReader в тип SampleModel, так что метод расширения используется повторно во всех методах загрузки в классе репозитория. Именно из-за этого подхода я знаю, что он работает со всеми другими методами.

Любые идеи о том, почему это будет видеть столбец как int вместо бит?

Фактическое хранимых процедур

ALTER PROCEDURE [dbo].[GetAllActiveScheduledEventsByDateRange] 
    @SiteGuid  VARCHAR(38), 
    @StartDate  DATE, 
    @EndDate  DATE 
AS 
    SELECT se.EventId, 
      se.AvailableDate, 
      se.StartTime, 
      se.NumberOfPatrons, 
      se.AgeOfPatrons, 
      se.ContactEmailAddress, 
      se.ContactPhone, 
      se.ContactName, 
      se.EventTypeId, 
      se.PartyName, 
      se.ConfirmationDateTime, 
      se.ReminderDateTime, 
      se.UserComments, 
      se.AdminComments, 
      se.Active, 
      se.CheckInTime, 
      se.CheckOutTime, 
      se.GunSize, 
      (
       Select Count(p.playerid) from 
        (select * from waiver2 where waiverid in (
         (Select WaiverId 
         from Waiver2 
         inner join 
         (
          Select max(CreateDateTime) as LatestDate, PlayerId 
          from Waiver2 
          WHERE siteguid = @SiteGuid 
          Group by PlayerId 
         ) SubMax 
         on Waiver2.CreateDateTime = SubMax.LatestDate 
         and Waiver2.PlayerId = SubMax.PlayerId))) w, 
        player p, PlayDateTime updt 
       where p.playerid = w.playerid 
       and p.playerid = updt.PlayerId 
       and updt.EventId = se.EventId) AS WaiverCount, 
      se.DepositAmount, 
      se.CreateDateTime, 
      se.PaymentReminderDateTime, 
      se.PaymentStatusId, 
      se.PackageId 
    FROM ScheduledEvent se 
    WHERE se.SiteGuid = @SiteGuid 
    AND  se.AvailableDate BETWEEN @StartDate AND @EndDate 
    AND  se.PaymentStatusId < '99' 
    AND  se.Active = 1 
    ORDER BY se.StartTime, se.ContactName 

Active колонна является тот, который бросает ошибку. Она определяется как BIT и индексируется в колонке 14.

Фактическая хранимая процедура вызывает проблему

ALTER proc [dbo].[W2_GetAllActiveScheduledEventsByDateWithWaivers] 
    @SiteGuid  VARCHAR(38), 
    @AvailableDate DATE 
AS 
    SELECT se.EventId, 
     se.AvailableDate, 
     se.StartTime, 
     se.NumberOfPatrons, 
     se.AgeOfPatrons, 
     se.ContactEmailAddress, 
     se.ContactPhone, 
     se.ContactName, 
     se.EventTypeId, 
     se.PartyName, 
     se.ConfirmationDateTime, 
     se.ReminderDateTime, 
     se.UserComments, 
     se.AdminComments, 
     se.Active, 
     se.CheckInTime, 
     se.CheckOutTime, 
     se.GunSize, 
     (
      Select Count(p.playerid) from 
       (
       select * from waiver2 where waiverid in 
        (
         (
          Select WaiverId 
          from Waiver2 
          inner join 
           (
            Select max(CreateDateTime) as LatestDate, PlayerId 
            from Waiver2 
            WHERE siteguid = @SiteGuid 
            Group by PlayerId 
           ) SubMax 
          on Waiver2.CreateDateTime = SubMax.LatestDate 
          and Waiver2.PlayerId = SubMax.PlayerId 
          and DateDiff(year,Waiver2.CreateDateTime,GETDATE()) = 0 
         ) 
        ) 
       ) w, 
       player p, PlayDateTime updt 
      where p.playerid = w.playerid 
      and p.playerid = updt.PlayerId 
      and updt.EventId = se.EventId 
      and ((
        FLOOR(DATEDIFF(day,p.DateOfBirth,GETDATE())/365.242199) >= 18 
        and 
        w.ParentId is null 
       ) 
       or 
       (
        FLOOR(DATEDIFF(day,p.DateOfBirth,GETDATE())/365.242199) < 18 
        and 
        w.ParentId is not null 
       )) 
     ) AS WaiverCount, 
     se.DepositAmount, 
     se.CreateDateTime, 
     se.PaymentReminderDateTime, 
     se.PaymentStatusId, 
     se.PackageId 
FROM ScheduledEvent se 
WHERE se.SiteGuid = @SiteGuid 
AND  se.AvailableDate = @AvailableDate 
AND  se.PaymentStatusId <= '90' 
AND  se.Active = 1 
--ORDER BY se.StartTime, se.ContactName 

union select null, 
     pdt.PlayDate, 
     pdt.PlayTime, 
     null, 
     null, 
     null, 
     null, 
     null, 
     null, 
     'Walk-up Players', 
     null, 
     null, 
     null, 
     null, 
     1, 
     null, 
     null, 
     null, 
     COUNT('x') AS WaiverCount, 
     0, 
     null, 
     null, 
     null, 
     null 
from PlayDateTime pdt 
where pdt.PlayDate = @AvailableDate 
and pdt.EventId is null 
and pdt.PlayerId in (
    Select p.playerid from 
     (select * from waiver2 where waiverid in (
      (Select WaiverId 
      from Waiver2 
      inner join 
      (
       Select max(CreateDateTime) as LatestDate, PlayerId 
       from Waiver2 
       WHERE siteguid = @SiteGuid 
       Group by PlayerId 
      ) SubMax 
       on Waiver2.CreateDateTime = SubMax.LatestDate 
       and Waiver2.PlayerId = SubMax.PlayerId 
       and DateDiff(year,Waiver2.CreateDateTime,GETDATE()) = 0))) w, 
     player p 
    where p.playerid = w.playerid 
    and ((
      FLOOR(DATEDIFF(day,p.DateOfBirth,GETDATE())/365.242199) >= 18 
      and 
      w.ParentId is null 
     ) 
     or 
     (
      FLOOR(DATEDIFF(day,p.DateOfBirth,GETDATE())/365.242199) < 18 
      and 
      w.ParentId is not null 
     )) 
) 
group by pdt.PlayDate, pdt.PlayTime 
order by 2, 3, 10 

Это фактический класс расширения (с именами изменены, чтобы защитить невинных)

namespace MyNameSpace.Svc.Core.Extensions.Registration { 
    public static class ScheduledEventExtension { 
    #region attributes 
    private const int INDEX_ID = 0; 
    private const int INDEX_DATE = 1; 
    private const int INDEX_STARTTIME = 2; 
    private const int INDEX_NUMBEROFPATRONS = 3; 
    private const int INDEX_AGEOFPATRONS = 4; 
    private const int INDEX_CONTACTEMAIL = 5; 
    private const int INDEX_CONTACTPHONE = 6; 
    private const int INDEX_CONTACTNAME = 7; 
    private const int INDEX_EVENTTYPE = 8; 
    private const int INDEX_PARTYNAME = 9; 
    private const int INDEX_CONFIRMDATE = 10; 
    private const int INDEX_REMINDDATE = 11; 
    private const int INDEX_USERCOMMENTS = 12; 
    private const int INDEX_ADMINCOMMENTS = 13; 
    private const int INDEX_ACTIVE = 14; 
    private const int INDEX_CHECKINTIME = 15; 
    private const int INDEX_CHECKOUTTIME = 16; 
    private const int INDEX_GUNSIZE = 17; 
    private const int INDEX_WAIVERCOUNT = 18; 
    private const int INDEX_DEPOSITAMOUNT = 19; 
    private const int INDEX_CREATEDATETIME = 20; 
    private const int INDEX_PAYMENTREMINDERDATETIME = 21; 
    private const int INDEX_PAYMENTSTATUS = 22; 
    private const int INDEX_PACKAGEID = 23; 
    #endregion 

    #region methods 
    public static ScheduledEvent ToScheduledEvent(this SqlDataReader rdr) { 
     ScheduledEvent retVal = new ScheduledEvent(); 

     retVal.Id = !rdr.IsDBNull(INDEX_ID) ? rdr.GetInt32(INDEX_ID) : 0; 
     retVal.SelectedDate.SelectedDate = !rdr.IsDBNull(INDEX_DATE) ? rdr.GetDateTime(INDEX_DATE) : DateTime.MinValue; 
     retVal.SelectedDate.StartTime = !rdr.IsDBNull(INDEX_STARTTIME) ? rdr.GetTimeSpan(INDEX_STARTTIME) : TimeSpan.MinValue; 

     int numOfPatrons = 0; 
     int.TryParse(rdr.GetString(INDEX_NUMBEROFPATRONS), out numOfPatrons); 
     retVal.NumberOfPatrons = numOfPatrons; 

     retVal.AgeOfPatrons = !rdr.IsDBNull(INDEX_AGEOFPATRONS) ? rdr.GetString(INDEX_AGEOFPATRONS) : string.Empty; 
     retVal.ContactEmailAddress = !rdr.IsDBNull(INDEX_CONTACTEMAIL) ? rdr.GetString(INDEX_CONTACTEMAIL) : string.Empty; 
     retVal.ContactPhone = !rdr.IsDBNull(INDEX_CONTACTPHONE) ? rdr.GetString(INDEX_CONTACTPHONE) : string.Empty; 
     retVal.ContactName = !rdr.IsDBNull(INDEX_CONTACTNAME) ? rdr.GetString(INDEX_CONTACTNAME) : string.Empty; 
     // event type is obsolete 
     retVal.PartyName = !rdr.IsDBNull(INDEX_PARTYNAME) ? rdr.GetString(INDEX_PARTYNAME) : string.Empty; 
     retVal.ConfirmationDateTime = !rdr.IsDBNull(INDEX_CONFIRMDATE) ? rdr.GetDateTime(INDEX_CONFIRMDATE) : DateTime.MinValue; 
     retVal.ReminderDateTime = !rdr.IsDBNull(INDEX_REMINDDATE) ? rdr.GetDateTime(INDEX_REMINDDATE) : DateTime.MinValue; 
     retVal.Comments = !rdr.IsDBNull(INDEX_USERCOMMENTS) ? rdr.GetString(INDEX_USERCOMMENTS) : string.Empty; 
     retVal.AdminComments = !rdr.IsDBNull(INDEX_ADMINCOMMENTS) ? rdr.GetString(INDEX_ADMINCOMMENTS) : string.Empty; 
     retVal.Active = !rdr.IsDBNull(INDEX_ACTIVE) ? rdr.GetBoolean(INDEX_ACTIVE) : false; 
     retVal.CheckInDateTime = !rdr.IsDBNull(INDEX_CHECKINTIME) ? rdr.GetDateTime(INDEX_CHECKINTIME) : DateTime.MinValue; 
     retVal.CheckOoutDateTime = !rdr.IsDBNull(INDEX_CHECKOUTTIME) ? rdr.GetDateTime(INDEX_CHECKOUTTIME) : DateTime.MinValue; 
     // gun size is obsolete 
     retVal.WaiverCount = !rdr.IsDBNull(INDEX_WAIVERCOUNT) ? rdr.GetInt32(INDEX_WAIVERCOUNT) : 0; 
     retVal.DepositAmount = !rdr.IsDBNull(INDEX_DEPOSITAMOUNT) ? rdr.GetDecimal(INDEX_DEPOSITAMOUNT) : 0; 
     retVal.CreateDateTime = !rdr.IsDBNull(INDEX_CREATEDATETIME) ? rdr.GetDateTime(INDEX_CREATEDATETIME) : DateTime.MinValue; 
     retVal.PaymentReminderDateTime = !rdr.IsDBNull(INDEX_PAYMENTREMINDERDATETIME) ? rdr.GetDateTime(INDEX_PAYMENTREMINDERDATETIME) : DateTime.MinValue; 
     retVal.PaymentStatus = !rdr.IsDBNull(INDEX_PAYMENTSTATUS) ? PaymentStatusExtension.ToPaymentStatusEnum(rdr.GetString(INDEX_PAYMENTSTATUS)) : PaymentStatusEnum.Unpaid; 
     retVal.SelectedPackage.Id = !rdr.IsDBNull(INDEX_PACKAGEID) ? rdr.GetInt32(INDEX_PACKAGEID) : 0; 

     return retVal; 
    } 
    #endregion 
    } 
} 

Это мой репозиторий класс (опять же с незначительными изменениями)

using MyNameSpace.Svc.Core.Extensions; 
using MyNameSpace.Svc.Core.Extensions.Registration; 
using MyNameSpace.Svc.Core.Interfaces.Registration; 
using MyNameSpace.Svc.Core.Models.Registration; 

using System; 
using System.Collections.Generic; 
using System.Data.SqlClient; 

namespace MyNameSpace.Svc.Impl.Repositories.Registration { 
    public class ScheduledEventRepositoryImpl : DatabaseConnection, IScheduledEventRepository { 
    #region attributes 
    private const string PARMNAME_RETURN = "retval"; 
    private const string PARMNAME_ID = "EventId"; 
    private const string PARMNAME_GUID = "SiteGuid"; 
    private const string PARMNAME_AVAILABLEDATE = "AvailableDate"; 
    private const string PARMNAME_STARTTIME = "StartTime"; 
    private const string PARMNAME_NUMPATRONS = "NumberOfPatrons"; 
    private const string PARMNAME_AGEPATRONS = "AgeOfPatrons"; 
    private const string PARMNAME_CONTACTEMAIL = "ContactEmailAddress"; 
    private const string PARMNAME_CONTACTPHONE = "ContactPhone"; 
    private const string PARMNAME_CONTACTNAME = "ContactName"; 
    private const string PARMNAME_PARTYNAME = "PartyName"; 
    private const string PARMNAME_CONFDATE = "ConfirmationDateTime"; 
    private const string PARMNAME_REMINDDATE = "ReminderDateTime"; 
    private const string PARMNAME_USERCOMMENTS = "UserComments"; 
    private const string PARMNAME_ADMINCOMMENTS = "AdminComments"; 
    private const string PARMNAME_CHECKINTIME = "CheckInTime"; 
    private const string PARMNAME_CHECKOUTTIME = "CheckOutTime"; 
    private const string PARMNAME_DEPOSITAMT = "DepositAmount"; 
    private const string PARMNAME_CREATEDATE = "CreateDateTime"; 
    private const string PARMNAME_PAYMENTREMINDDATE = "PaymentReminderDateTime"; 
    private const string PARMNAME_PAYMENTSTATUS = "PaymentStatusId"; 
    private const string PARMNAME_PKGID = "PackageId"; 
    private const string PARMNAME_EMAIL = "EmailAddress"; 
    private const string PARMNAME_DAYSOUT = "DaysOut"; 
    private const string PARMNAME_EVENTTYPE = "EventTypeId"; 
    private const string PARMNAME_STARTDATE = "StartDate"; 
    private const string PARMNAME_ENDDATE = "EndDate"; 

    private const string SPNAME_GETALLACTIVEBYDATERANGE = "GetAllActiveScheduledEventsByDateRange"; 
    private const string SPNAME_GETALLACTIVEBYDATEWITHWAIVERS = "W2_GetAllActiveScheduledEventsByDateWithWaivers"; 
    #endregion 

    #region methods 
    public List<ScheduledEvent> GetAllActiveScheduledEventsByDateRange(Guid siteGuid, DateTime startDate, DateTime endDate) { 
     List<ScheduledEvent> retVal = new List<ScheduledEvent>(); 

     SqlCommand cmd = new SqlCommand(SPNAME_GETALLACTIVEBYDATERANGE, base.Connection); 

     cmd.Parameters.AddWithValue(PARMNAME_GUID, siteGuid.ToFormattedString()); 
     cmd.Parameters.AddWithValue(PARMNAME_STARTDATE, startDate); 
     cmd.Parameters.AddWithValue(PARMNAME_ENDDATE, endDate); 

     using(SqlDataReader rdr = base.GetDataReader(cmd)) { 
      while(rdr.Read()) { 
       retVal.Add(rdr.ToScheduledEvent()); 
      } 
     } 

     return retVal; 
    } 

    public List<ScheduledEvent> GetAllActiveScheduledEventsByDateWithWaivers(Guid siteGuid, DateTime availableDate) { 
     List<ScheduledEvent> retVal = new List<ScheduledEvent>(); 

     using(SqlDataReader rdr = base.GetDataReader(SPNAME_GETALLACTIVEBYDATEWITHWAIVERS, PARMNAME_AVAILABLEDATE, availableDate, siteGuid)) { 
      while(rdr.Read()) { 
       retVal.Add(rdr.ToScheduledEvent()); 
      } 
     } 

     return retVal; 
    } 
    #endregion 
    } 
} 
+0

Возможно, хранимая процедура, которую вы звоните, читая его как целое, а не бит. –

+1

какая строка дает вам исключение? У вас есть образец трассировки стека? – vmg

+0

Можем ли мы увидеть фактический SP? Или, по крайней мере, лучшее представление об этом? – Mark

ответ

9

Это весьма сомнительно, что "SqlDataReader считает, что это int32". знает только, что такое каждое поле, потому что РСУБД отправляет схему результирующего набора вместе с результирующим набором. Так что столбец (или, вернее: это поле в наборе результатов) : a int.

В исходных окончательных 3 образцах кода (обозначенных как «фактические») сохраненная процедура не соответствовала процессу, который вызывается классом репозитория в любом имени или параметрах.

Теперь, когда образцы кода были обновлены, реальный proc (как отмечено в комментарии к вопросу) имеет UNION, в котором тот же столбец имеет буквальный 1.По умолчанию тип литерала 1 равен INT, поэтому имеет смысл, что поле BIT неявно литовано в INT из-за UNION.

И если вы хотите, чтобы увидеть это в действии, попробуйте следующее (который заглядывает в схеме результирующего набора с помощью sys.dm_exec_describe_first_result_set, которая была введена в SQL Server 2012):

SELECT [system_type_name] 
FROM sys.dm_exec_describe_first_result_set('SELECT CONVERT(BIT, 1) AS [BITorINT?]', 
              NULL, NULL); 
-- bit 

SELECT [system_type_name] 
FROM sys.dm_exec_describe_first_result_set('SELECT 1 AS [BITorINT?]', NULL, NULL); 
-- int 

SELECT [system_type_name] 
FROM sys.dm_exec_describe_first_result_set('SELECT CONVERT(BIT, 1) AS [BITorINT?] 
              UNION ALL 
              SELECT 1', NULL, NULL); 
-- int 

Советы в следующий раз :

  1. Запустите proc в SSMS и убедитесь сами, что возвращается для каждого поля.
  2. Двойное определение столбца таблицы.
+0

Я думаю, что это ответ. Довольно легко, когда вы видите фактический код, а затем немного в маске, укоротите один –

+0

Да, когда я увидел союз, это то, что я понял. Я ценю те длины, которые вы прошли, чтобы доказать это. – fizch

+0

@GiorgiNakeuri Да, я некоторое время смотрел на неправильный код. Я пытался упростить все, «маскируя» его. – fizch

0

Внутренний sql-сервер преобразует значения бит в крошечный int, поэтому объединение попытается оценить и сказать, что эти два значения вместе являются этим типом данных.

1 и нуль приведет к типу данных INT скорее всего

Смежные вопросы