【问题标题】:Auto-generate Unique ID within the constructor在构造函数中自动生成唯一 ID
【发布时间】:2014-02-21 09:48:45
【问题描述】:

使用 VB.net,创建一个名为人员的新类,具有三个属性: Name , LastName, ID - 应该适合用作数据库中的主键。

提供一个类构造函数来填充 Name 和 LastName。 ID 应在构造函数中自动生成,不应传入。

我知道如何创建类、属性和构造函数,我只需要知道如何在构造函数中自动生成 ID 字段。是否有可能做到这一点?

我通常做的是将数据库中的 id 字段设置为身份字段和主键,以便它自动插入下一个可用的 id,或者在我的应用程序中,我从数据库中读取最后一个 ID 并向其中添加一个。但我需要知道如何在构造函数中自动生成 ID 字段。

【问题讨论】:

  • 构造函数中生成的ID是否只需要在内存中的所有对象中唯一,还是需要在已经存储到DB的所有对象中唯一?
  • 它需要在数据库中是唯一的,因为问题说它应该适合用作数据库中的主键。
  • 在这种情况下,构造函数无法生成唯一 ID except 作为 GUID 或类似的东西。在之后将它插入数据库之前,无法知道自增 ID 是什么。
  • 没错,你是对的。它的常识,你需要阅读数据库才能知道最后插入的 ID 是什么。
  • @Diana 是的,即使在这种情况下(从数据库中读取),您也必须考虑并发性。要正确完成这一切并非易事(除非您使用的东西是 - 应该是 - 本身独一无二的东西,比如 GUID)。

标签: vb.net constructor


【解决方案1】:

指南

如果您对 ID 类型没有任何限制,您可以使用 GUID:

Dim id As Guid = Guid.NewGuid()

您甚至可以将其保留为字符串:

Dim id As String = Guid.NewGuid().ToString("N")

这应该被授予在不同机器上是唯一的(以满足您的要求,即它必须适合用作数据库中的主键)。另见this post

时间戳

更糟糕的情况是,如果您没有如此严格的要求(跨网络的唯一性),您可以使用时间戳。当然,在这种情况下,你要考虑更多的问题:

  • 法定时间:时间每年往返两次。
  • Zones:如果用户在伦敦输入数据然后搬到纽约会怎样?
  • 并发:您必须假设没有其他人将记录添加到您的数据库中(如果他们使用不同的技术,您可能会发生冲突)。如果执行是并发的(您的程序的多个实例一起运行),您也不能应用此功能。
  • 计时器粒度:系统日期的粒度较粗:如果您在短时间内构造了许多对象,那么您可能会有重复的 ID。 this post 中的解决方法。

计数器

如果所有这些条件都满足:

  • 您的应用程序的多个实例不会并行运行。
  • 您正在一台计算机上工作(不是网络)。
  • 每次应用程序启动时数据库都是空的。

您可以使用Shared 计数器在每次构造新对象时递增。如果系统计时器粒度不是问题(请参阅有关时间戳的段落),您可以使用system up time 作为 ID。由于粒度限制,即使同一应用程序的多个实例同时运行,它也应该可以工作。

如果您使用Shared 字段,您必须处理同步问题。正如this comment 中所建议的,您可以使用SyncLock。作为替代方案,您可以使用Interlocked.Increment 操作。

哈希码

如果一个计数器的所有条件都满足并且这个条件也满足:

  • 您的应用程序是 32 位的。
  • 您的对象不是ValueType,它不会覆盖GetHashCode() 方法。

您可以使用哈希码(通过GetHashCode() 获得)因为(来自MSDN):

换句话说,ReferenceEquals 方法返回 true 的两个对象具有相同的哈希码。

因为Object.ReferenceEquals() 仅在比较同一个实例时才返回true,那么每个实例都会有一个唯一的哈希码(因为在 32 位应用程序中,哈希码是对象引用本身)。请注意,这是一个实现细节,可能会发生变化。

随机数

我知道这可能会让某些人感到震惊,但一个好的 64 位值随机数生成器发生冲突的概率非常低。我重复非常非常低概率(有关更多数学细节,请参阅this article)。只是不要为此使用System.Random

根据您使用的种子,您也可以在网络场景中生成随机数(不要忘记 - 需要引用 - 一个本地网络协议的早期草案提出了一个 32 位随机数作为地址,它已经由于用户反馈不好而改变了,但这并不意味着它不能工作)。

