November Happy Hour will be moved to Thursday December 5th.

Delay in finding updated block content

Vote:
 

Hello,

We have a requirement to ensure that a property (Callsign) on a block is unique in our site.

We are currently inherting IValidate to allow us to validate the block and within the Validate method we use the method below to get all blocks of the same type so we can iterate over them and check the values accordingly to find if it already exists.

This works for the most part but if I create/edit two blocks in quick succession then the index doesnt appear to keep up and allows duplicates. If I wait around five minutes between edits all appears to work OK.

  1. Is it reasonable to expect the index to be realtime? 
  2. Can I force a block to update the index when publishing to keep it in sync?
  3. Is there a better way to acheive unique values in EPiServer?

Snippet

public List<>ILocalTeamMemberBlock> GetMatchingByCallSign(ILocalTeamMemberBlock block)
        {
            var search = SearchClient.Instance.Search<>ILocalTeamMemberBlock>()
                .For(block.CallSign).InFields(x => x.CallSign)
                .Take(1000);
            try
            {
                var result = search.GetContentResult().ToList();
                if (result.IsNullOrEmpty())
                {
                    IsError = true;
                    return new List<>ILocalTeamMemberBlock>();
                }
                else
                {
                    return result.ToList();
                }
            }
            catch (Exception ex)
            {
                IsError = true;
                return new List<>ILocalTeamMemberBlock>();
            }
        }
#172495
Edited, Dec 05, 2016 13:05
Vote:
 

GUID is one way if you are lazy...

Implementing a separate thread-safe service that creates call signs is another (auto-increment or similar). Probably want to store either in DDS or SQL database...I use SQL database for another project. 

Something similar to this 

  public class IdService : IIdService
    {
        public const string DefaultSequenceId = "GLOBAL";
        public const int DefaultBatchSize = 1000;

        private static readonly ConcurrentDictionary<string, SequenceInfo> _sequenceInfos =
            new ConcurrentDictionary<string, SequenceInfo>();

        private IConfigService _configService { get; set; }

        public IdService(IConfigService configService)
        {
            _configService = configService;
        }

        public long NewId(string sequenceId = null, int? batchSize = null)
        {
            sequenceId = sequenceId ?? DefaultSequenceId;
            batchSize = batchSize ?? DefaultBatchSize;
            var sequenceInfo = _sequenceInfos.GetOrAdd(sequenceId, key => new SequenceInfo());

            if (batchSize < 1)
            {
                throw new Exception($"The batch size was set to {batchSize}, but it must be greater than 0.");
            }
            lock (sequenceInfo.Lock)
            {
                if (sequenceInfo.Next > sequenceInfo.Until || sequenceInfo.Next == 0)
                {
                    //Skapa connection mot databasen
                    using (var con = new SqlConnection(_configService.ConnectionString))
                    {
                        //Öppna databasen
                        con.Open();

                        SqlTransaction tx = null;
                        try
                        {
                            //Starta transaction
                            tx = con.BeginTransaction(IsolationLevel.Serializable);

                            //Läs nästa värde
                            using (var cmd = con.CreateCommand())
                            {
                                cmd.Transaction = tx;
                                cmd.CommandText = "select [Next] from [Sequence] where [SequenceId] = @sequenceId";
                                cmd.Parameters.AddWithValue("@sequenceId", sequenceId);
                                sequenceInfo.Next = (long) cmd.ExecuteScalar();
                            }

                            //Tills ...
                            sequenceInfo.Until = sequenceInfo.Next + batchSize.Value - 1;

                            //Uppdatera sekvensen i databasen
                            using (var cmd = con.CreateCommand())
                            {
                                cmd.Transaction = tx;
                                cmd.CommandText =
                                    "update [Sequence] set [Next] = @next where [SequenceId] = @sequenceId";
                                cmd.Parameters.AddWithValue("@next", sequenceInfo.Until + 1);
                                cmd.Parameters.AddWithValue("@sequenceId", sequenceId);
                                cmd.ExecuteNonQuery();
                            }

                            tx.Commit();
                        }
                        catch (Exception exc)
                        {
                            tx?.Rollback();
                            throw new Exception($"Could not generate id from {sequenceId}", exc);
                        }

                        con.Close();
                    }
                }
                return sequenceInfo.Next++;
            }
        }

        private class SequenceInfo
        {
            public long Next { get; set; }
            public long Until { get; set; }
            public object Lock { get; }

            public SequenceInfo()
            {
                Lock = new object();
            }
        }
    }
#172503
Dec 05, 2016 14:32
Vote:
 

Hi Daniel,

Thanks for the reply.

In our scenario, call-sign is content which is editable. We expect at least 5000 of this particular block to be created so we need to be able to inform editors if the call-sign exists as they may have trouble remembering/finding the block otherwise.

Regards

Ben

#172505
Dec 05, 2016 14:50
Vote:
 

Store them in a static dictionary with call sign as key and do lookup vs that instead (in a threadsafe manner with locks)?

I'm using that to match users on personal identity number / emails for 100 000s of users. Takes zero time :)

Initialize dictionary on site startup using Episerver find to get all existing keys. Add new key to dictionary in the onpublishing event / validation...

#172506
Edited, Dec 05, 2016 16:16
* You are NOT allowed to include any hyperlinks in the post because your account hasn't associated to your company. User profile should be updated.