How to parse a Uuid stored as a char(16)?
I have a Firebird database which stores Uuid values in a char(16) field. In my C# program, I need to retrieve these values in order to use them in subsequent queries. But the values I get back from the database are "garbage". (e.g. ¿ñ)êNµmÏc—ÝX
) I've tried various methods of reading the data as a byte array and using that to create a local Guid
, but that hasn't gotten me anywhere. The closest I've gotten was by using Encoding.ASCII.GetBytes()
which did give me a "valid" guid, however it doesn't match the "real" guids in the database. I know they don't match because (1) the database has a UDF which converts the 'garbage' into a human readable string and (2) when I manually copy that human readable guid into my application, using it to create a new local Guid
, and use THAT value in my queries, I get the correct results. (Whereas guids created from byte arrays do not yield correct results.) I also tried IDataRecord.GetBytes()
but that resulted in an InvalidCastException ("Unable to cast object of type 'System.String' to type 'System.Byte[]'.")
This application uses databases created by another, unrelated product and I have no control over its structure, nor can I use things like stored procedures. I also cannot use the UDF's I mentioned earlier because they are going bye-bye soon. The available UDF's apparently are simple wrappers around UuidToString and UuidFromString. I suppose I could use those functions in my own code, but I'd rather not if there's another way that doesn't involve interop.
Ultimately, the I need the guid to work in a method that looks something like this:
protected DataTable QueryDataTable(string query, string paramName, Guid guid)
{
DataTable table = new DataTable();
IDbCommand command = CreateDbCommand(query);
if (command is FbCommand)
{
FbCommand fbCommand = (FbCommand)command;
fbCommand.Parameters.Add(paramName, FbDbType.Binary).Value =
guid.ToByteArray();
// Also tried passing in the byte[] generated by
// Encoding.Ascii.GetBytes.... didn't work.
//fbCommand.Parameters.Add(paramName, FbDbType.Binary).Value = guid;
FbDataAdapter adapter = new FbDataAdapter(fbCommand);
adapter.Fill(table);
}
return table;
}
Questions:
(1) Why isn't my tactic of using a byte array to create a guid resulting in a "correct" guid?
(2) What other tactics might I use to extract these guids and store them as locally as such?
Thanks!
Edit:
Here's an example of what I've got in front of me. From the database:
Resulting char array from the garbage string:
Using the tactic suggested by @Alexei Levenkov, the resulting guid (1) is very close but not exa开发者_如何学JAVActly correct (2).
(1): fca3120b-511e-4269-b88f-d053a34b3513
fca3120b-5184-4269-b88f-d053a34b3596
SOME garbage strings do turn out correctly, but this is an example of one that does not. This is how I'm implementing Alexei's suggestion, as I didn't see a Select method for strings:
// table is a DataTable
List<byte> bytes = new List<byte>();
string blah = (string)table.Rows[0][0];
foreach (char c in blah.ToCharArray())
{
bytes.Add((byte)c);
}
Guid guid = new Guid(bytes.ToArray());
Char(16) looks like byte representation of the GUID. Try converting each character of the string to a byte array and than creating GUID out of it
new Guid("0000000000000000".Select(c=> (byte)c).ToArray())
For reverse conversion use guid.ToByteArray() and convert it to string with ASCII encoding.
Encoding.ASCII.GetString(Guid.Empty.ToByteArray())
in Firebird 2.5 you have built-in function to make this
This question is pretty old but I just had similar problem, while working with Firebird 2.0 (no built-in UUID generator).
So, the main problem with the code presented above was wrong parameter type (binary). It should either be FbDbType.Char or FbDbType.Guid. Below is a working example.
Guid newGuid = Guid.NewGuid();
Guid retrieved = Guid.Empty;
using (FbConnection conn = new FbConnection(connectionString)) {
conn.Open();
using (FbCommand cmd = conn.CreateCommand()) {
// first create the table for testing
cmd.CommandText = "recreate table GUID_test (guid char(16) character set octets)";
cmd.ExecuteNonQuery();
}
using (FbCommand cmd = conn.CreateCommand()) {
// inserting GUID into db table
cmd.CommandText = "insert into GUID_test values (@guid)";
// classic way, works good
//cmd.Parameters.Add("@guid", FbDbType.Char, 16).Value = newGuid.ToByteArray();
// another way, maybe better readability, but same result
cmd.Parameters.Add("@guid", FbDbType.Guid).Value = newGuid;
cmd.ExecuteNonQuery();
}
using (FbCommand cmd = conn.CreateCommand()) {
// reading GUID back from db
cmd.CommandText = "select first 1 guid from GUID_test";
retrieved = (Guid)cmd.ExecuteScalar();
}
using (FbCommand cmd = conn.CreateCommand()) {
// drop the table, it has no real application
cmd.CommandText = "drop table GUID_test";
cmd.ExecuteNonQuery();
}
}
MessageBox.Show(newGuid.Equals(retrieved).ToString());
When inserting guids values, pass them in your query as
"INSERT INTO MyTable(GuidCol) VALUES (CHAR_TO_UUID(" + yourGuid.ToString() + "))"
when reading, you can correct the badly parsed value from the Firebird .NET Data Provider using the below class:
public class FirebirdCorrectingReader : IDataReader {
private readonly IDataReader _decoratedReader;
public FirebirdCorrectingReader(IDataReader decoratedReader) {
_decoratedReader = decoratedReader;
}
#region DataReader Impl
public void Dispose() {
_decoratedReader.Dispose();
}
public string GetName(int i) {
return _decoratedReader.GetName(i);
}
public string GetDataTypeName(int i) {
return _decoratedReader.GetDataTypeName(i);
}
public Type GetFieldType(int i) {
return _decoratedReader.GetFieldType(i);
}
public object GetValue(int i) {
var result = _decoratedReader.GetValue(i);
if (result is Guid) {
result = CorrectGuid((Guid)result);
}
return result;
}
public int GetValues(object[] values) {
return _decoratedReader.GetValues(values);
}
public int GetOrdinal(string name) {
return _decoratedReader.GetOrdinal(name);
}
public bool GetBoolean(int i) {
return _decoratedReader.GetBoolean(i);
}
public byte GetByte(int i) {
return _decoratedReader.GetByte(i);
}
public long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) {
return _decoratedReader.GetBytes(i, fieldOffset, buffer, bufferoffset, length);
}
public char GetChar(int i) {
return _decoratedReader.GetChar(i);
}
public long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) {
return _decoratedReader.GetChars(i, fieldoffset, buffer, bufferoffset, length);
}
public Guid GetGuid(int i) {
return CorrectGuid(_decoratedReader.GetGuid(i));
}
public short GetInt16(int i) {
return _decoratedReader.GetInt16(i);
}
public int GetInt32(int i) {
return _decoratedReader.GetInt32(i);
}
public long GetInt64(int i) {
return _decoratedReader.GetInt64(i);
}
public float GetFloat(int i) {
return _decoratedReader.GetFloat(i);
}
public double GetDouble(int i) {
return _decoratedReader.GetDouble(i);
}
public string GetString(int i) {
return _decoratedReader.GetString(i);
}
public decimal GetDecimal(int i) {
return _decoratedReader.GetDecimal(i);
}
public DateTime GetDateTime(int i) {
return _decoratedReader.GetDateTime(i);
}
public IDataReader GetData(int i) {
return _decoratedReader.GetData(i);
}
public bool IsDBNull(int i) {
return _decoratedReader.IsDBNull(i);
}
public int FieldCount { get { return _decoratedReader.FieldCount; } }
object IDataRecord.this[int i] {
get { return _decoratedReader[i]; }
}
object IDataRecord.this[string name] {
get {return _decoratedReader[name]; }
}
public void Close() {
_decoratedReader.Close();
}
public DataTable GetSchemaTable() {
return _decoratedReader.GetSchemaTable();
}
public bool NextResult() {
return _decoratedReader.NextResult();
}
public bool Read() {
return _decoratedReader.Read();
}
public int Depth { get { return _decoratedReader.Depth; } }
public bool IsClosed { get { return _decoratedReader.IsClosed; } }
public int RecordsAffected { get { return _decoratedReader.RecordsAffected; } }
#endregion
public static Guid CorrectGuid(Guid badlyParsedGuid) {
var rfc4122bytes = badlyParsedGuid.ToByteArray();
if (BitConverter.IsLittleEndian) {
Array.Reverse(rfc4122bytes, 0, 4);
Array.Reverse(rfc4122bytes, 4, 2);
Array.Reverse(rfc4122bytes, 6, 2);
}
return new Guid(rfc4122bytes);
}
}
Note: Do not use this class when this bug is fixed.
精彩评论