Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
589 views
in Technique[技术] by (71.8m points)

salesforce - Creating non-duplicate records with a generated ID

(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

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I think I solved it. Told you mama ain't raised no... nevermind.

The code now looks at the results of Database.Insert and counts any errors where the field name is WebID_Value__c and the status code indicates a duplicate. If any are found, the function calls itself recursively with the number of errors found and appends the result to the returned list. It'll recurse three levels deep before giving up and throwing an exception (which would only ever happen if there were a huge number of WebID records already). This is bulkified so this whole thing only counts as up to four dml operations even if I'm importing records using DataLoader.

NOT TESTED, PROBABLY BUGGY

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 class WebIDException extends Exception {}

   public static list<WebID__c> CreateWebID(integer count)
   {
      return CreateWebID(count, 0);
   }

   static list<WebID__c> CreateWebID(integer count, integer recursioncount)
   {
      if (recursioncount > 2)
      {
         throw new WebIDException('Couldn't find new ID after 3 attempts.');
      }

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


      Integer FailedDuplicateID = 0;

      for (Database.SaveResult sr : srList)
      {
         if (!sr.issuccess())
         {
            for (Database.Error err : sr.getErrors())
            {
               for (string f : err.fields)
               {
                  if (f == 'WebID_Value__c')
                  {
                     if (err.statuscode == system.statuscode.DUPLICATE_VALUE)
                     {
                        FailedDuplicateID++;
                     }
                  }
               }
            }
         }
      }
  
      if (FailedDuplicateID > 0)
      {
         MainList.addall(CreateWebID(FailedDuplicateID, recursioncount + 1));
      }

      return MainList;      
   }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...