24

I'm using Dapper to hammer out some load testing tools that need to access a PostgreSQL database. This particular version of PostgreSQL does not support GUIDs natively, so GUID values are stored as 32 character strings. The values are converted to strings using someGuid.ToString("N"), conversion back to Guid can be done using new Guid(stringValueFromColumn).

My question is how do I get Dapper to read the strings and convert them back to Guids?

I tried modifying the DbType mapping but that doesn't work.

Marnix van Valen
  • 13,265
  • 4
  • 47
  • 74
  • 1
    Though you mention this prominently in your question, I missed your mention that you are using a database that "does not support GUIDs natively". It is worth note that in databases that do, one can map UNIQUEIDENTIFIER in tSQL (or similar) to Guid in C# ([e.g.](https://github.com/StackExchange/dapper-dot-net/issues/447)) – dumbledad Feb 01 '16 at 14:17

6 Answers6

74

I'm using MySql but it has the same problem since I store the Guid as a string. To fix the mapping without having to alias the column i used the following:

public class MySqlGuidTypeHandler : SqlMapper.TypeHandler<Guid>
{
    public override void SetValue(IDbDataParameter parameter, Guid guid)
    {
        parameter.Value = guid.ToString();
    }

    public override Guid Parse(object value)
    {
        return new Guid((string)value);
    }
}

And in my Startup.cs:

public void ConfigureServices(IServiceCollection services)
    {
        SqlMapper.AddTypeHandler(new MySqlGuidTypeHandler());
        SqlMapper.RemoveTypeMap(typeof(Guid));
        SqlMapper.RemoveTypeMap(typeof(Guid?));
    }
StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Cpt.Ohlund
  • 2,589
  • 1
  • 20
  • 15
28

Perhaps the simplest way to do this (without waiting on dapper) is to have a second property:

public Guid Foo {get;set;}

public string FooString {
    get { return Foo.ToString("N"); }
    set { Foo = new Guid(value); }
}

And in your query, alias the column as FooString.

Of course, then this prompts the question: should dapper support private properties for this type of thing? To which I say: probably.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
8

It looks like there is UUID type in PostgreSQL already. But @Cpt.Ohlund's solution is still great for MySQL/MariaDB in 2020.

But the solution might cause problems itself.

When VARCHAR(36) is used for System.Guid the following exception is thrown:

System.InvalidCastException: Invalid cast from 'System.String' to 'System.Guid'.

@Cpt.Ohlund's solution makes it work.

But if the column is CHAR(36) then it is mapped to System.Guid automatically! And if @Cpt.Ohlund's solution is applied there will be another exception:

System.InvalidCastException: Unable to cast object of type 'System.Guid' to type 'System.String'.

The exception is caused by an instance System.Guid passed to Parse(object value) instead of a string.


So the simple answer is to use CHAR(36) in MySQL and MariaDB and it will just work.

But if you need to handle any string type of the column you have to use an improved @Cpt.Ohlund's solution:

public class MySqlGuidTypeHandler : SqlMapper.TypeHandler<Guid>
{
    public override void SetValue(IDbDataParameter parameter, Guid guid)
    {
        parameter.Value = guid.ToString();
    }

    public override Guid Parse(object value)
    {
        // Dapper may pass a Guid instead of a string
        if (value is Guid)
            return (Guid)value;

        return new Guid((string)value);
    }
}

And register it using SqlMapper.AddTypeHandler().

StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Supa
  • 466
  • 6
  • 7
5

This is an old question but I feel it needs updating as Dapper now supports private properties, which Marc referenced to in his answer.

private String UserIDString { get; set; }
public Guid UserID
{
    get
    {
        return new Guid(UserIDString);
    }
    private set
    {
        UserID = value;
    }
}

Then in SQL give your ID column an alias to map it to the private property and not the actual property:

SELECT UserID AS UserIDString FROM....
StayOnTarget
  • 11,743
  • 10
  • 52
  • 81
Jack Pettinger
  • 2,715
  • 1
  • 23
  • 37
2

I hacked a solution together. As far as I can tell, there is no way to instruct Dapper to generate alternate binding code for a particular type so I modified the GetClassDeserializer method to force the unbox type to string if the property is a guid. Next I re-used the code that generates a constructor call for enums.

Here's the modified code snippet (starting at line 761 of rev. rf6d62f91f31a) :

// unbox nullable enums as the primitive, i.e. byte etc
  var nullUnderlyingType = Nullable.GetUnderlyingType( item.Info.Type );
  var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : item.Info.Type;
  if( unboxType == typeof(Guid))
  {
    unboxType = typeof (string);
  }
  il.Emit( OpCodes.Unbox_Any, unboxType ); // stack is now [target][target][typed-value]
  if (  ( item.Info.Type == typeof( Guid ) && unboxType == typeof( string ) ) 
        || ( nullUnderlyingType != null && nullUnderlyingType.IsEnum ) )
  {
    il.Emit( OpCodes.Newobj, item.Info.Type.GetConstructor( new[] { nullUnderlyingType ?? unboxType} ) );
  }

  il.Emit( OpCodes.Callvirt, item.Info.Setter ); // stack is now [target]
Marnix van Valen
  • 13,265
  • 4
  • 47
  • 74
  • type coercion is something I deliberately avoided, would you mind posting a feature request on our bug tracker to see if there it makes sense to accommodate, keep in mind there will be spectacular explosion if the guid in postgres happens to be 31 chars long ... – Sam Saffron May 07 '11 at 11:14
  • @Sam - meh, GIGO; personally this wouldn't bother me hugely. And there is prior in that L2S does *some* conversions here (including enums via name as string). – Marc Gravell May 07 '11 at 11:43
  • @Sam Thanks, I've posted the feature request. – Marnix van Valen May 09 '11 at 10:51
0

I hope that can help.

I didn't have to use alias in my query. What I did:

public abstract class Entity
{
    protected Entity()
    {
        Id = Guid.NewGuid().ToString();
    }

    public string Id
    {
        get; set; 
    }
}

And in your table the type as varchar

Leonardo Lima
  • 586
  • 1
  • 6
  • 20