programing

'System' 형식의 개체를 캐스팅할 수 없습니다.DBNull'을 클릭하여 'System'을 입력합니다.문자열

skycolor 2023. 4. 22. 09:11
반응형

'System' 형식의 개체를 캐스팅할 수 없습니다.DBNull'을 클릭하여 'System'을 입력합니다.문자열

제 앱에서 위와 같은 오류가 발생했습니다.여기 원본 코드가 있습니다.

public string GetCustomerNumber(Guid id)
{
     string accountNumber = 
          (string)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidmyApp, 
                          CommandType.StoredProcedure, 
                          "GetCustomerNumber", 
                          new SqlParameter("@id", id));
     return accountNumber.ToString();
 }

로 대체했습니다.

public string GetCustomerNumber(Guid id)
{
   object accountNumber =  
          (object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM, 
                                CommandType.StoredProcedure, 
                                "spx_GetCustomerNumber", 
                                new SqlParameter("@id", id));
    if (accountNumber is System.DBNull)
    {
       return string.Empty;
    }
    else
    {
       return accountNumber.ToString();
    }
}

더 좋은 방법이 있을까요?

심플한 범용 기능으로 매우 간단하게 할 수 있습니다.다음 작업을 수행합니다.

return ConvertFromDBVal<string>(accountNumber);

다음 기능을 사용합니다.

public static T ConvertFromDBVal<T>(object obj)
{
    if (obj == null || obj == DBNull.Value)
    {
        return default(T); // returns the default value for the type
    }
    else
    {
        return (T)obj;
    }
}

보다 짧은 형식을 사용할 수 있습니다.

return (accountNumber == DBNull.Value) ? string.Empty : accountNumber.ToString()

편집: 주의하지 않음ExecuteScalar반환 결과에 필드가 없는 경우 null이 반환됩니다.대신 다음을 사용합니다.

return (accountNumber == null) ? string.Empty : accountNumber.ToString() 

ExecuteScalar가 반환됩니다.

  • 결과 집합이 없는 경우 null
  • 그렇지 않으면 결과 집합의 첫 번째 행 첫 번째 열(DB Null일 수 있음)이 표시됩니다.

결과 세트의 첫 번째 열이 문자열임을 알고 있는 경우 모든 베이스를 커버하려면 null과 DBNull을 모두 확인해야 합니다.예를 들어 다음과 같습니다.

object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null) ? String.Empty : accountNumber.ToString();

위의 코드는 DBNull이라는 사실에 의존합니다.ToString은 빈 문자열을 반환합니다.

account Number가 다른 유형(예를 들어 정수)인 경우 보다 명확하게 해야 합니다.

object accountNumber = ...ExecuteScalar(...);
return (accountNumber == null || Convert.IsDBNull(accountNumber) ?     
         (int) accountNumber : 0;

결과 세트에 항상 하나 이상의 행(예: SELECT COUNT(*)...)이 있는 것을 확실히 알고 있는 경우는, null 의 체크를 건너뛸 수 있습니다.

이 경우 오류 메시지 "Unable to cast object of the type 'System.DBNull'을 클릭하여 'System'을 입력합니다.String'은 결과 집합의 첫 번째 열이 DBNUll 값임을 나타냅니다.이것은 첫 번째 줄의 캐스트에서 문자열까지입니다.

string accountNumber = (string) ... ExecuteScalar(...);

DBNull을 확인할 필요가 없다는 Marc_s의 코멘트.값이 잘못되었습니다.

C#의 null 병합 연산자를 사용할 수 있습니다.

return accountNumber ?? string.Empty;

DBNull일 수 있는 개체를 변환하기 위해 사용하는 일반적인 방법입니다.값:

public static T ConvertDBNull<T>(object value, Func<object, T> conversionFunction)
{
    return conversionFunction(value == DBNull.Value ? null : value);
}

사용방법:

var result = command.ExecuteScalar();

return result.ConvertDBNull(Convert.ToInt32);

단축:

return command
    .ExecuteScalar()
    .ConvertDBNull(Convert.ToInt32);

이 문제를 회피하는 다른 방법이 있습니다.스토어 프로시저를 수정하는 것은 어떻습니까?ISNULL(your field, "") sql 함수를 사용하면 반환값이 null일 경우 빈 문자열을 반환할 수 있습니다.

그러면 원래 버전대로 클린 코드를 가지게 됩니다.

다음과 같이 할 수 있을 것 같아요.

string accountNumber = DBSqlHelperFactory.ExecuteScalar(...) as string;

accountNumber가 null인 경우 문자열이 아닌 DBNull임을 나타냅니다.

String.Concat은 DBNull 및 null 값을 빈 문자열로 변환합니다.

public string GetCustomerNumber(Guid id)
{
   object accountNumber =  
          (object)DBSqlHelperFactory.ExecuteScalar(connectionStringSplendidCRM, 
                                CommandType.StoredProcedure, 
                                "spx_GetCustomerNumber", 
                                new SqlParameter("@id", id));

    return String.Concat(accountNumber);

 }

하지만 코드 이해성에 대해 뭔가 잃어버린 것 같습니다.

null이 아닌 인스턴스를 얻었기 때문에 DBNULL과 비교하면Operator '==' cannot be applied to operands of type 'string' and 'system.dbnull'exception을 NULL과 비교하려고 해도 (DB Null이 객체이기 때문에) 전혀 동작하지 않습니다.이 답변이 받아들여지고 있습니다.

나는 단순히 'is' 키워드를 사용하기로 했다.따라서 결과는 매우 읽기 쉽습니다.

data = (item is DBNull) ? String.Empty : item

@syslog의 회답에 근거해

public static class DbDataReaderExtensions
{
    public static TObjProp Get<TObj, TObjProp>(
        this DbDataReader reader,
        Expression<Func<TObj, TObjProp>> expression)
    {
        MemberExpression member = expression.Body as MemberExpression;
        string propertyName = member.Member.Name;

        //PropertyInfo propInfo = member.Member as PropertyInfo;

        var recordOrdinal = reader.GetOrdinal(propertyName);
        var obj = reader.GetValue(recordOrdinal);

        if (obj == null || obj == DBNull.Value)
        {
            return default(TObjProp);
        }
        else
        {
            return (TObjProp)obj;
        }
    }
}

지정:

public class MyClass
{
    public bool? IsCheckPassed { get; set; }
}

사용처:

var test = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);

