2013年5月7日 星期二

EntLib Data Access Application Block + Oralce 出現「只接受非 Null 的 OracleParameter 型別物件,不接受 OracleParameter 物件」錯誤

文章標題很長,但主要是要記錄出現「OracleParameterCollection 只接受非 Null 的 OracleParameter 型別物件,不接受 OracleParameter 物件。」這個錯誤時的解決方式,這是出現在專案使用 Enterprise Library Data Access Application Block 存取 Oracale 資料庫的情況,同樣的存取方式如果是換成是對 MS SQL Server 做存取時就不會有問題,而對 Oracle 資料庫存取時就必須要使用另一種方式來解決。

錯誤畫面:

image

 


開發使用環境:

Visual Studio 2012, Oracle XE, ASP.NET MVC 4,
.NET Framework 4.5,
Enterprise Library 6 - Data Access Application.

 

對 MS SQL Server 取得資料的處理

先看看對 MS SQL Server 資料庫取得資料的程式,

CategoryRepository.cs - GetOne Method

/// <summary>
/// Gets the one.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public Category GetOne(int id)
{
    string sqlStatement = "select * from Categories where CategoryID = @CategoryID";
 
    try
    {
        DataAccessor<Category> accessor =
            this.Db.CreateSqlStringAccessor<Category>(
                sqlStatement,
                new CategoryIDParameterMapper(),
                new CategoryMapper());
 
        var data = accessor.Execute(new object[] { id }).FirstOrDefault();
        return data;
    }
    catch
    {
        throw;
    }
}
public class CategoryIDParameterMapper : IParameterMapper
{
    public void AssignParameters(DbCommand command, object[] parameterValues)
    {
        command.Parameters.Add(new SqlParameter("CategoryID", parameterValues[0]));
    }
}
public class CategoryMapper : IRowMapper<Category>
{
    public Category MapRow(IDataRecord reader)
    {
        Category item = new Category();
 
        item.CategoryID = int.Parse(reader["CategoryID"].ToString());
        item.CategoryName = reader["CategoryName"].ToString();
        item.Description = reader["Description"].ToString();
 
        return item;
    }
}

這邊我所使用了 EntLib DAAB 裡面的 DataAccessor, IRowMapper, IParameterMapper,能夠讓程式碼更為簡潔,而且可以重複使用 IRowMapper 與 IParameterMapper,當其他 Method 使用到相同 Parameter 結構或是資料對應輸出時,就可以不必重複輸入一樣的程式碼內容。

為了做個比對,以下是一樣使用 EntLib DAAB 但沒有使用 IParamMapper 與 IRowMapper 的程式碼內容,

/// <summary>
/// Gets the categories.
/// </summary>
/// <returns></returns>
public List<Category> GetCategories()
{
    List<Category> categories = new List<Category>();
 
    string sqlStatement = "select * from Categories order by CategoryID";
 
    using (DbCommand comm = Db.GetSqlStringCommand(sqlStatement))
    using (IDataReader reader = this.Db.ExecuteReader(comm))
    {
        while (reader.Read())
        {
            Category item = new Category();
            item.CategoryID = int.Parse(reader["CategoryID"].ToString());
            item.CategoryName = reader["CategoryName"].ToString();
            item.Description = reader["Description"].ToString();
 
            categories.Add(item);
        }
    }
    return categories;
}
 
/// <summary>
/// Gets the one.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public Category GetOne(int id)
{
    string sqlStatement = "select * from Categories where CategoryID = @CategoryID";
 
    Category item = new Category();
 
    using (DbCommand comm = Db.GetSqlStringCommand(sqlStatement))
    {
        comm.Parameters.Add(new SqlParameter("CategoryID", id));
 
        using (IDataReader reader = this.Db.ExecuteReader(comm))
        {
            if (reader.Read())
            {
                item.CategoryID = int.Parse(reader["CategoryID"].ToString());
                item.CategoryName = reader["CategoryName"].ToString();
                item.Description = reader["Description"].ToString();
            }
        }
    }
    return item;
}

在上面的程式碼裡可以看到兩個 Method 裡都有做資料對應的處理,同樣的處理作法卻重複出現,如果是改用 IRowMapper, IParameterMapper, DataAccessor 的方式來處理就會簡潔許多,如下:

/// <summary>
/// Gets the categories .
/// </summary>
/// <returns></returns>
public List<Category> GetCategories()
{
    string sqlStatement = "select * from Categories order by CategoryID";
 
    try
    {
        DataAccessor<Category> accessor =
            this.Db.CreateSqlStringAccessor<Category>(sqlStatement, new CategoryMapper());
 
        var categories = accessor.Execute().ToList();
        return categories;
    }
    catch
    {
        throw;
    }
}
 
/// <summary>
/// Gets the one.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public Category GetOne(int id)
{
    string sqlStatement = "select * from Categories where CategoryID = @CategoryID";
 
    try
    {
        DataAccessor<Category> accessor =
            this.Db.CreateSqlStringAccessor<Category>(
                sqlStatement,
                new CategoryIDParameterMapper(),
                new CategoryMapper());
 
        var data = accessor.Execute(new object[] { id }).FirstOrDefault();
        return data;
    }
    catch
    {
        throw;
    }
}

而如果是不使用 EntLib DAAB 而是使用傳統的 ADO.NET 方式呢?

