/// <summary> /// Updates and immediately saves the UserDefault for the 'Last Partner Used'. /// </summary> /// <param name="APartnerKey">Partner Key of the last used Partner</param> /// <param name="ALastPartnerUse">Specifies which 'Last Partner Used' setting should be /// updated /// </param> /// <param name="APartnerClass">Partner Class of the Partner (default=<see cref="TPartnerClass.FAMILY"/>). /// Only relevant if <paramref name="ALastPartnerUse"/> is <see cref="TLastPartnerUse.lpuMailroomPartner"/>.</param> /// <returns>void</returns> public static void SetLastPartnerWorkedWith(Int64 APartnerKey, TLastPartnerUse ALastPartnerUse, TPartnerClass APartnerClass = TPartnerClass.FAMILY) { switch (ALastPartnerUse) { case TLastPartnerUse.lpuMailroomPartner: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPARTNERMAILROOM, (object)APartnerKey); if (APartnerClass == TPartnerClass.PERSON) { TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONPERSONNEL, (object)APartnerKey); } break; case TLastPartnerUse.lpuPersonnelPerson: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONPERSONNEL, (object)APartnerKey); break; case TLastPartnerUse.lpuPersonnelUnit: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTUNITPERSONNEL, (object)APartnerKey); break; case TLastPartnerUse.lpuConferencePerson: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONCONFERENCE, (object)APartnerKey); // if partner is person then also set this for personnel module TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONPERSONNEL, (object)APartnerKey); break; } }
public static bool AddRecentlyUsedPartner(Int64 APartnerKey, TPartnerClass APartnerClass, Boolean ANewPartner, TLastPartnerUse ALastPartnerUse) { bool ResultValue = false; ResultValue = Server.MPartner.Partner.TRecentPartnersHandling.AddRecentlyUsedPartner (APartnerKey, APartnerClass, ANewPartner, ALastPartnerUse); return ResultValue; }
/// <summary> /// Updates and immediately saves the UserDefault for the 'Last Partner Used'. /// </summary> /// <param name="APartnerKey">Partner Key of the last used Partner</param> /// <param name="ALastPartnerUse">Specifies which 'Last Partner Used' setting should be /// updated /// </param> /// <returns>void</returns> public static void SetLastPartnerWorkedWith(Int64 APartnerKey, TLastPartnerUse ALastPartnerUse) { String PartnerName; TPartnerClass PartnerClass; /* * Store the fact that this Partner is the 'Last Partner' that was worked with */ switch (ALastPartnerUse) { case TLastPartnerUse.lpuMailroomPartner: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPARTNERMAILROOM, (object)APartnerKey); // if partner is person then also set this for personnel module TServerLookup.TMPartner.GetPartnerShortName(APartnerKey, out PartnerName, out PartnerClass); if (PartnerClass == TPartnerClass.PERSON) { TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONPERSONNEL, (object)APartnerKey); } break; case TLastPartnerUse.lpuPersonnelPerson: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONPERSONNEL, (object)APartnerKey); break; case TLastPartnerUse.lpuPersonnelUnit: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTUNITPERSONNEL, (object)APartnerKey); break; case TLastPartnerUse.lpuConferencePerson: TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONCONFERENCE, (object)APartnerKey); // if partner is person then also set this for personnel module TUserDefaults.SetDefault(TUserDefaults.USERDEFAULT_LASTPERSONPERSONNEL, (object)APartnerKey); break; } }
/// <summary> /// Add Key of a recently used partner to the table that holds the keys of the /// recent partners. /// </summary> /// <remarks> /// IMPORTANT: This Method has a built-in recovery mechanism that allows it to /// recover from an Exception that can be thrown due to the 'Predicate Locking' implementation /// in PostgreSQL. If the Method makes attempts to recover from that, it /// ROLLS BACK THE CURRENT DB TRANSACTION so that the next SQL Command can succeed. The Method will make /// MAX_SUBMIT_RETRIES attempts. If the Method was called while a DB Transaction was already running /// then this Method will have ROLLED BACK that DB Transaction in such a case and the Method /// will return false!!! If the caller cares about that (i.e. if it was writing to the DB in that /// DB Transaction) then the caller needs to re-do the writing to the DB if this Method returns 'false' /// as that DB Transaction was just rolled back! /// </remarks> /// <param name="APartnerKey">Key of the partner that was recently used</param> /// <param name="APartnerClass"></param> /// <param name="ANewPartner">Indicate if the partner is a new partner</param> /// <param name="ALastPartnerUse">Where is the partner used?</param> /// <returns>returns true if handling was successful /// </returns> public static bool AddRecentlyUsedPartner(Int64 APartnerKey, TPartnerClass APartnerClass, bool ANewPartner, TLastPartnerUse ALastPartnerUse) { const int MAX_SUBMIT_RETRIES = 5; Boolean ReturnValue; TDBTransaction ReadAndWriteTransaction; Boolean NewTransaction = false; PPartnerTable PartnerDT; PRecentPartnersTable RecentPartnersDT; DataRowView RecentPartnersRowView; PRecentPartnersRow RecentPartnersRow; PPartnerRow PartnerRow; DataView RecentPartnersDV; string PartnerClassString; int Counter; int ClassCounter; int NumberOfRecentPartners; StringCollection FieldList; int SubmitRetries = 0; bool SubmitSuccessful = false; // initialize result ReturnValue = true; try { ReadAndWriteTransaction = DBAccess.GDBAccessObj.GetNewOrExistingTransaction(IsolationLevel.ReadCommitted, TEnforceIsolationLevel.eilMinimum, out NewTransaction); if (!ANewPartner) { PartnerDT = PPartnerAccess.LoadByPrimaryKey(APartnerKey, ReadAndWriteTransaction); if (PartnerDT.Rows.Count == 0) { // Somehow the partner does not exist, don't add this and return here already ... return false; } } // At this point the partner is either new or it is not new but in existing in the db // Now get the class of the recently used partner. PartnerClassString = SharedTypes.PartnerClassEnumToString(APartnerClass); RecentPartnersDT = PRecentPartnersAccess.LoadViaSUser(UserInfo.GUserInfo.UserID, ReadAndWriteTransaction); // Check if the recently used partner already exists for the current user. // Add it if not, otherwise just change the timestamp. RecentPartnersRow = (PRecentPartnersRow)RecentPartnersDT.Rows.Find(new System.Object[] { UserInfo.GUserInfo.UserID, APartnerKey }); if (RecentPartnersRow == null) { RecentPartnersRow = RecentPartnersDT.NewRowTyped(true); RecentPartnersRow.UserId = UserInfo.GUserInfo.UserID; RecentPartnersRow.PartnerKey = APartnerKey; RecentPartnersDT.Rows.Add(RecentPartnersRow); } // Change the Timestamp RecentPartnersRow.whenDate = DateTime.Now; RecentPartnersRow.whenTime = Conversions.DateTimeToInt32Time(DateTime.Now); RecentPartnersDV = new DataView(RecentPartnersDT); RecentPartnersDV.Sort = PRecentPartnersTable.GetwhenDateDBName() + " ASC, " + PRecentPartnersTable.GetwhenTimeDBName() + " ASC"; FieldList = new StringCollection(); FieldList.Add(PPartnerTable.GetPartnerKeyDBName()); FieldList.Add(PPartnerTable.GetPartnerClassDBName()); PartnerDT = PPartnerAccess.LoadViaSUserPRecentPartners(UserInfo.GUserInfo.UserID, FieldList, ReadAndWriteTransaction, null, 0, 0); // Get the number of recent partners that the user has set, if not found // take 10 as default value. NumberOfRecentPartners = TUserDefaults.GetInt16Default(MSysManConstants.USERDEFAULT_NUMBEROFRECENTPARTNERS, 10); ClassCounter = 0; for (Counter = RecentPartnersDV.Count - 1; Counter >= 0; Counter -= 1) { RecentPartnersRowView = RecentPartnersDV[Counter]; RecentPartnersRow = (PRecentPartnersRow)RecentPartnersRowView.Row; if (APartnerKey == RecentPartnersRow.PartnerKey) { // we don't need to check the added partner (was done already) ClassCounter = ClassCounter + 1; } else { // For each recent partner we need to find out if the class matches the // class of the added recent partner. Look in the table that we loaded // earlier on. PartnerRow = (PPartnerRow)PartnerDT.Rows.Find(RecentPartnersRow.PartnerKey); if (PartnerRow == null) { // Partner not found. Should not happen so we delete this record to prevent // future problems. RecentPartnersRow.Delete(); continue; } // If the recent partners record is of a different class then don't do // anything with this record. if (PartnerRow.PartnerClass != PartnerClassString) { continue; } else { ClassCounter = ClassCounter + 1; } } if (ClassCounter > NumberOfRecentPartners) { // Only keep the number of records of a certain class that the user // wants to keep (find in User Defaults, otherwise use 10) RecentPartnersRow.Delete(); } } while ((SubmitRetries < MAX_SUBMIT_RETRIES) && (!SubmitSuccessful)) { try { // now submit the changes to the database PRecentPartnersAccess.SubmitChanges(RecentPartnersDT, ReadAndWriteTransaction); SubmitSuccessful = true; } catch (Npgsql.NpgsqlException Exc) { // Check if we ran into the error 'could not serialize access due to read/write dependencies among transactions' // which has error code 40001. This is due to the 'Predicate Locking' implementation in PostgreSQL. // If so, retry, as this should overcome the error condition! // (See http://stackoverflow.com/questions/12837708/predicate-locking-in-postgresql-9-2-1-with-serializable-isolation) // That error has been encountered with the 'PetraMultiStart' test program where many clients may open a Partner Edit // at nearly the same time, but it could be encountered in a 'real office scenario' as well when two users open a // Partner Edit screen at nearly the same time. if (String.Compare(Exc.Code, "40001") == 0) { // TLogging.LogAtLevel(0, "TRecentPartnersHandling.AddRecentlyUsedPartner: We need to retry issuing SubmitChanges as the RDBMS suggested to do that when 'Predicate Locking' failed (PostgreSQL Error Code 40001)."); // TLogging.LogAtLevel(0, "TRecentPartnersHandling.AddRecentlyUsedPartner: rolling back DB Transaction to allow further SQL Commands to succeed"); DBAccess.GDBAccessObj.RollbackTransaction(); // TLogging.LogAtLevel(0, "TRecentPartnersHandling.AddRecentlyUsedPartner: rolled back DB Transaction, now starting a new DB Transaction"); // Let the caller of this Method know that the Method has rolled back the DB Transaction that was started outside of this Method. // If the caller cares about that (i.e. if it was writing to the DB in that DB Transaction) then the caller needs to re-do the // writing to the DB if this Method returns 'false' as that DB Transaction was just rolled back! if (!NewTransaction) { ReturnValue = false; } SubmitRetries++; ReadAndWriteTransaction = DBAccess.GDBAccessObj.BeginTransaction(IsolationLevel.ReadCommitted, 2); // TLogging.LogAtLevel(0, "TRecentPartnersHandling.AddRecentlyUsedPartner: successfully started a new DB Transaction, now retrying SubmitChanges (Retry attempt number: " + SubmitRetries.ToString() + ")..."); // Now let the while statement retry the submitting! } else { throw; } } catch (Exception) { throw; } finally { if ((SubmitSuccessful) && (SubmitRetries > 0)) { TLogging.LogAtLevel(0, "TRecentPartnersHandling.AddRecentlyUsedPartner: SubmitChanges was successful after " + SubmitRetries.ToString() + " retry attempts (we recovered from the 'Predicate Locking' error that PostgreSQL issued [PostgreSQL Error Code 40001])!"); } else if (SubmitRetries > MAX_SUBMIT_RETRIES) { TLogging.LogAtLevel(0, "TRecentPartnersHandling.AddRecentlyUsedPartner: SubmitChanges was NOT successful after " + SubmitRetries.ToString() + " retry attempts (we FAILED TO RECOVER from the 'Predicate Locking' error that PostgreSQL issued [PostgreSQL Error Code 40001])!"); } } } } finally { if (NewTransaction) { DBAccess.GDBAccessObj.CommitTransaction(); TLogging.LogAtLevel(0, "TRecentPartnersHandling.AddRecentlyUsedPartner: committed own transaction."); } } return ReturnValue; }