【讨论】:

  • GetHashCode 不保证是唯一的。它只是为了均匀分布。来自MSDN“不要使用这个方法的默认实现作为唯一的对象标识符”
  • +1,但还有一件事...值得一提的是,如果您执行 Shared 计数器,则需要将其包装在 SyncLock 块中以使其成为线程安全的--如果这是一个问题。
【解决方案2】:

您想要一个永远不会重复的数字!那么为什么不直接使用呢?

    Dim dateAndTime As Date
    dateAndTime = Now
    TextBoxPID.Text = Format(dateAndTime, "yyyyMMddHHmmss").ToString

除非您的数据输入将在几毫秒内完成,否则此解决方案非常有效。如果您遇到毫秒问题,只需在字符串末尾添加一个计数器。

    counter +=1
    TextBoxPID.Text = Format(dateAndTime, "yyyyMMddHHmmss").ToString & counter.ToString

如果您在网络上工作并且有几个人进行数据输入,则将他们的员工 ID 添加到字符串中。每个问题都有简单的解决方案,但在大多数情况下(如果不是所有情况),这将毫无问题地工作。

【讨论】:

  • 如果您担心时间变化,除非您在凌晨 2 点进行数据输入,此时它在秋季倒退,否则不会有任何问题。大多数公司不喜欢让员工熬夜。
  • 谢谢。该解决方案适合我。
【解决方案3】:

根据 SNTP 服务器生成随机唯一用户 ID。

我的要求有点不同;但是我需要生成一个随机且唯一的用户 ID,即 10 个数字,花费相当多的时间找不到合适的解决方案。 所以我最终得到了以下功能;其独特而随机的结果。

根据在一台测试机器上的一个应用程序瞬间,它是增量的唯一结果;因为用户将在非预选时间戳上一次生成十位数字。除了使用随机的 alpha 前缀;我希望这个功能可以提供一个解决方案:

Imports System.Globalization
Imports System.Net

Public Class GetNetTime
    Public Shared Function GetUTC()
        ' connect to google servers
        ' you could use some SNTP time servers but can't be sure port will be open
        ' or you could just ping your own webserver
        Dim myNetRequest As WebRequest = HttpWebRequest.Create("http://www.example.com")
        ' read response header from connection
        Dim response = myNetRequest.GetResponse()
        ' read date/time header
        ' assume its UTC format
        Dim GlobalUTC As String = response.Headers("date").ToString
        ' convert string to datetime object
        Dim parsedDateTime As DateTime = DateTime.Parse(GlobalUTC)
        ' get UNIX time stamp
        Dim unixTime = (parsedDateTime - New DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds
        Return unixTime 
    End Function
End Class

要测试输出,您可以添加:

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim utc As String = GetNetTime.GetUTC
        ' add random alpha prefix to UNIX time stamp
        Dim sPrefix As String = ""
        Dim rdm As New Random()
        For i As Integer = 1 To 3 ' if you need more than 3 alpah random charachters adjust i length
            sPrefix &= ChrW(rdm.Next(65, 90))
        Next
        MsgBox(sPrefix & utc) ' OR MsgBox("ID" & sPrefix & utc)
        ' code here to use result
    End Sub

我发现这个解决方案比查询 SQL 表并读取最后一条记录 id 并进行增量更有用。

注意事项

  • 请不要介意冗长的回答;当我试图评论代码时 详细解释场景。
  • 我认为这对生成有好处 在多个工作站上运行的应用程序的用户 ID。
  • 请不要将函数放入 for ... 循环或耗尽运行它。

输出示例:

GYK1501270543

VWT1501270606

WRH1501270634

SKI1501270648

QXL1501270716

【讨论】:

    【解决方案4】:

    这也基于上面的@wpcoder 答案,但这是一种基本形式,这对我有用

        Public Function UIDGen(ByRef f As String) As String
            Dim currentTime As DateTime = DateTime.UtcNow
            Dim StringTime As String = currentTime.ToString
            Dim parsedDateTime As DateTime = DateTime.Parse(StringTime)
            Dim unixTime = (parsedDateTime - New DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds
            Dim utcString As String = unixTime.ToString
            Dim sPrefix As String = ""
            Dim rdm As New Random()
    
            For i As Integer = 1 To 3 ' 3 Letters enough ?
                sPrefix &= ChrW(rdm.Next(65, 90))
            Next
            f = (sPrefix & utcString)
            Return f
        End Function
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-03-08
      • 2016-12-07
      • 2011-08-21
      • 2019-09-25
      • 1970-01-01
      • 1970-01-01
      • 2012-01-07
      相关资源
      最近更新 更多