Amazon DynamoDB store list of custom objects - c#

I'm using Amazon DynamoDB for the first time (and it is awesome). I am working with my the object persistence model so I'm using my own objects to send back and forth. This is working great on my primitive properties (strings, etc.). However I also have a property that is a list of other custom objects that I would like to store and I can't get that to commit to the DB. It's a list of custom objects; each object has a few primitive properties and another list of custom objects (and these have only primitives on them). I found this:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/ArbitraryDataMappingHLAPI.html
and have tried to copy it but can't get it to work. I also found this on storing maps:
http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/DataModel.html#DataModel.DataTypes
but that doesn't really seem to work directly with objects unless I'm missing something. Anyway here is the converter I came up with:
public class AwsCommentConverter : IPropertyConverter
{
public object FromEntry(DynamoDBEntry entry)
{
PrimitiveList primList = entry as PrimitiveList;
if (primList == null)
{ throw new ArgumentOutOfRangeException(); }
List<CommentVM> comments = new List<CommentVM>();
foreach (string s in primList.AsListOfString())
{
string[] vals = s.Split(new string[] { "$~$" }, StringSplitOptions.None);
if (vals.Length < 3) throw new ArgumentOutOfRangeException();
List<StatusUpdate> updates = new List<StatusUpdate>();
for (int i = 2; i < vals.Length; i++)
{
string[] udVals = vals[i].Split(new string[] { "###" }, StringSplitOptions.None);
if (udVals.Length != 4) throw new ArgumentOutOfRangeException();
StatusUpdate ud = new StatusUpdate()
{
StatusType = (StatusUpdate.StatusUpdateType)Enum.Parse(typeof(StatusUpdate.StatusUpdateType), udVals[0]),
StatusDate = DateTime.FromFileTime(long.Parse(udVals[1])),
UserName = udVals[2],
NewValue = udVals[3]
};
updates.Add(ud);
}
comments.Add(new CommentVM(vals[0], vals[1], updates));
}
return comments;
}
public DynamoDBEntry ToEntry(object value)
{
List<CommentVM> comments = value as List<CommentVM>;
List<Primitive> storageVals = new List<Primitive>();
if (comments == null) throw new ArgumentOutOfRangeException();
foreach (CommentVM cm in comments)
{
string dta = cm.ID + "$~$" + cm.CommentText + "$~$";
foreach (StatusUpdate ud in cm.StatusLog)
{ dta += ud.StatusType.ToString() + "###" + ud.StatusDate.ToFileTime().ToString() + "###" + ud.UserName + "###" + ud.NewValue + "$~$"; }
if (dta.EndsWith("$~$"))
{ dta = dta.Substring(0, dta.Length - 3); }
storageVals.Add(new Primitive() { Value = dta });
}
return new PrimitiveList() { Entries = storageVals };
}
}
Here is the property declaration:
[DynamoDBProperty(typeof(AwsCommentConverter))]
public List<CommentVM> Comments
I set a break point in the converter and it never seems to get hit which leads me to believe I've messed up setting it up, but I'm not sure how... Can anyone tell me how to get this stored and retrieved?

Ok I think I figured it out. The property is read only and apparently that means it is skipped? It's a list and it doesn't have a setter because the list is constructed on object creation and the list object itself should not really be replaced, although the contents of the list are modified frequently (hence the need to store them in the db).
I added a setter and now it works with the object persistence model and my custom converter...

Related

preventing new data snapshot from creating additional child

