I would like to insert data as array of json objects into a postgresql table column.
db table structure (create command for the table):
CREATE TABLE Recipes (
key SERIAL PRIMARY KEY,
name varchar(255) NOT NULL,
ingredients json[],
duration int);
A working sql query example (with any sql client):
INSERT INTO
recipes (name, duration, ingredients)
VALUES(
'title',
60,
array['{"name": "in1",
"amount": 125,
"unit": "g" }',
'{ "name": "in2",
"amount": 75,
"unit": "ml" }'
]::json[]);
In npgsql I try it as following:
//Create connection
var connString = $"Host={host};Port={port};Username={user};Password={password};Database={database}";
Npgsql.NpgsqlConnection.GlobalTypeMapper.UseJsonNet();
await using var conn = new NpgsqlConnection(connString);
await conn.OpenAsync();
//create query command
await using var cmd = new NpgsqlCommand("INSERT INTO recipes (name,duration,ingredients) VALUES (#p0,#p1,#p2)", conn)
{
Parameters =
{
new NpgsqlParameter("p0", recipe.Name),
new NpgsqlParameter("p1", recipe.Duration),
new NpgsqlParameter("p2", recipe.Ingredients)
}
};
//execute query
await cmd.ExecuteNonQueryAsync();
I have two classes from which I want generate the query params:
public class Recipe
{
public Recipe() { }
public string Name { get; set; }
public int Duration { get; set; }
//tried this --> did not help
//[Column(TypeName = "jsonb")]
public Ingredients[] Ingredients { get; set; }
}
public class Ingredients
{
public string Name { get; set; }
public float Amount { get; set; }
public string Unit { get; set; }
}
Since I was not able to do so, I tried to debug with hard coded stuff like this:
JObject jsonObject1 = new JObject();
jsonObject1.Add("name", "value1");
jsonObject1.Add("amount", 1);
jsonObject1.Add("unit", "ml");
JObject jsonObject2 = new JObject();
jsonObject2.Add("name", "value2");
jsonObject2.Add("amount", 2);
jsonObject2.Add("unit", "g");
JObject jsonObject = new JObject();
jsonObject.Add("name", "value0");
jsonObject.Add("amount", 19);
jsonObject.Add("unit", "ts");
//OPTION 1 to insert into the query command instead of recipe.Ingredients
JArray ingredientsJArray = new JArray();
ingredientsJArray.Add(jsonObject1);
ingredientsJArray.Add(jsonObject2);
ingredientsJArray.Add(jsonObject);
//AND OPTION 2 to insert into the query command instead of recipe.Ingredients
JObject[] ingredientsArray = new JObject[3];
ingredientsArray[0] = jsonObject;
ingredientsArray[1] = jsonObject1;
ingredientsArray[2] = jsonObject2;
For the Json handling I use Newtonsoft.Json (Nuget Package)
I also tried to make an array of (json formatted) strings to get the query working which understandably lead to exceptions which say that I use text[] instead of json[].
Is it really that hard to achieve this with c# npgsql? In other languages like js (npm package of pg) it is super easy. Or am I missing s.th. very obvious?
Remark: without the json[] column the query works like a charm.
Some help is highly appreciated. Thx!
PostgreSQL has an array type which is very different from a JSON array; in other words, you can't write a .NET JArray (which maps to PG json) to json[], which is your column. Instead, map a regular .NET array of JObject as follows:
var jsonObject1 = new JObject
{
{ "name", "value1" },
{ "amount", 1 },
{ "unit", "ml" }
};
var jsonObject2 = new JObject
{
{ "name", "value2" },
{ "amount", 2 },
{ "unit", "g" }
};
cmd.CommandText = "INSERT INTO Recipes (ingredients) VALUES (#p)";
cmd.Parameters.Add(new("p", NpgsqlDbType.Json | NpgsqlDbType.Array) { Value = new[] { jsonObject1, jsonObject2 } });
await cmd.ExecuteNonQueryAsync();
Note that you need to manually specify the NpgsqlDbType as in the above, opened this issue to make this better.
Finally, Npgsql also supports the built-in System.Text.Json as an alternative to Newtonsoft.Json - no additional plugin is needed for that.
Related
If I was to use the high level model, I might try something like this:
public async void GetBooksData()
{
GetItemRequest request = new GetItemRequest
{
TableName = "Customer",
Key = new Dictionary<string, AttributeValue>
{
{"UserName", new AttributeValue{S="a"} },
{"BookNum", new AttributeValue { S = starts_with(queryTerm)} }
}
};
try
{
var response = await client.GetItemAsync(request);
if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
if (response.Item.Count > 0)
{
foreach (var item in response.Item)
{
MessageBox.Show("Value : \n" + item.Value.S);
}
}
}
}
catch (InternalServerErrorException iee)
{
MessageBox.Show(iee);
}
}
I need to use the method 'begins_with' for getting 2 items what UserName is 'a' and the BookNum are book_1 and book_2. This is possible in the high level interface in Java. As an example as to what can be done on the range key in Java:
public List<Comment> allForItemWithMinRating(String itemId, int minRating) {
Comment comment = new Comment();
comment.setItemId(itemId);
Condition condition = new Condition()
.withComparisonOperator(ComparisonOperator.GE)
.withAttributeValueList(
new AttributeValue()
.withN(Integer.toString(minRating)));
DynamoDBQueryExpression<Comment> queryExpression
= new DynamoDBQueryExpression<Comment>()
.withHashKeyValues(comment)
.withRangeKeyCondition(
"rating",
condition
)
.withScanIndexForward(false);
return mapper.query(Comment.class, queryExpression);
}
In the low level interface for C# you can achieve this as so:
var requestDynamodb = new QueryRequest
{
TableName = "GroupEdEntries",
KeyConditionExpression = "partition_key = :s_Id and begins_with(sort_key, :sort)",
ExpressionAttributeValues = new Dictionary<string, AttributeValue> {
{":s_Id", new AttributeValue { S = my_id }},
{":sort", new AttributeValue { S = sort_key_starts_with }}
},
ConsistentRead = true
};
var results = await client.QueryAsync(requestDynamodb);
where the keys are called partition_key and sort_key. However, this returns the results as attribute values, which then need to be converted into POCOs one property at a time. It requires using reflection and is made more complicated using converters. It seems strange that this fundamental functionality (as well as other functionality) isn't supported in the C# SDK.
I ended up using reflection to create the tables based on the attributes, when this is also supported by default in Java. Am I missing a high level API for C#?
It's a bit of a different syntax and I can't find it documented anywhere (other than in code comments), but this works for me:
string partition_key = "123";
string sort_key_starts_with = "#type"
List<object> queryVal = new List<object>();
queryVal.Add(sort_key_starts_with);
var myQuery = context.QueryAsync<GroupEdEntry>(partition_key, QueryOperator.BeginsWith, queryVal);
var queryResult = await myQuery.GetRemainingAsync();
I made a mistake with my SQL query with ComsosDb .NET SDK 3. I want to request a list of objects from a document.
This is my document as stored in CosmosDb (fid is partition key):
{
"id": "1abc",
"fid": "test",
"docType": "Peeps",
"People": [
{
"Name": "Bill",
"Age": 99
},
{
"Name": "Marion",
"Age": 98
},
{
"Name": "Seb",
"Age": 97
}
],
"_rid": "mo53AJczKUuL9gMAAAAAAA==",
"_self": "dbs/mo53AA==/colls/mo53AJczKUs=/docs/mo53AJczKUuL9gMAAAAAAA==/",
"_etag": "\"9001cbc7-0000-1100-0000-60c9d58d0000\"",
"_attachments": "attachments/",
"_ts": 1623840141
}
My results show an item count of 1 with properties set to default values - Name is null and Age is 0.
I was expecting a IEnumerable of 3 persons. Here is the code:
class MyPeople
{
public IEnumerable<Person> People { get; set; }
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
[Fact]
public async Task CosmosPeopleTest_ReturnsThreePeople()
{
var config = GetConFig();
var cosmosClientV2 = new CosmosClient(config["Cosmos:ConnectionString"]);
var container = cosmosClientV2.GetContainer(config["Cosmos:DbName"], config["Cosmos:Collectionname"]);
var sql = "SELECT c.People FROM c WHERE c.docType = 'Peeps'";
QueryDefinition queryDefinition = new QueryDefinition(sql);
var results = new List<Person>();
FeedIterator<Person> q = container.GetItemQueryIterator<Person>(queryDefinition, null, new QueryRequestOptions { PartitionKey = new PartitionKey("test") });
while (q.HasMoreResults)
{
var x = await q.ReadNextAsync();
results.AddRange(x.ToList());
}
Assert.Equal(3, results.Count);
}
If I change the query to
sql = "SELECT c.People FROM c JOIN d IN c.People";
I have three Person all with properties Name & Age which are defaults.
You have an issue with the types. SELECT c.People return an object in this form:
{
People: [
...
]
}
When you iterate with this code
FeedIterator<Person> q = container.GetItemQueryIterator<Person>(queryDefinition, null, new QueryRequestOptions { PartitionKey = new PartitionKey("test") });
The CosmosDB try to "convert" every result object (as above) to a Person class. But it using reflection for that. So it creates a Person object but doesn't find any fields to fill its properties - it will not fail, but create empty objects with all the properties initialized with default values.
So to solve it you need to use MyPeople instead of Person:
FeedIterator<MyPeople> q = container.GetItemQueryIterator<MyPeople>(queryDefinition, null, new QueryRequestOptions { PartitionKey = new PartitionKey("test") });
Since MyPeople is the right form of the returned object, it will be able to read the objects that the CosmosDB returns and use them.
The full working code:
var config = GetConFig();
var cosmosClientV2 = new CosmosClient(config["Cosmos:ConnectionString"]);
var container = cosmosClientV2.GetContainer(config["Cosmos:DbName"], config["Cosmos:Collectionname"]);
var sql = "SELECT c.People FROM c WHERE c.docType = 'Peeps'";
QueryDefinition queryDefinition = new QueryDefinition(sql);
var results = new List<Person>();
FeedIterator<MyPeople> q = container.GetItemQueryIterator<MyPeople>(queryDefinition, null, new QueryRequestOptions { PartitionKey = new PartitionKey("test") });
while (q.HasMoreResults)
{
var x = await q.ReadNextAsync();
var myPeopleRes = x.Resource;
foreach (var people in myPeopleRes)
{
results.AddRange(people.People);
}
}
Assert.Equal(3, results.Count);
I am insert data into .json file. My insertion works fine, but I have a problem - when I insert new data, the previous data in the file is deleted.
How can I manage that?
Also I would need it to be in the format of because I need to query it:
[
{
"Rollnumber": 3,
"StudentName": "Tapas"
},
{
"Rollnumber": 4,
"StudentName": "Papas"
}
]
When I pass the data as the list.ToArray()(that would be the _data in the code example) I get the [] brackets, but only on the first data with rollnumber = 3.
This is my code so far:
private void AddStudent()
{
Student objStudent = new Student();
objStudent.Rollnumber = 3;
objStudent.StudentName = "Tapas";
/* List<Student> _data = new List<Student>();
_data.Add(new Student()
{
Rollnumber = 4,
StudentName = "Papas"
});
*/
string jsonData = JsonConvert.SerializeObject(objStudent, Formatting.Indented);
System.IO.File.WriteAllText(jsonFileS, jsonData);
}
I have tried with the StreamWritter also, but I could not do it.
Retrieve the source file that containing JSON.
Read text content (JSON data) from source file.
Deserialize JSON data to List of student object.
Add new student student list.
Serialize the student list, you will get JSON data string as return from that method.
Save JSON string to the source file.
Here is example code:
var file = await ApplicationData.Current.LocalFolder.GetFileAsync(fileName);
var jsonString = await FileIO.ReadTextAsync(file);
var studentList = JsonConvert.DeserializeObject<List<Student>>(jsonString);
var newStudent = new Student();
newStudent.Rollnumber = 2;
newStudent.StudentName = "Abcd";
studentList.Add(newStudent);
var updatedJsonString = JsonConvert.SerializeObject(studentList);
await FileIO.WriteTextAsync(file, updatedJsonString);
I think in your situation, you can do a little trick like :
1, Read data from file
StreamReader sw = new StreamReader(fileName);
string res = sw.ReadToEnd();
2, Remove the "]" of the "res"(res variable) and add new Json string(remove the "[" and add ",") with your format.
So your string will look like two strings bellow adding together
[
{
"Rollnumber": 3,
"StudentName": "Tapas"
}
// Cut the last "]"
and
// cut the first "[" and add ","
,
{
"Rollnumber": 4,
"StudentName": "Papas"
}
]
So it will become what you want in the final :
[
{
"Rollnumber": 3,
"StudentName": "Tapas"
},
{
"Rollnumber": 4,
"StudentName": "Papas"
}
]
The answer is on this link : append to a json file
var jsonData = System.IO.File.ReadAllText(jsonFile);
// De-serialize to object or create new list
var employeeList = JsonConvert.DeserializeObject<List<Student>>(jsonData)
?? new List<Student>();
// Add any new employees
employeeList.Add(new Student()
{
Rollnumber = 1,
StudentName = "Paja1",
Subject = new Subject
{
Id = 1,
Name = "Sub1"
}
});
employeeList.Add(new Student()
{
Rollnumber = 1,
StudentName = "Pera1",
Subject = new Subject
{
Id = 1,
Name = "Sub1"
}
});
// Update json data string
jsonData = JsonConvert.SerializeObject(employeeList,Formatting.Indented);
System.IO.File.WriteAllText(jsonFile, jsonData);
As I mentioned in my comments to your question, you need to either load the entire file, deserialize it and then add items to it in memory. Once done, rewrite to the file by serializing it.
If you do not want to load the entire file into memory, then work with the file manually by manipulating the serialized string and then append to the file; else you will end up with bad JSON in file.
Here is the first method wherein you deserialize the entire file:
public static class Program
{
public static void Main()
{
var rolls = LoadRolls();
// Once the contents of the file are in memory you can also manipulate them
Roll firstRoll = rolls.SingleOrDefault(x => x.Rollnumber == 1);
if (firstRoll != null)
{
firstRoll.StudentName = "Jerry";
}
// Let's just add a few records.
rolls.AddRange(
new List<Roll>{
new Roll { Rollnumber = 1, StudentName = "Elaine" },
new Roll { Rollnumber = 2, StudentName = "George" }
});
string json = JsonConvert.SerializeObject(rolls, Newtonsoft.Json.Formatting.Indented);
File.WriteAllText("Rolls.txt", json);
Console.Read();
}
private static List<Roll> LoadRolls()
{
List<Roll> rolls = JsonConvert.DeserializeObject<List<Roll>>(File.ReadAllText("Rolls.txt"));
return rolls ?? new List<Roll>();
}
}
public class Roll
{
public int Rollnumber { get; set; }
public string StudentName { get; set; }
}
For some of my unit tests I want the ability to build up particular JSON values (record albums in this case) that can be used as input for the system under test.
I have the following code:
var jsonObject = new JObject();
jsonObject.Add("Date", DateTime.Now);
jsonObject.Add("Album", "Me Against The World");
jsonObject.Add("Year", 1995);
jsonObject.Add("Artist", "2Pac");
This works fine, but I have never really like the "magic string" syntax and would prefer something closer to the expando-property syntax in JavaScript like this:
jsonObject.Date = DateTime.Now;
jsonObject.Album = "Me Against The World";
jsonObject.Year = 1995;
jsonObject.Artist = "2Pac";
Well, how about:
dynamic jsonObject = new JObject();
jsonObject.Date = DateTime.Now;
jsonObject.Album = "Me Against the world";
jsonObject.Year = 1995;
jsonObject.Artist = "2Pac";
You can use the JObject.Parse operation and simply supply single quote delimited JSON text.
JObject o = JObject.Parse(#"{
'CPU': 'Intel',
'Drives': [
'DVD read/writer',
'500 gigabyte hard drive'
]
}");
This has the nice benefit of actually being JSON and so it reads as JSON.
Or you have test data that is dynamic you can use JObject.FromObject operation and supply a inline object.
JObject o = JObject.FromObject(new
{
channel = new
{
title = "James Newton-King",
link = "http://james.newtonking.com",
description = "James Newton-King's blog.",
item =
from p in posts
orderby p.Title
select new
{
title = p.Title,
description = p.Description,
link = p.Link,
category = p.Categories
}
}
});
Json.net documentation for serialization
Neither dynamic, nor JObject.FromObject solution works when you have JSON properties that are not valid C# variable names e.g. "#odata.etag". I prefer the indexer initializer syntax in my test cases:
JObject jsonObject = new JObject
{
["Date"] = DateTime.Now,
["Album"] = "Me Against The World",
["Year"] = 1995,
["Artist"] = "2Pac"
};
Having separate set of enclosing symbols for initializing JObject and for adding properties to it makes the index initializers more readable than classic object initializers, especially in case of compound JSON objects as below:
JObject jsonObject = new JObject
{
["Date"] = DateTime.Now,
["Album"] = "Me Against The World",
["Year"] = 1995,
["Artist"] = new JObject
{
["Name"] = "2Pac",
["Age"] = 28
}
};
With object initializer syntax, the above initialization would be:
JObject jsonObject = new JObject
{
{ "Date", DateTime.Now },
{ "Album", "Me Against The World" },
{ "Year", 1995 },
{ "Artist", new JObject
{
{ "Name", "2Pac" },
{ "Age", 28 }
}
}
};
There are some environment where you cannot use dynamic (e.g. Xamarin.iOS) or cases in where you just look for an alternative to the previous valid answers.
In these cases you can do:
using Newtonsoft.Json.Linq;
JObject jsonObject =
new JObject(
new JProperty("Date", DateTime.Now),
new JProperty("Album", "Me Against The World"),
new JProperty("Year", "James 2Pac-King's blog."),
new JProperty("Artist", "2Pac")
)
More documentation here:
http://www.newtonsoft.com/json/help/html/CreatingLINQtoJSON.htm
Sooner or later you will have property with a special character. e.g. Create-Date. The hyphen won't be allowed in property name. This will break your code. In such scenario, You can either use index or combination of index and property.
dynamic jsonObject = new JObject();
jsonObject["Create-Date"] = DateTime.Now; //<-Index use
jsonObject.Album = "Me Against the world"; //<- Property use
jsonObject["Create-Year"] = 1995; //<-Index use
jsonObject.Artist = "2Pac"; //<-Property use
Simple way of creating newtonsoft JObject from Properties.
This is a Sample User Properties
public class User
{
public string Name;
public string MobileNo;
public string Address;
}
and i want this property in newtonsoft JObject is:
JObject obj = JObject.FromObject(new User()
{
Name = "Manjunath",
MobileNo = "9876543210",
Address = "Mumbai, Maharashtra, India",
});
Output will be like this:
{"Name":"Manjunath","MobileNo":"9876543210","Address":"Mumbai, Maharashtra, India"}
You could use the nameof expression combined with a model for the structure you're trying to build.
Example:
record RecordAlbum(string Album, string Artist, int Year);
var jsonObject = new JObject
{
{ nameof(RecordAlbum.Album), "Me Against The World" },
{ nameof(RecordAlbum.Artist), "2Pac" },
{ nameof(RecordAlbum.Year), 1995 }
};
As an added benefit to removing the "magic string" aspect - this also will give you a little bit of refactor-ability. You can easily rename any given property name for the record and it should update the value returned by the nameof() expression.
You can use Newtonsoft library and use it as follows
using Newtonsoft.Json;
public class jb
{
public DateTime Date { set; get; }
public string Artist { set; get; }
public int Year { set; get; }
public string album { set; get; }
}
var jsonObject = new jb();
jsonObject.Date = DateTime.Now;
jsonObject.Album = "Me Against The World";
jsonObject.Year = 1995;
jsonObject.Artist = "2Pac";
System.Web.Script.Serialization.JavaScriptSerializer oSerializer =
new System.Web.Script.Serialization.JavaScriptSerializer();
string sJSON = oSerializer.Serialize(jsonObject );
I have the following variable of type {Newtonsoft.Json.Linq.JArray}.
properties["Value"] {[
{
"Name": "Username",
"Selected": true
},
{
"Name": "Password",
"Selected": true
}
]}
What I want to accomplish is to convert this to List<SelectableEnumItem> where SelectableEnumItem is the following type:
public class SelectableEnumItem
{
public string Name { get; set; }
public bool Selected { get; set; }
}
I am rather new to programming and I am not sure whether this is possible. Any help with working example will be greatly appreciated.
Just call array.ToObject<List<SelectableEnumItem>>() method. It will return what you need.
Documentation: Convert JSON to a Type
The example in the question is a simpler case where the property names matched exactly in json and in code. If the property names do not exactly match, e.g. property in json is "first_name": "Mark" and the property in code is FirstName then use the Select method as follows
List<SelectableEnumItem> items = ((JArray)array).Select(x => new SelectableEnumItem
{
FirstName = (string)x["first_name"],
Selected = (bool)x["selected"]
}).ToList();
The API return value in my case as shown here:
{
"pageIndex": 1,
"pageSize": 10,
"totalCount": 1,
"totalPageCount": 1,
"items": [
{
"firstName": "Stephen",
"otherNames": "Ebichondo",
"phoneNumber": "+254721250736",
"gender": 0,
"clientStatus": 0,
"dateOfBirth": "1979-08-16T00:00:00",
"nationalID": "21734397",
"emailAddress": "sebichondo#gmail.com",
"id": 1,
"addedDate": "2018-02-02T00:00:00",
"modifiedDate": "2018-02-02T00:00:00"
}
],
"hasPreviousPage": false,
"hasNextPage": false
}
The conversion of the items array to list of clients was handled as shown here:
if (responseMessage.IsSuccessStatusCode)
{
var responseData = responseMessage.Content.ReadAsStringAsync().Result;
JObject result = JObject.Parse(responseData);
var clientarray = result["items"].Value<JArray>();
List<Client> clients = clientarray.ToObject<List<Client>>();
return View(clients);
}
I can think of different method to achieve the same
IList<SelectableEnumItem> result= array;
or (i had some situation that this one didn't work well)
var result = (List<SelectableEnumItem>) array;
or use linq extension
var result = array.CastTo<List<SelectableEnumItem>>();
or
var result= array.Select(x=> x).ToArray<SelectableEnumItem>();
or more explictly
var result= array.Select(x=> new SelectableEnumItem{FirstName= x.Name, Selected = bool.Parse(x.selected) });
please pay attention in above solution I used dynamic Object
I can think of some more solutions that are combinations of above solutions. but I think it covers almost all available methods out there.
Myself I use the first one
using Newtonsoft.Json.Linq;
using System.Linq;
using System.IO;
using System.Collections.Generic;
public List<string> GetJsonValues(string filePath, string propertyName)
{
List<string> values = new List<string>();
string read = string.Empty;
using (StreamReader r = new StreamReader(filePath))
{
var json = r.ReadToEnd();
var jObj = JObject.Parse(json);
foreach (var j in jObj.Properties())
{
if (j.Name.Equals(propertyName))
{
var value = jObj[j.Name] as JArray;
return values = value.ToObject<List<string>>();
}
}
return values;
}
}
Use IList to get the JArray Count and Use Loop to Convert into List
var array = result["items"].Value<JArray>();
IList collection = (IList)array;
var list = new List<string>();
for (int i = 0; i < collection.Count; j++)
{
list.Add(collection[i].ToString());
}