使用Dapper将字符串映射为GUID

25

我正在使用Dapper编写一些负载测试工具,这些工具需要访问PostgreSQL数据库。这个特定版本的PostgreSQL不支持GUID,因此GUID值被存储为32个字符的字符串。将GUID转换为字符串使用someGuid.ToString("N"),将字符串转换回Guid可以使用new Guid(stringValueFromColumn)

我的问题是如何让Dapper读取这些字符串并将它们转换回Guid?

我尝试修改DbType映射,但那并不起作用。


1
尽管您在问题中明确提到了这一点,但我错过了您提到的“数据库不支持原生GUID”的信息。值得注意的是,在支持GUID的数据库中,可以将tSQL(或类似语言)中的UNIQUEIDENTIFIER映射到C#中的Guid(例如 e.g.)。 - dumbledad
6个回答

74

我正在使用MySql,但是由于我将Guid存储为字符串,所以遇到了同样的问题。 要修复映射而无需给列命名别名,我使用了以下方法:

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);
    }
}

在我的 Startup.cs 文件中:

public void ConfigureServices(IServiceCollection services)
    {
        SqlMapper.AddTypeHandler(new MySqlGuidTypeHandler());
        SqlMapper.RemoveTypeMap(typeof(Guid));
        SqlMapper.RemoveTypeMap(typeof(Guid?));
    }

1
不确定为什么这不是最受赞的答案,它完美地工作,并且比其他方法更正式。 - drowhunter
1
太棒了,比我的解决方案更好。 - Jack Pettinger
运行得很好,谢谢!完全是最简单的解决方案! - XLR8
2
有人将此标记为正确答案。该方法还可以正确处理匿名查询参数中的GUID字段映射。RemoveTypeMap调用也很重要。 - Arshia001
2
在MySql中,也可以将列创建为二进制(16),并在此TypeHandler中使用guid.ToByteArray()new Guid((byte[])value) - Hinek
显示剩余3条评论

28

也许最简单的方法(无需等待dapper)是拥有第二个属性:

public Guid Foo {get;set;}

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

在你的查询中,将该列别名为FooString

当然,这引发了一个问题:dapper是否应支持此类私有属性?我想说:可能需要。


嗯,这比在Dapper上瞎搞要干净一些。允许设置私有属性(或字段)将是很好的。 - Marnix van Valen
@Marnix - 是的,我打算添加那些。 - Marc Gravell
@Marnix - 现在一切都存在了,顺便说一下(源码中 - 我还没有卸载NuGet包) - Marc Gravell
1
@MarcGravell,您能否更新您的答案并提供一个私有属性的示例? - Jack Pettinger
@MarcGravell 谢谢。我在使用SQLite时遇到了这个问题,但是我正在尝试使用Contrib库中的Get方法。这意味着我将不得不使用Query方法。 - Christian Findlay

8
看起来PostgreSQL已经有了UUID类型。但是@Cpt.Ohlund的solution在2020年对于MySQL/MariaDB仍然非常好用。
但是这种解决方案本身可能会引起问题。
VARCHAR(36)用于System.Guid时,会抛出以下异常:

System.InvalidCastException:从'System.String'到'System.Guid'的转换无效。

@Cpt.Ohlund的解决方案使其可行。
但是,如果列是CHAR(36),则会自动映射到System.Guid!如果应用了@Cpt.Ohlund的解决方案,则会出现另一个异常:

System.InvalidCastException:无法将类型为'System.Guid'的对象强制转换为类型'System.String'。

该异常是由传递给Parse(object value)的实例System.Guid而不是字符串引起的。
所以,对于MySQL和MariaDB,简单的答案是使用CHAR(36)就可以了。
但是,如果你需要处理任何字符串类型的列,你必须使用改进后的@Cpt.Ohlund的解决方案:
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);
    }
}

并使用SqlMapper.AddTypeHandler()进行注册。


5

这是一个老问题,但我认为它需要更新,因为 Dapper 现在支持私有属性,Marc 在 他的回答 中引用了相关内容。

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

然后在SQL中为您的ID列起一个别名,将其映射到私有属性而不是实际属性:

SELECT UserID AS UserIDString FROM....

你是不是想说 UserIDString = value; 而不是 UserID = value;?否则我认为你陷入了一个无限循环。 - StayOnTarget

2
我拼凑出了一个解决方案。据我所知,没有办法指示 Dapper 为特定类型生成备用绑定代码,因此我修改了 GetClassDeserializer 方法,以便在属性是 GUID 时强制将非装箱类型转换为字符串。 接下来,我重用了为枚举生成构造函数调用的代码。
以下是修改后的代码片段(从 rev. rf6d62f91f31a 的第761行开始):
// 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]

类型强制转换是我故意避免的,您能否在我们的错误跟踪器上发布一个功能请求,以查看是否有必要进行调整,请记住,如果Postgres中的GUID恰好为31个字符长,则会发生惊人的爆炸... - Sam Saffron
@Sam - 嗯,GIGO;就我个人而言,这并不会对我产生太大的影响。而且在L2S中有先例,它可以在某些情况下进行转换(包括通过名称作为字符串来处理枚举)。 - Marc Gravell
@Sam 谢谢,我已经发布了该功能请求。 - Marnix van Valen

0

希望能够帮到你。

在我的查询中,我没有使用别名。我所做的是:

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

    public string Id
    {
        get; set; 
    }
}

并且在您的表中类型为varchar


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接