Chia sẻ sourcecode chuyển đổi CSDL

Cảnh báo: đây là bài viết nguy hiểm có nhiều hình ảnh mang tính chất khó hiểu, nghiêm cấm phụ nữ dưới 18 tuổi và trẻ em đang cho con bú

103900d1376831529-dangerous-things-not-ignore-your-car-danger

Thực ra thì viết đùa vậy thôi chứ chẳng qua là bài này mang nhiều tính chất coder hơn so với các bài viết trước mà mọi người có thể xem như là đọc truyên cười :v Chính vì vậy ai không am hiểu có thể bỏ qua cho đỡ phải kéo chuột hết bài

ignore-this-sign

Ở bài viết này mình có tham chiếu đến bài viết lần trước mà mình mới đề cập đến vào ngày hôm qua đó là trigger. Ai chưa rõ thì có thể đọc lại theo link dưới

Tối ưu hiệu năng SQL (phần 4): TriggerTối ưu hiệu năng SQL (phần 4): Trigger

Sau khi dạo lại tí kiến thức ở bài trước xong thì mình xin đi thẳng vào vấn đề đó là hiện tại thì dự án mình đang tham gia có yêu cầu phải chuyển đổi CSDL từ Sql server sang CSDL Oracle.

data-migration805x340-805x321

Do thời gian qua ít nên mình có đưa ra 1 giải pháp tạm thời cho vấn đề này đó là sử dụng trigger để lưu lại toàn bộ các tác động lên CSDL Sql ( Cái việc này có lẽ nó cũng hơi ngu ngu nhưng là giải pháp tình thế nên mình nghĩ có thể bỏ qua được :v). Sau đó sẽ 1 tiến trình ngầm sẽ quét vào bảng log để đọc dữ liệu ra và chuyển sang phía CSDL Oracle.

Ý tưởng thì cũng đơn giản không có gì quá khó khăn nên mình có triển khai ngay và đã hoàn thành chỉ trong 1 cái nháy mắt dài … vài ngày =))

benh20sau20tet20kenh890

Công việc đầu tiên đó là chuyển toàn bộ cấu trúc của CSDL Sql qua bên Oracle trước. Bên mình thì sử dụng luôn công cụ mặc định của thằng Oracle đó là Sql developer. Anh em có thể tham khảo cách làm ở link dưới

http://www.oracle.com/webfolder/technetwork/tutorials/obe/db/hol08/sqldev_migration/mssqlserver/migrate_microsoft_sqlserver_otn.htm

Nói chung nếu chỉ chuyển cấu trúc thì vù vù ấy mà :v Anh em xem thêm mấy loại dữ liệu của 2 thằng để xem cách chúng nó convert qua nhau cho dễ hình dung

http://www.sqlines.com/sql-server-to-oracle

Và rồi từ đó mình bắt đầu viết 1 cái tool nhỏ nhỏ xinh xinh cho công việc nặng nề vất vả này. Thư viện được sử dụng đó là ADO.NET cùng với OracleDataAccess để kết nối CSDL. Ngoài ra để lưu log thì sử dụng log4net, việc chạy ngầm thì được viết dưới dạng WindowsService. Mấy cái này anh em google tự cài nhé :v

Cách xử lý đó là đọc dữ liệu từ trong bảng Log. Từ đó chúng ta xác định được row trong SQL có sự thay đổi để tiến hành sinh ra câu lệnh tương ứng cho việc INSERT, UPDATE hay DELETE ở bên Oracle. (kiểu dynamic sql )

Nghe vẻ ngon ăn nhưng đến lúc làm thì mới phát hiên ra 1 số vấn đề. Chính vì vậy mình mạn phép chia sẻ lại cho anh em  phòng tránh :3 Chơi bời phải biết giữ mình

choi-boi-voi-gai-lang-choi-va-cai-ket-dang-ngat-43a56d

  • Thằng Oracle thì tên cột hay tên bảng không được quá 30 kí tự. Á đù cái này khi dùng tool chuyển từ Sql sang thì cháu nó tự cắt đi phần thừa 😐 Điên đếch chịu được 😐 Thế nên giải pháp là substring :v. Code Item.SubString(0, 3)
  • Tiếp đến đó là thằng nào quá 4000 kí tự là nó phải dùng định dạng khác là CLOB. Anh em xem lại cái link bên trên, thế nên lại phải bổ sung TO_CLOB mới chuyển đổi được.Ví như  TO_CLOB(item)
  • Rồi đến cái ngày tháng lấy ra từ Sql mang qua Oracle cóc được 😐 Thế là lại cũng nhét cái hàm TO_DATE cho cái định dang của nó nữa mới được. Kiểu như này này  String.Format(TO_DATE(‘{0}’, ‘MM/DD/YYYY HH:MI:SS AM’), item)
  • Còn đối với dữ liệu kiểu boolean thì Oracle cũng cóc có nữa mà chỉ là NUMBER(1). Thế là lại ngậm đắng nuốt cay chuyển qua int 0, 1 cho cháu nó :3 Mệt chết đi được
  • Để đảm bảo tránh được Sql Injection thì mình có sử dụng replace cái ‘ thành ”. Giải pháp tình thế cho đỡ lỗi =))
  • À còn cái nữa hôm nay gặp nữa là đó là với dữ liệu kiểu UNIQUEIDENTIFER thì đọc ra từ SQL thì nó không viết hoa nên khi chuyển qua Oracle cũng là không viết hoa. Còn cái tool mặc định của Oracle nó chuyển đổi thì để viết hoa. Cái này làm mình loay hoay cả chiều vĩ SELECT lại để kiểm tra thì không tìm được :v
  • Ban đầu thì mình viết cái INSERT với UPDATE thành từng câu lệnh riêng cho dễ 😛 Ai ngờ kết cục vẫn phải viết kiểu kiểm tra đã có hay chưa, nếu có rồi thì chỉ UPDATE còn không thì INSERT. Search qua mấy bài thì có hướng dẫn nên anh em có thể đọc code là thấy :v Phần này lưu ý phải có BEGIN END thì Oracle mới hiểu được đó là cụm câu lệnh
  • …………………………………………………..

