How can I create an On-Demand DynamoDB table in C#? - c#

I cannot seem to find an example of creating an On-Demand DynamoDB table in C#.
The C# examples on AWS only describes how to create a table with provisioned throughput.

I'm surprised by #MaYaN's answer, I've got it working nicely with
CreateTableRequest createRequest = new CreateTableRequest
{
TableName = "Foo",
AttributeDefinitions = new List<AttributeDefinition> {
new AttributeDefinition {
AttributeName = "Id",
AttributeType = ScalarAttributeType.S,
}
},
KeySchema = new List<KeySchemaElement> {
new KeySchemaElement("Id", KeyType.HASH)
},
BillingMode = BillingMode.PAY_PER_REQUEST
};

In my opinion the way this is supported in the SDK is unintuitive.
One would need to first create a table with a default ProvisionedThroughput then update the table and set the billing to: PAY_PER_REQUEST
CreateTableRequest createRequest = new CreateTableRequest
{
TableName = "Foo",
AttributeDefinitions = new List<AttributeDefinition> {
new AttributeDefinition {
AttributeName = "Id",
AttributeType = ScalarAttributeType.S,
}
},
KeySchema = new List<KeySchemaElement> {
new KeySchemaElement("Id", KeyType.HASH)
},
ProvisionedThroughput = new ProvisionedThroughput(1, 1)
};
await client.CreateTableAsync(createRequest);
// Wait for it to be created
await client.UpdateTableAsync(new UpdateTableRequest
{
TableName = name,
BillingMode = BillingMode.PAY_PER_REQUEST
});

Related

Stripe - how add connected account on the subscription (application fee amount)?

I try add subscription method to my project where customer can subscription product from other user. But i have problem because when i use it:
LineItems = new List<SessionLineItemOptions>
{
new SessionLineItemOptions
{
Price = "{{PRICE_ID}}",
Quantity = 1,
},
},
Mode = "subscription",
SuccessUrl = "https://example.com/success",
CancelUrl = "https://example.com/cancel",
PaymentIntentData = new SessionPaymentIntentDataOptions
{
ApplicationFeeAmount = 123,
},
};
var requestOptions = new RequestOptions
{
StripeAccount = "{{CONNECTED_ACCOUNT_ID}}",
};
var service = new SessionService();
Session session = service.Create(options, requestOptions);
But I have error „You can not pass payment_intent_data in subscription mode".
Can i add application fee amount with linked account to subscription? Is payment usually the only option?
PS. I create my product like this:
var options = new ProductCreateOptions
{
Name = "new product",
DefaultPriceData = new ProductDefaultPriceDataOptions
{
UnitAmount = price,
Currency = "pln"
},
Expand = new List<string> { "default_price" }
};
var requestOptions = new RequestOptions
{
StripeAccount = stripeAccountId,
};
_productService.Create(options, requestOptions);
You'd use https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-subscription_data-application_fee_percent instead.
var options = new Stripe.Checkout.SessionCreateOptions
{
...
SubscriptionData = new Stripe.Checkout.SessionSubscriptionDataOptions
{
ApplicationFeePercent = 10
},
};
...

How to query the Google Analytics Data API (GA4) using an event_category and an event_label?

I'm using .NET 5 and the nuget package Google.Analytics.Data.V1Beta to query Google Analytics. But I need to query events using the event_category and the event_label and I get this error: "Field customEvent:event_category is not a valid dimension". This is my code:
var click = new FilterExpression
{
Filter = new Filter
{
FieldName = "ga:event_category",
StringFilter = new Filter.Types.StringFilter
{
CaseSensitive = false,
MatchType = Filter.Types.StringFilter.Types.MatchType.Exact,
Value = "click"
}
},
};
var request = new RunReportRequest
{
Property = $"properties/{_settings.GoogleAnalytics.PropertyId}",
Dimensions = {new Dimension {Name = "eventName"}, new Dimension {Name = "customEvent:event_category" } },
DimensionFilter = click,
Metrics = {new Metric {Name = "eventCount"}},
DateRanges =
{
new DateRange
{StartDate = startDate.ToString("yyyy-MM-dd"), EndDate = endDate.ToString("yyyy-MM-dd")}
}
};
var response = await _analyticsClient.GetClient().RunReportAsync(request);
How do I pass event_category and event_label to the query?

