(Disclaimer: New to APEX and database programming, not new to coding)
I'm trying to build code which will be called from a trigger which creates a record in a particular table (I'll call this a WebID record) any time time one of a number of other objects is created. This is pretty straightforward but the constraints are that the WebID record has a text field (string WebID_Value__c;
) to be used as an ID which is required to be unique (and is indexed but that probably not relevant here). This is NOT an autonumber field as the ID must also NOT be sequential.
Because triggers use bulkified code, and there's a really good chance more than a single record will be created at a time, I wrote a function (list<WebID__c> CreateWebID(integer count)
) which creates n WebID records and attempts to insert them into the database. After successful input of all n records, it returns them.
So far, so good.
The problem I'm having, is what is the best way to catch the very uncommon edge-case of the same random ID being generated twice (ever)? I figure I can use Database.insert([list], false) and look at the Database.SaveResult[] for any errors but then we run into the thing I've been warned never to do: DO NOT PUT DML IN A LOOP.
(The ID I'm generating has 281,474,976,710,656 possible permutations but I'm prototyping a system which could potentially be accessed by a large number of people all creating records. The final design will probably have more than 8 digits for the ID. That said, I want to code for this specific edge case, just in case.)
What should be my strategy here?
tl;dr: I want to create and insert a list of records with generated IDs which are guaranteed to be unique (even against previous runs of the function) and have the function ALWAYS return the number of entries requested.
Here's my code. (Again, new to APEX and database coding so point out other issues if you find them as well.)
public class WebIDHelper {
public static final string[] ValidChars = new string[] {'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_'};
public static final integer IDLENGTH = 8;
public static final integer ValidCharsLength = ValidChars.size();
public static string GenerateID()
{
//generated an 8 character long ID which looks to all the world like a
//base64 encoded string (we don't care about the value being stored, just
//that it's unique), replacing the last two characters with - and _ since
//this will be used in a URL at some point.
string ID = '';
for (integer i = 0; i < IDLENGTH; i++)
{
ID = ID + ValidChars[(integer) (math.random() * ( ValidCharsLength ))];
}
return ID;
}
public static list<WebID__c> CreateWebID(integer count)
{
//generate a number of unique WebID instances (defined by count), insert
//them, return them. If a non-unique value for WebID_Value__c is found,
//the record being entered gets a new value set for WebID_Value__c.
list<WebID__c> MainList = new list<WebID__C>();
//create the WebID instances
for (integer i = 0; i < count; i++)
{
WebID__c item = new WebID__c(WebID_Value__c = GenerateID());
MainList.add(item);
}
//add to database
Database.SaveResult[] srList = Database.insert(MainList, false);
//to-do: StackExchange question: do we look through srList in a loop,
//generate new IDs, and try to insert again, in a loop? if not, what's
//the right way to do this?
return MainList;
}
}
question from:
https://stackoverflow.com/questions/65877298/creating-non-duplicate-records-with-a-generated-id