I am trying to send data snapshots to Firebase database. Which is mostly working fine, but I am having an issue where, instead of child objects being attached directly to their intended parents, they are being added to an additional child that is attached to the intended parent.
public void CreateCampaign()
{
campaignName = campaignNameText.text;
ownerName = pInfo.userName;
if (string.IsNullOrEmpty(campaignName))
{
DebugLog("invalid campaign name.");
return;
}
DebugLog(String.Format("Attempting to add campaign ", campaignName, ownerName));
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("Users").Child(pInfo.userName).Child("Campaigns").Push();
DebugLog("Running Transaction...");
reference.RunTransaction(AddCampaignTransaction)
.ContinueWith(task =>
{
if (task.Exception != null)
{
DebugLog(task.Exception.ToString());
}
else if (task.IsCompleted)
{
DebugLog("Campaign " + campaignName + " added successfully.");
}
});
}
TransactionResult AddCampaignTransaction(MutableData mutableData)
{
List<object> Campaigns = mutableData.Value as List<object>;
if (Campaigns == null)
{
Campaigns = new List<object>();
}
Dictionary<string, object> newCampaignMap = new Dictionary<string, object>();
newCampaignMap["CampaignName"] = campaignName;
newCampaignMap["Owner"] = pInfo.userName;
newCampaignMap["Members"] = 0;
Campaigns.Add(Child(newCampaignMap));
mutableData.Value = Campaigns;
return TransactionResult.Success(mutableData);
InitializeCampaign();
}
So with this all my data is added to the database, however my data structure looks like this.
Users
User Name
Campaigns
pushID
0
Campaign Name
Owner Name
Memebrs
What i need to know is; how can I prevent the child "0" from being placed between pushID and the three keys I'm adding, so that my data structure looks like this.
Users
User Name
Campaigns
pushID
Campaign Name
Owner Name
Members
Like mentioned in Gazihan's answer, the problem arises from uploading a list (Campaigns) to the database and not the node you desire (newCampaignMap).
Transactions are used where you need to modify data that already exists on the server in a ordered fashion. Because of your use of Push() to generate a new database reference, this is not needed and SetValueAsync can be used instead.
In your code for AddCampaignTransaction above, you generate an empty list (because there is never existing data), add the value of newCampaignMap to that list and then upload the list to the database instead of just the new value. In this function, you are also making use of shared internal variables from the CreateCampaign function which is bad practice, especially when dealing with asynchronous code.
Instead, I propose using the following code:
public void CreateCampaign()
{
// take local copies of values
string campaignName = campaignNameText.text;
string ownerName = pInfo.userName;
if (string.IsNullOrEmpty(campaignName))
{
DebugLog("invalid campaign name.");
return;
}
DebugLog(String.Format("Attempting to add campaign '{0}' for '{1}'... ", campaignName, ownerName));
// structure data
Dictionary<string, object> newCampaignMap = new Dictionary<string, object>();
newCampaignMap["CampaignName"] = campaignName;
newCampaignMap["Owner"] = pInfo.userName;
newCampaignMap["Members"] = 0;
DebugLog("Adding to database... ");
// get reference and upload data
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("Users").Child(pInfo.userName).Child("Campaigns").Push();
reference.SetValueAsync(newCampaignMap)
.ContinueWith(task =>
{
if (task.IsFaulted)
{
DebugLog(task.Exception.ToString());
return;
}
DebugLog("Campaign " + campaignName + " added successfully.");
});
}
You are pushing a List with a single element. The 0 signifies that it is the 0th element. If there were more elements in the List, you would see 1, 2, etc.
You should replace this line
mutableData.Value = Campaigns;
with this line
mutableData.Value = newCampaignMap;
and try again.
Also you can get rid of the Campaigns etc. that are not used anymore.

MVC List Error List<Model>

