public UsageAggregatePerUserByBillingCycle GetUsageAggregatePerUserByBillingCycle(Guid orderId, Guid billingCycleId, string resourceId, string userId) { UsageAggregatePerUserByBillingCycle aggregate = null; string partitionKey = string.Format("{0}-{1}", orderId.ToString(), billingCycleId.ToString()); string rowKey = string.Format("{0}-{1}", resourceId, userId); try { aggregate = (from e in this.aggregateTableServiceContext.CreateQuery <UsageAggregatePerUserByBillingCycle>(AggregatePerUserUsageTableName) where e.PartitionKey == partitionKey && e.RowKey == rowKey select e).FirstOrDefault(); } catch { //do nothing, this covers the case when the entity does not exist //TODO handle other cases } return(aggregate); }
public bool TrackUsage(UsageEvent usage) { int statusCode = -1; BillingCycle currentBillingCyle = this.manager.FetchOrCreateCurrentBillingCycle(); usage.BillingCycleId = currentBillingCyle.Id; //PartitionKey = orderId + billingCycleId //RowKey = NewGuid usage.PartitionKey = string.Format("{0}-{1}", usage.OrderId.ToString(), usage.BillingCycleId.ToString()); usage.RowKey = Guid.NewGuid().ToString(); //locking to reduce contention for aggregate update if multiple events for same order are coming at the same time //this is not foolproff though. TODO - when updates happen from different servers. check what error Azure //returns when server1 read, server2 updated, server1 updated //based on that tweak the business logic lock (lockObject) { //step1 - fetchORcreate and increment aggregate usage UsageAggregateByBillingCycle aggregate = GetUsageAggregateByBillingCycle(usage.OrderId, usage.BillingCycleId, usage.ResourceId); bool isNew = false; if (aggregate == null) { aggregate = new UsageAggregateByBillingCycle { OrderId = usage.OrderId, BillingCycleId = usage.BillingCycleId, ResourceId = usage.ResourceId }; aggregate.PartitionKey = string.Format("{0}-{1}", usage.OrderId.ToString(), usage.BillingCycleId.ToString()); aggregate.RowKey = usage.ResourceId; isNew = true; } aggregate.AmountConsumed += usage.AmountConsumed; //step2 - save raw usage this.tableContext.AddObject(RawUsageTableName, usage); if (isNew) { this.aggregateTableServiceContext.AddObject(AggregateUsageTableName, aggregate); } else { this.aggregateTableServiceContext.UpdateObject(aggregate); } //Now add logic for per user aggregate UsageAggregatePerUserByBillingCycle aggregatePerUser = GetUsageAggregatePerUserByBillingCycle(usage.OrderId, usage.BillingCycleId, usage.ResourceId, usage.UserId); isNew = false; if (aggregatePerUser == null) { aggregatePerUser = new UsageAggregatePerUserByBillingCycle { OrderId = usage.OrderId, BillingCycleId = usage.BillingCycleId, ResourceId = usage.ResourceId, UserId = usage.UserId }; aggregatePerUser.PartitionKey = string.Format("{0}-{1}", usage.OrderId.ToString(), usage.BillingCycleId.ToString()); aggregatePerUser.RowKey = string.Format("{0}-{1}", usage.ResourceId, usage.UserId); isNew = true; } aggregatePerUser.AmountConsumed += usage.AmountConsumed; if (isNew) { this.aggregatePerUserTableServiceContext.AddObject(AggregatePerUserUsageTableName, aggregatePerUser); } else { this.aggregatePerUserTableServiceContext.UpdateObject(aggregatePerUser); } //end DataServiceResponse response = this.tableContext.SaveChangesWithRetries(SaveChangesOptions.Batch); response = this.aggregateTableServiceContext.SaveChangesWithRetries(SaveChangesOptions.Batch); statusCode = response.BatchStatusCode; } return(statusCode == Http200 || statusCode == 202); }