Inheritance: Microsoft.WindowsAzure.Storage.Table.TableEntity
        /// <summary>
        /// Adds the transaction item to search index. Can also be used for updating existing items by specifying the Transaction.ID value.
        /// Throws exception when item is not valid, resolving customer external ID failed or if search index operation fails.
        /// </summary>
        /// <param name="t"></param>
        public void AddToIndex(Transaction t)
        {
            t.EnsureEntityValidForStorage();

            Guid internalID;
            if (t.InternalRef.HasValue)
            {
                internalID = t.InternalRef.Value;
            }
            else
            {
                Customer c = new CustomerRepository().GetCustomerByExternalID(t.MvaNumber.Value, t.ExternalRef);
                if (c == null)
                {
                    throw new Exception(string.Format("Failed to resolve customer external ID ({0}) to internal", t.ExternalRef));
                }
                internalID = c.InternalID.Value;
            }
            
            var tableEnt = new TransactionTableEntity(t.ID.Value, t.Date.Value, internalID)
            {
                Amount = t.Amount,
                CustomerNumber = t.CustomerNumber,
                AccountID = t.AccountID,
                Description = t.Description,
                MvaNumber = t.MvaNumber,
                ProductID = t.ProductID
            };

            AddToIndex(tableEnt);
        }
        /// <summary>
        /// Inserts or updates (using merge operation) a batch of transaction table entities.
        /// Also generates IDs for items that do not have them and sets Date to DateTime.Now if it is null.
        /// </summary>
        /// <param name="batch">Batch of Transaction items to add/update</param>
        /// <exception cref="System.Exception">Thrown when any of the items is not valid or if adding to either table storage or search index failed.</exception>
        public void AddOrUpdateTransactionBatch(IEnumerable<Transaction> batch)
        {
            var tableEnts = new List<TransactionTableEntity>();

            EnsureTransactionHasCustomerInternalID(batch.ToArray());

            foreach (Transaction t in batch)
            {
                //also ensure that all entities are valid before any of them are committed to table storage or search index
                t.EnsureEntityValidForStorage(true);

                var ent = new TransactionTableEntity(t.ID.Value, t.Date.Value, t.InternalRef.Value)
                {
                    MvaNumber = t.MvaNumber,
                    Amount = t.Amount,
                    Description = t.Description,
                    CustomerNumber = t.CustomerNumber,
                    AccountID = t.AccountID,
                    ProductID = t.ProductID,
                    NotInvoiceable = t.NotInvoiceable
                };

                tableEnts.Add(ent);
            }

            //Need to group entities by partition key because table storage only allows batch operations in the scope of the same partition key
            var entsGroupedByPartition = tableEnts.GroupBy(t => t.PartitionKey);
            foreach (var entGroup in entsGroupedByPartition)
            {
                for (int i = 0; i < entGroup.Count(); i += 100)
                {
                    var tableBatch = new TableBatchOperation();
                    foreach (var ent in entGroup.Skip(i).Take(100))
                    {
                        tableBatch.Add(TableOperation.InsertOrMerge(ent));
                    }

                    TransactionTable.ExecuteBatch(tableBatch);
                }
            }
            
            var dtReIndexReq = DateTime.UtcNow.Date.AddDays(-Constants.DaysToKeepTransactions);
            var entsToAddToIndex = tableEnts.Where(t => t.DateTime >= dtReIndexReq);

            var sRepo = new SearchRepository();
            sRepo.AddToIndex(entsToAddToIndex.ToArray());

            AddReIndexRequestIfNeeded(tableEnts.ToArray());
        }
        /// <summary>
        /// Inserts or updates (using merge operation) the transaction table entity.
        /// </summary>
        /// <param name="ent"></param>
        public void AddOrUpdateTransaction(TransactionTableEntity ent)
        {
            TableOperation op = TableOperation.InsertOrMerge(ent);
            var r = TransactionTable.Execute(op);

            //TODO: if productID was changed, a re-index request is not added for the previous productID value
            AddReIndexRequestIfNeeded(ent);
        }
        /// <summary>
        /// Adds new transaction to table storage or updates an existing item.
        /// Throws exception when item is not valid, resolving customer external ID failed or if table storage operation fails.
        /// </summary>
        /// <param name="t">Transaction item to add/update</param>
        public void AddOrUpdateTransaction(Transaction t)
        {
            EnsureTransactionHasCustomerInternalID(t);
            t.EnsureEntityValidForStorage();

            var ent = new TransactionTableEntity(t.ID.Value, t.Date.Value, t.InternalRef.Value)
            {
                MvaNumber = t.MvaNumber,
                Amount = t.Amount,
                Description = t.Description,
                CustomerNumber = t.CustomerNumber,
                AccountID = t.AccountID,
                ProductID = t.ProductID,
                NotInvoiceable = t.NotInvoiceable,
                ExternalRecordID = t.ExternalRecordID,
            };

            AddOrUpdateTransaction(ent);
        }