/// <summary>
/// Gets the categories.
/// </summary>
/// <returns></returns>
public List<Category> GetCategories()
{
    List<Category> categories = new List<Category>();
 
    string sqlStatement = "select * from Categories order by CategoryID";
 
    using (SqlConnection conn = new SqlConnection(this.ConnectionString))
    using (SqlCommand command = new SqlCommand(sqlStatement, conn))
    {
        command.CommandType = CommandType.Text;
        command.CommandTimeout = 180;
 
        if (conn.State != ConnectionState.Open) conn.Open();
 
        using (SqlDataReader reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                Category item = new Category();
                item.CategoryID = int.Parse(reader["CategoryID"].ToString());
                item.CategoryName = reader["CategoryName"].ToString();
                item.Description = reader["Description"].ToString();
 
                categories.Add(item);
            }
        }
    }
    return categories;
}
 
/// <summary>
/// Gets the one.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public Category GetOne(int id)
{
    string sqlStatement = "select * from Categories where CategoryID = @CategoryID";
 
    Category item = new Category();
 
    using (SqlConnection conn = new SqlConnection(this.ConnectionString))
    using (SqlCommand comm = new SqlCommand(sqlStatement, conn))
    {
        comm.Parameters.Add(new SqlParameter("CategoryID", id));
 
        if (conn.State != ConnectionState.Open) conn.Open();
 
        using (IDataReader reader = comm.ExecuteReader())
        {
            if (reader.Read())
            {
                item.CategoryID = int.Parse(reader["CategoryID"].ToString());
                item.CategoryName = reader["CategoryName"].ToString();
                item.Description = reader["Description"].ToString();
            }
        }
    }
    return item;
}

 

對 Oracle 取得資料的處理

離題有點太遠了,趕緊拉回來……

上面所看到的是資料庫為 MS SQL Server 的情況,如果資料庫換做是使用 Oracle XE 的情況,一樣是使用 EntLib DAAB, DataAccessor, IRowMapper, IParameterMapper,程式碼如下:

CategoryRepository.cs - GetOne() Method

/// <summary>
/// Gets the one.
/// </summary>
/// <param name="id">The id.</param>
/// <returns></returns>
public Category GetOne(int id)
{
    string sqlStatement = "select * from \"Categories\" where \"CategoryID\" = :CategoryID";
 
    try
    {
        DataAccessor<Category> accessor =
            this.Db.CreateSqlStringAccessor<Category>(
                sqlStatement,
                new CategoryIDParameterMapper(),
                new CategoryMapper());
 
        var data = accessor.Execute(new object[] { id }).FirstOrDefault();
        return data;
    }
    catch
    {
        throw;
    }
}

CategoryIDParameterMapper, CategoryMapper

public class CategoryIDParameterMapper : IParameterMapper
{
    public void AssignParameters(DbCommand command, object[] parameterValues)
    {
        command.Parameters.Add(new OracleParameter("CategoryID", parameterValues[0]));
    }
}
 
public class CategoryMapper : IRowMapper<Category>
{
    public Category MapRow(IDataRecord reader)
    {
        Category item = new Category();
 
        item.CategoryID = int.Parse(reader["CategoryID"].ToString());
        item.CategoryName = reader["CategoryName"].ToString();
        item.Description = reader["Description"].ToString();
 
        return item;
    }
}
上面的程式內容就會發生錯誤「OracleParameterCollection 只接受非 Null 的 OracleParameter 型別物件,不接受 OracleParameter 物件。

image

問題發生在 CategoryIDParameterMapper 裡面,如果將 OracleParamerer 加入到 DbCommand 的方式改使用一般的方式,如下:

public class CategoryIDParameterMapper : IParameterMapper
{
    public void AssignParameters(DbCommand command, object[] parameterValues)
    {
        OracleParameter param = new OracleParameter("CategoryID", OracleDbType.Long, 11);
        param.Value = parameterValues[0];
        command.Parameters.Add(param);
    }
}

還是會出現同樣的錯誤,

image

 

DbCommand.CreateParameter 方法

解決的方式就是使用 DbCommand.CreateParameter 方法,透過 DbCommand 建立 DbParameter 物件的新執行個體,修改後的 CategoryIDParameterMapper 如下:

public class CategoryIDParameterMapper : IParameterMapper
{
    public void AssignParameters(DbCommand command, object[] parameterValues)
    {
        var param = command.CreateParameter();
        param.ParameterName = "CategoryID";
        param.Value = parameterValues[0];
        command.Parameters.Add(param);
    }
}

修改後就不會出現錯誤,執行結果如下:

image

 

 

參考資料:

MSDN - DbCommand.CreateParameter 方法

簡述 Oracle + Enterprise Library 5.0 Data Access Application Block 的操作

Enterprise Library DAAB + Oracle:Provider 使用 ODP.NET

 

以上

沒有留言:

張貼留言

提醒

千萬不要使用 Google Talk (Hangouts) 或 Facebook 及時通訊與我聯繫、提問,因為會掉訊息甚至我是過了好幾天之後才發現到你曾經傳給我訊息過,請多多使用「詢問與建議」(在左邊,就在左邊),另外比較深入的問題討論,或是有牽涉到你實作程式碼的內容,不適合在留言板裡留言討論,請務必使用「詢問與建議」功能(可以夾帶檔案),謝謝。