DynamoDB Conditional SaveAsync

I'm using the following code:
DynamoDBContextConfig config = new DynamoDBContextConfig()
{
ConsistentRead = false,
Conversion = DynamoDBEntryConversion.V2
};
using (DynamoDBContext context = new DynamoDBContext(config))
{
long unixTimestamp = (long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds;
QTrackStatus qTrackStatus = new QTrackStatus()
{
Key = "m#m.com",
EventCode = "CC",
EventDateTime = "2019-09-09 14:00:30",
EventLocation = "BFS",
EventLastUpdate = unixTimestamp
};
DynamoDBOperationConfig dynamoDBOperationConfig = new DynamoDBOperationConfig()
{
QueryFilter = new List<ScanCondition>()
{
{ new ScanCondition("EventCode", ScanOperator.NotEqual, "CC") }
},
ConditionalOperator = ConditionalOperatorValues.And
};
await context.SaveAsync(qTrackStatus, dynamoDBOperationConfig);
}
What I'm trying to do is only save the record if the EventCode is not CC. Currently it's always saving. I could retrieve the record first, do a check and then call SaveAsync if required - however I'd like to do it all in the SaveAsync call. Is this possible?
Unless I've missed something, it doesn't look like this is possible with the higher level api. I ended up re-factoring to the following:
AmazonDynamoDBClient client = new AmazonDynamoDBClient();
UpdateItemRequest updateItemRequest = new UpdateItemRequest()
{
TableName = "QTrackStatus",
ReturnValues = ReturnValue.ALL_NEW,
ConditionExpression = "EventCode <> :ec",
UpdateExpression = "SET EventCode = :ec, EventDateTime = :edt, EventLocation = :el, EventLastUpdate = :elu",
ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
{
{ ":ec", new AttributeValue("CC") },
{ ":edt", new AttributeValue("2019-09-09 14:00:30") },
{ ":el", new AttributeValue("BFS") },
{ ":elu", new AttributeValue() { N = ((long)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds).ToString() } }
},
Key = new Dictionary<string, AttributeValue>()
{
{ "Key", new AttributeValue("m#m.com") }
}
};
try
{
UpdateItemResponse updateItemResponse = await client.UpdateItemAsync(updateItemRequest);
}
catch(ConditionalCheckFailedException e)
{
}
This allows me to achieve my goal.

Why does my DynamoDB secondary index only work with a partial part of the sort key

Querying a table on Secondary Index, using a BeginsWith Operator does not always return the correct records. What could be the reason for that?
I have sample Code and tried it against a local DynamoDB table and against a table in AWS. The results are the same.
Defining the secondary index:
var gsiKey1Index = new GlobalSecondaryIndex
{
IndexName = "GSIKey1Index",
ProvisionedThroughput = new ProvisionedThroughput
{
ReadCapacityUnits = 1,
WriteCapacityUnits = 1
},
Projection = new Projection { ProjectionType = "ALL" }
};
var indexKey1Schema = new List<KeySchemaElement> {
{
new KeySchemaElement
{
AttributeName = "HashKey", KeyType = KeyType.HASH
}
}, //Partition key
{
new KeySchemaElement
{
AttributeName = "GSISortKey1", KeyType = KeyType.RANGE
}
} //Sort key
};
gsiKey1Index.KeySchema = indexKey1Schema;
Creating the table:
var request = new CreateTableRequest
{
TableName = "TestTable",
AttributeDefinitions = new List<AttributeDefinition>
{
new AttributeDefinition
{
AttributeName = "HashKey",
// "S" = string, "N" = number, and so on.
AttributeType = "S"
},
new AttributeDefinition
{
AttributeName = "SortKey",
AttributeType = "S"
},
new AttributeDefinition
{
AttributeName = "GSISortKey1",
// "S" = string, "N" = number, and so on.
AttributeType = "S"
},
},
KeySchema = new List<KeySchemaElement>
{
new KeySchemaElement
{
AttributeName = "HashKey",
// "HASH" = hash key, "RANGE" = range key.
KeyType = "HASH"
},
new KeySchemaElement
{
AttributeName = "SortKey",
KeyType = "RANGE"
},
},
GlobalSecondaryIndexes = { gsiKey1Index },
ProvisionedThroughput = new ProvisionedThroughput
{
ReadCapacityUnits = 10,
WriteCapacityUnits = 5
},
};
response2 = await _client.CreateTableAsync(request);
The Entity looks like this:
[DynamoDBTable("TestTable")]
public sealed class TestResult
{
[DynamoDBHashKey]
public string HashKey {get;set;}
public string SortKey {get;set;}
public string GSISortKey1 {get; set;}
}
The code to query looks like this:
public async Task<List<TestResult>> GetTestResultByDateTimeAsync(Guid hashid, Guid someId, string someName, DateTime timestamp)
{
var key2 = $"TR-{someId}-{someName}-{timestamp:yyyy-MM-ddTHH\\:mm\\:ss.fffZ}";
var key = $"TR-{someId}-{someName}-{timestamp:yyyy-MM-dd}";
//var filter = new QueryFilter();
//filter.AddCondition("HashKey", ScanOperator.Equal, hashid.ToString());
//filter.AddCondition("GSISortKey1", ScanOperator.BeginsWith, key);
//var queryConfig = new QueryOperationConfig
//{
// Filter = filter
//};
DynamoDBOperationConfig operationConfig = new DynamoDBOperationConfig()
{
OverrideTableName = "DEVTransponderTR",
IndexName = "GSIKey1Index",
QueryFilter = new List<ScanCondition>()
};
var sc = new ScanCondition("GSISortKey1", ScanOperator.BeginsWith, new[] { key });
operationConfig.QueryFilter.Add(sc);
var search = _context.QueryAsync<TestResult>(tenantid.ToString(), operationConfig);
var testresults = await search.GetRemainingAsync();
bool keywasok = true;
foreach (var testresult in testresults)
{
keywasok = testresult.GSISortKey1.StartsWith(key2) && keywasok;
}
if (keywasok) // this means I should find the same values if I use key2
{
DynamoDBOperationConfig operationConfig2 = new DynamoDBOperationConfig()
{
OverrideTableName = "TestTable",
IndexName = "GSIKey1Index",
QueryFilter = new List<ScanCondition>()
};
var sc2 = new ScanCondition("GSISortKey1", ScanOperator.BeginsWith, new[] { key2 });
operationConfig2.QueryFilter.Add(sc2);
var search2 = _context.QueryAsync<TestResult>(tenantid.ToString(), operationConfig);
var testresults2 = await search.GetRemainingAsync();
}
return testresults;
}
Then I add three instances to the table. importants is that all information is the same except the GSISortKey1 value. This is the GSISortKey1 value for the three items:
"TR-0d798900-a852-4a75-bef0-5dd5c96c657c-Module1-2019-04-02T12:24:19.000Z-Open"
"TR-0d798900-a852-4a75-bef0-5dd5c96c657c-Module1-2019-04-02T12:24:19.000Z-Send"
"TR-0d798900-a852-4a75-bef0-5dd5c96c657c-Module1-2019-04-02T12:24:19.000Z-Test"
So in the example I first query on "BeginsWith" with a value of "TR-0d798900-a852-4a75-bef0-5dd5c96c657c-Module1-2019-04-02" (this is var key)
It returns all three items.
Then I check if the whole GSISortKey1 value of three returned items would also begin with the value "TR-0d798900-a852-4a75-bef0-5dd5c96c657c-Module1-2019-04-02T12:24:19.000Z-", and that is true.
If that is true I just query again with a BeginsWith using the value "TR-0d798900-a852-4a75-bef0-5dd5c96c657c-Module1-2019-04-02T12:24:19.000Z-"
It should return all three items. it returns 0 items.
I guess I am doing something wrong, some help appreciated.

AWS SDK .NET DynamoDB ASYNC

I am trying to use AWS SDK for .NET Core.
Create a table to count views on videos.
Add a view count for a day.
Increment existing count for a day.
Query for video counts between two dates for a video.
.NET Core AWS SDK uses Async methods which are not documented in AWS. There is a feature request on their github page for this to happen.... but it is dated from last year. (https://github.com/aws/aws-sdk-net/issues/787)
CREATE THE TABLE
This works and creates a table on the AWS Console.
var ctRequest = new CreateTableRequest
{
AttributeDefinitions = new List<AttributeDefinition>()
{
new AttributeDefinition
{
AttributeName = "ViewUid",
AttributeType = ScalarAttributeType.S
},
new AttributeDefinition
{
AttributeName = "ViewDate",
AttributeType = ScalarAttributeType.S
}
},
KeySchema = new List<KeySchemaElement>
{
new KeySchemaElement
{
AttributeName = "ViewUid",
KeyType = KeyType.HASH //Partition key
},
new KeySchemaElement
{
AttributeName = "ViewDate",
KeyType = KeyType.RANGE
}
},
ProvisionedThroughput = new ProvisionedThroughput
{
ReadCapacityUnits = 5,
WriteCapacityUnits = 6
},
TableName = _settings.AWSDynamoDBViewCountTable
};
var response = _client.CreateTableAsync(ctRequest).Result;
UPDATE AND ITEM WITH AUTO-INCREMENT A FIELD
This, sadly, is where i hit issues. The old docs are found here under the Atomic Counter section. (https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LowLevelDotNetItemCRUD.html)
Invalid ConditionExpression: Syntax error; token: \"SET\", near: \"SET
VC\"
var viewData = new Document();
viewData["ViewUid"] = videoUid; //Table entry UID
viewData["VideoId"] = videoId; // Video ID
viewData["ViewDate"] = date;
viewData["ViewCount"] = 0;
//Document result = await _viewCountTable.PutItemAsync(viewData);
Expression expr = new Expression();
expr.ExpressionStatement = "SET #VC = #VC + :val";
expr.ExpressionAttributeValues[":val"] = 1;
expr.ExpressionAttributeNames["#VC"] = "ViewCount";
var updateConfig = new UpdateItemOperationConfig() {
ConditionalExpression = expr,
ReturnValues = ReturnValues.UpdatedNewAttributes
};
var result = await _viewCountTable.UpdateItemAsync(viewData, updateConfig);
return result;
QUERY FOR DATE RANGE
Get one video's view count for a date range.
string queryTimeSpanStartString = dateFrom.ToString(AWSSDKUtils.ISO8601DateFormat);
string queryTimeSpanEndString = dateTo.ToString(AWSSDKUtils.ISO8601DateFormat);
var request = new QueryRequest
{
TableName = _settings.AWSDynamoDBViewCountTable,
KeyConditions = new Dictionary<string, Condition>()
{
{
"VideoId", new Condition()
{
ComparisonOperator = "EQ",
AttributeValueList = new List<AttributeValue>()
{
new AttributeValue { S = videoId }
}
}
},
{
"ViewDate",
new Condition
{
ComparisonOperator = "BETWEEN",
AttributeValueList = new List<AttributeValue>()
{
new AttributeValue { S = queryTimeSpanStartString },
new AttributeValue { S = queryTimeSpanEndString }
}
}
}
}
};
var response = await _client.QueryAsync(request);
Any help would be appreciated.
I was able to update the ViewCount with the following code:
string tableName = "videos";
var request = new UpdateItemRequest
{
Key = new Dictionary<string, AttributeValue>() { { "ViewUid", new AttributeValue { S = "replaceVideoIdhere" } } },
ExpressionAttributeNames = new Dictionary<string, string>()
{
{"#Q", "ViewCount"}
},
ExpressionAttributeValues = new Dictionary<string, AttributeValue>()
{
{":incr", new AttributeValue {N = "1"}}
},
UpdateExpression = "SET #Q = #Q + :incr",
TableName = tableName
};
var response = await _dynamoDbClient.UpdateItemAsync(request);
I created a table called "videos" with a partition key named "ViewUid" as string. Let me know if this works for you.

Categories