I'm using foreach to transfer data from list to another but when adding value updated automatically to last value added. For example:
list1 = [1,2,3]
list2 = new List<Model>()
foreach(var item in list1) {
list2.Add(item)
}
the result in list2 is [ 3, 3, 3]
Actually example is below :
var _sizes = new List<ProductsSize>();
var _size = new ProductsSize();
if (model.Dynamic_ProductsSize.Count > 0)
{
foreach (var item in model.Dynamic_ProductsSize)
{
_size.SizeId = item;
_sizes.Add(_size);
}
}
model.ProductsSize = _sizes.ToList();
I need to know why it only takes the last item and what is the solution for this case
You only have one ProductsSize object:
var _size = new ProductsSize();
And you keep modifying that same object. All references to that object, including any list elements it's been added to, get updated when you modify that one object.
Instead, create your new object in the loop:
foreach (var item in model.Dynamic_ProductsSize)
{
var _size = new ProductsSize();
_size.SizeId = item;
_sizes.Add(_size);
}
That way each element in the list is a new object instead of the same object added multiple times.
Side note, you have a few things in the code which aren't necessary. Checking the length before the loop, for example, as well as converting a list to a list at the end.
In fact, I imagine all of the code shown can be shortened to simply this:
model.ProductsSize = model.Dynamic_ProductsSize.Select(p => new ProductsSize { SizeId = p }).ToList();
In which case you're also just converting one model property to another model property. Why not put this logic in the model itself and skip the whole thing?
public IEnumerable<ProductsSize> ProductsSize
{
get { return this.Dynamic_ProductsSize.Select(p => new ProductsSize { SizeId = p });
}
Unless there's a particular reason you want the same data twice in two different properties that isn't clear from this code, having one set of data and just different views/calculations/etc. of that data is often preferred.
Create a new object before adding it to the list. You can use the object initializer syntax to keep it concise:
if (model.Dynamic_ProductsSize.Count > 0)
{
foreach (var item in model.Dynamic_ProductsSize)
{
_sizes.Add(new ProductsSize(){SizeId = item});
}
}

c# list in property which is array

This is my property class:
class Actions
{
public string[] Style { get; set; }
}
and this is my main method:
Actions action = new Actions();
List<string> list = new List<string>();
list.Add("one");
list.Add("two");
foreach (var item in list)
{
for (int i = 0; i < action.Style.Length; i++)
{
action.Style[i] = item.ToString();
Console.WriteLine(action.Style[i]);
}
}
How do I fill the property with list items?
This gives me a exception:
"object reference not set to an instance of an object".
There is no need to add your items one by one, you could just use the ToArray() method of your list like so:
List<string> list = new List<string>();
list.Add("one");
list.Add("two");
Actions action = new Actions {
Style = list.ToArray()
};
As has already been pointed out, Style is always null, given the code you have shared. #Eldeniz and #paul have shared different ways to fix that. Obviously, your sample code is just a sample fragment, so here are 2 other options you could consider if the previous two don't work for whatever reason (I'm just free-handing this, please excuse any typos).
1) You can have your Actions class always return a not-null object
class Actions
{
private string[] _style;
public string[] Style
{
get { return _style ?? new string[0]; }
set { _style = value; }
}
}
Note that this will allow you to always see the output of the style property as requested, assuming an empty array and null are, for your purposes, the same thing.
2) You can make your loop tolerant to null values
foreach (var item in list)
{
for (int i = 0; i < action?.Style.Length ?? 0; i++)
{
action.Style[i] = item.ToString();
Console.WriteLine(action.Style[i]);
}
}
Finally, just as a tip, if you have your debugger attached and you are stepping through your code, Visual Studio will help you pinpoint these sorts of errors pretty easily. Take the time to become friends with your debugger. If it gives you an error you don't understand, do a quick web search. Your future self will thank you.
You must create an instance of the Style property
List<string> list = new List<string>();
list.Add("one");
list.Add("two");
Actions action = new Actions();
action.Style=new string[list.Count];
foreach (var item in list)
{
for (int i = 0; i < action.Style.Length; i++)
{
action.Style[i] = item.ToString();
Console.WriteLine(action.Style[i]);
}
}

How to combine two c# objects using Linq