또는 예외 메서드에서 클래스를 하드코드하는 경우:

var test = reader.Get(o => o.IsCheckPassed);

는 아직 p.s를 만드는 generics 제공하다

완전한 예:

public async Task<MyClass> Test(string connectionString) {
    var result = new MyClass();
    
    await using var con = new SQLiteConnection(connectionString);
    con.Open();

    await using var cmd = con.CreateCommand();
    cmd.CommandText = @$"SELECT Id, IsCheckPassed FROM mytable";
    
    var reader = await cmd.ExecuteReaderAsync();
    while (reader.Read()) {
        // old, not working! Throws exception!
        //bool? isCheckPassed1 = reader.GetBoolean(reader.GetOrdinal("IsCheckPassed"));
        
        // old, working, but too long (also if you have like 20 properties then all the more reasons to refactor..)
        bool? isCheckPassed2 = null;
        bool? isCheckPassed2Temp = reader.GetValue(reader.GetOrdinal("IsCheckPassed"));
        if (isCheckPassed2Temp != null && isCheckPassed2Temp != DBNull.Value)
            isCheckPassed2 = (bool?)isCheckPassed2Temp;
        
        // new
        var isCheckPassed3 = reader.Get<MyClass, bool?>(o => o.IsCheckPassed);
        // repeat for 20 more properties :)
        
        result.IsCheckPassed = isCheckPassed3;
    }
    
    return result;
}

솔루션은 테이블 열 이름이 클래스의 속성 이름과 일치하는 한 작동합니다.또, 실가동 레벨의 퍼포먼스는 적절하지 않을 수 있으므로, 사용 또는 변경은, 고객의 책임으로 실시해 주세요.

보다 최신 C# 구문을 사용하고 늘 타입의 어카운팅을 사용하는 보다 간결한 어프로치:

private static T? FromDbNull<T>(object? obj) => 
    obj == null || obj == DBNull.Value ? default : (T)obj;

다음과 같이 데이터 판독기와 함께 사용할 수 있습니다.

        while (reader.Read())
        {
            var newObject = new SomeObject(
                FromDbNull<string?>(reader["nullable_field_1"]),
                FromDbNull<string?>(reader["nullable_field_2"]),
                FromDbNull<string?>(reader["nullable_field_3"]), 
                FromDbNull<double?>(reader["nullable_field_4"])
            );
            
            response.Add(newObject);
        }

저는 이 문제를 제거하기 위해 확장을 사용합니다.이것은 당신이 원하는 것일 수도 있고 그렇지 않을 수도 있습니다.

그건 이런 식이다:

public static class Extensions
{

    public String TrimString(this object item)
    {
        return String.Format("{0}", item).Trim();
    }

}

주의:

내선번호는 반환되지 않습니다.null★★★★★★★의 경우null또는 DBNull을 선택합니다.. 빈 문자열을 반환합니다.

사용방법:

public string GetCustomerNumber(Guid id)
{
    var obj = 
        DBSqlHelperFactory.ExecuteScalar(
            connectionStringSplendidmyApp, 
            CommandType.StoredProcedure, 
            "GetCustomerNumber", 
            new SqlParameter("@id", id)
        );
    return obj.TrimString();
}

다음과 같이 변환

string s = System.DBNull.value.ToString();

언급URL : https://stackoverflow.com/questions/870697/unable-to-cast-object-of-type-system-dbnull-to-type-system-string

반응형