Hiện tại thì mình vẫn đang fixbug và bổ sung thêm 1 số thứ nhằm đáp ứng được nhiều nhu cầu chuyển đổi hơn, anh em có thể tham khảo sourcecode ở link dưới

https://github.com/hoangnl/SqlToOracle

Mọi người ai có đóng góp gì thì có thể email cho mình để mình sửa đổi và nâng cấp: nguyenlehoang@hotmail.com

Cập nhật

Tình hình là với trường CLOB thì dùng cách trên nói nó chả chuyển được. Giải pháp là phải dùng 1 biến varchar2 sử dụng 32766 byte rồi chuyền giá trị vào biến đó thì mới được :v


public static string ToDynamicUpdate(this DataRow dr, string tableName, string primaryKeyField, string primaryKeyValue)
{
var command = @"DECLARE
{3}
BEGIN
{4}
UPDATE {0} SET {1} WHERE {2};
END;";
var valueList = new List<string>();
var declareCommand = string.Empty;
var setCommand = string.Empty;
foreach (var item in dr.Table.Columns.Cast<DataColumn>())
{
if (dr[item] == DBNull.Value)
{
valueList.Add((item.ToString()).ConvertToStandardName() + "= NULL");
}
else
{
if (item.DataType == typeof(int))
{
valueList.Add((item.ToString()).ConvertToStandardName() + "= " + dr[item].ToString());
}
else if (item.DataType == typeof(bool))
{
valueList.Add((item.ToString()).ConvertToStandardName() + "= " + (dr[item] == Boolean.FalseString ? "0" : "1"));
}
else if (item.DataType == typeof(DateTime))
{
valueList.Add((item.ToString()).ConvertToStandardName() + "= " + String.Format("TO_DATE('{0}', 'MM/DD/YYYY HH:MI:SS AM')", dr[item]));
}
else if (item.DataType == typeof(Guid))
{
valueList.Add((item.ToString()).ConvertToStandardName() + "= '" + (dr[item].ToString()).ToUpper() + "'");
}
else if (item.DataType == typeof(byte[]))
{
var hex = Utility.ConvertByteArrayToString(((byte[])dr[item]));
declareCommand += String.Format("p{0} RAW(100000); ", item.ToString());
setCommand += String.Format("p{0} := hextoraw('{1}');", item.ToString(), hex);
valueList.Add(String.Format("{0} = p{1}", (item.ToString()).ConvertToStandardName(), item.ToString()));
}
else
{
var tmp = dr[item].ToString();
if (tmp.Length > 2000)
{
declareCommand += String.Format("p{0} varchar2(32767); ", item.ToString());
setCommand += String.Format("p{0} := {1};", item.ToString(), tmp.ConvertToSafeString());
valueList.Add(String.Format("{0} = p{1}", (item.ToString()).ConvertToStandardName(), item.ToString()));
}
else
{
valueList.Add((item.ToString()).ConvertToStandardName() + "= " + (dr[item].ToString()).ConvertToSafeString());
}
}
}
}
var condition = CreateCondition(primaryKeyField, primaryKeyValue, ChangeType.UPDATED);
return String.Format(command, tableName.ConvertToStandardName(), String.Join(",", valueList), condition, declareCommand, setCommand);
}

view raw

Update

hosted with ❤ by GitHub

Ngoài ra thì với giá trị kiểu byte[] cũng có 1 số vấn đề. Dữ liệu kiểu này bên Oracle sẽ được chuyển thành định dạng BLOB (Binary Large Object). Ý tưởng là sau khi lấy xong mình sẽ convert thành mã HEX và tạo 1 biến kiểu RAW để truyền giá trị vào như thằng CLOB tuy nhiên thì với giá trị vượt quá 32766 byte thì lại lỗi 😐 Hiện tại thì bug này mình chưa xử lý được nên khi nào có cách khắc phục thì mình sẽ cập nhật vào blog 😦

One thought on “Chia sẻ sourcecode chuyển đổi CSDL

Add yours

Leave a comment

Create a free website or blog at WordPress.com.

Up ↑