I am trying to combine two LIKE objects together and remove duplicates.
Tried this
This didn't work
Here is my object [simple]
public class LabelItem
{
public string LabelName { get; set; }
public string LabelValue { get; set; }
}
my data call returns the same object type
public static List<LabelItem> ReturnControlLabelList(Enums.LanguageType languageType, string labelList = "")
I pass this to the method
string[] LABELLIST = new string[] { "foxLabel", "commonLabel" };
var helper = new LabelHelper(, LABELLIST);
this is where I get null
public LabelHelper(Enums.LanguageType languageType, string[] labelListName)
{
if (labelListName != null)
{
List<LabelItem> labels = new List<LabelItem>();
this.LabelList = new List<LabelItem>();
foreach (var name in labelListName)
{
labels = DBCommon.ReturnControlLabelList(languageType, name);
this.LabelList.Concat(labels).Distinct().ToList();
}
}
else
{
this.LabelList = null;
}
}
public List<LabelItem> LabelList { get; private set; }
The concat is not working. I keep getting count 0 for labels and I can see the returns come back with 275 and 125 in the for loop.
Thanks in advance for the help.
Still having an issue
I want to use the suggestion from below but am still struggling.
The string[] passed in will get two lists of labelitems that are not unique when joined together in the loop. I need the distinct of the multiple lists returned in this.LabelList.
I got it to work with this but...I'm sure it's crazy inefficient.
Thanks for the help.
this.LabelList = new List<LabelItem>();
foreach (var name in labelListName)
{
var ret = DBCommon.ReturnControlLabelList(languageType, name);
this.LabelList = this.LabelList.Concat(ret).Distinct().ToList();
}
var distinctList = this.LabelList.GroupBy(x => new { x.LabelName, x.LabelValue })
.Select(x => x.FirstOrDefault());
this.LabelList = new List<LabelItem>();
foreach (var item in distinctList)
{
this.LabelList.Add(item);
Debug.WriteLine(item.LabelName + ' ' + item.LabelValue);
}
}
this.LabelList.Concat(labels).Distinct().ToList(); without assigning it to something doesn't make much sense. LINQ query does not modify the source collection, it returns a new one, so you'd have to assign it back to this.LabelList if you want it to get updated:
this.LabelList = this.LabelList.Concat(labels).Distinct().ToList();
You should be aware, that it's highly inefficient solution, and you should go with something based on SelectMany:
this.LabelList
= labelListName.SelectMany(name => DBCommon.ReturnControlLabelList(languageType, name)
.Distinct()
.ToList();
Concat and most other linq methods return an IEnumerable which you then need to do something with. It will not change your existing list so you need to just assign it with:
this.LabelList = this.LabelList.Concat(labels).Distinct().ToList();

C# NetSuite WebServices: Get value from custom field in saved search (ItemSearchAdvanced)

I'm using C# MVC to connect to NetSuite using their WebServices API. I have some current code that calls a saved search of inventory items. Here is the current code that is working perfectly:
ItemSearchAdvanced searchItems = new ItemSearchAdvanced();
searchItems.savedSearchId = "128";
SearchResult result = netSuiteSvc.search(searchItems);
int totalRecords = 0;
int processedRecords = 0;
UpdateNetsuitePriceListModel returnObj = new UpdateNetsuitePriceListModel();
returnObj.NewPriceList = new List<NetSuitePriceListRecord>();
if (result.status.isSuccess)
{
SearchRow[] searchRows = result.searchRowList;
if (searchRows != null && searchRows.Length >= 1)
{
for (int i = 0; i < searchRows.Length - 1; i++)
{
ItemSearchRow itemRow = (ItemSearchRow)searchRows[i];
if (itemRow.basic.itemId != null && itemRow.basic.mpn != null && itemRow.basic.basePrice != null && itemRow.basic.salesDescription != null)
{
returnObj.NewPriceList.Add(new NetSuitePriceListRecord()
{
ItemId = itemRow.basic.itemId[0].searchValue,
ManufacturerPartNumber = itemRow.basic.mpn[0].searchValue,
ContractPrice = Convert.ToDecimal(itemRow.basic.basePrice[0].searchValue),
Cost = CalculateProductCostForIngram(Convert.ToDecimal(itemRow.basic.basePrice[0].searchValue)),
Description = itemRow.basic.salesDescription[0].searchValue
});
processedRecords++;
}
totalRecords++;
}
}
else
{
throw new Exception("NetSuite Part List Blank");
}
}
else
{
throw new Exception("NetSuite Part List Search Failure");
}
Now I have need to pull the itemId from a custom added field rather than the default itemId field.
Obviously since this is a custom field it isn't a property of ItemSearchRowBasic. It looks like instead of the property I can choose "customFieldList" which is an array of "SearchColumnCustomField". If I choose an index for the array I can see that SearchColumnCustomField contains:
customLabel
internalId
scriptId
I imagine I should be able to get the internalId of the SearchColumnCustomField and somehow use that to get the search value for that custom column but I've had some trouble finding any examples that fit so far.
This custom field is a free form text field added to all inventory items.
Try setting scriptId with the ID of the field ("custitem_xyz"). That should work.
Before 2013 one would use internalId, but since then it changed to scriptId.
You would need to loop over the CustomRecord items in the customFieldList. I then usually check for a specific type so I can cast to the correct object, but with some reflection you could probably avoid that.
foreach (Record r in mySearchResponse.recordList){
foreach (CustomFieldRef cr in ((CustomRecord)r).customFieldList){
if (cr.GetType().Name == "SelectCustomFieldRef"){
if (((SelectCustomFieldRef)cr).scriptId == "my_custom_field"){
internalID = ((CustomRecord)r).internalId;
}
}
}
}

Categories

Resources