How to get Class Metadata into JSON string - c#

How to generate JSON of Class meta data.
for eg.
C# Classes
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsActive { get; set; }
public Description Description { get; set; }
}
public class Description
{
public string Content { get; set; }
public string ShortContent { get; set; }
}
JSON
[
{
"PropertyName" : "Id",
"Type" : "Int",
"IsPrimitive" : true
},
{
"PropertyName" : "Name",
"Type" : "string",
"IsPrimitive" : true
},
{
"PropertyName" : "IsActive",
"Type" : "bool",
"IsPrimitive" : true
},
{
"PropertyName" : "Description",
"Type" : "Description",
"IsPrimitive" : false
"Properties" : {
{
"PropertyName" : "Content",
"Type" : "string",
"IsPrimitive" : true
},
{
"PropertyName" : "ShortContent",
"Type" : "string",
"IsPrimitive" : true
}
}
},
]

If you define a class that will map your Json Model:
public class PropertyDescription
{
public string PropertyName { get; set; }
public string Type { get; set; }
public bool IsPrimitive { get; set; }
public IEnumerable<PropertyDescription> Properties { get; set; }
}
And then just create a function that read the properties of your object recursively:
public static List<PropertyDescription> ReadObject(Type type)
{
var propertyDescriptions = new List<PropertyDescription>();
foreach (var propertyInfo in type.GetProperties())
{
var propertyDescription = new PropertyDescription
{
PropertyName = propertyInfo.Name,
Type = propertyInfo.PropertyType.Name
};
if (!propertyDescription.IsPrimitive
// String is not a primitive type
&& propertyInfo.PropertyType != typeof (string))
{
propertyDescription.IsPrimitive = false;
propertyDescription.Properties = ReadObject(propertyInfo.PropertyType);
}
else
{
propertyDescription.IsPrimitive = true;
}
propertyDescriptions.Add(propertyDescription);
}
return propertyDescriptions;
}
You can use Json.Net to serialize the result of this function :
var result = ReadObject(typeof(Product));
var json = JsonConvert.SerializeObject(result);
EDIT: Linq solution based on #AmitKumarGhosh answer:
public static IEnumerable<object> ReadType(Type type)
{
return type.GetProperties().Select(a => new
{
PropertyName = a.Name,
Type = a.PropertyType.Name,
IsPrimitive = a.PropertyType.IsPrimitive && a.PropertyType != typeof (string),
Properties = (a.PropertyType.IsPrimitive && a.PropertyType != typeof(string)) ? null : ReadType(a.PropertyType)
}).ToList();
}
...
var result = ReadType(typeof(Product));
json = JsonConvert.SerializeObject(result);

One probable solution -
static void Main(string[] args)
{
var o = typeof(Product).GetProperties().Select(a =>
{
if (a.PropertyType != null && (a.PropertyType.IsPrimitive || a.PropertyType == typeof(string)))
{
return MapType(a);
}
else
{
dynamic p = null;
var t = MapType(a);
var props = a.PropertyType.GetProperties();
if (props != null)
{ p = new { t, Properties = props.Select(MapType).ToList() }; }
return new { p.t.PropertyName, p.t.Type, p.t.IsPrimitive, p.Properties };
}
}).ToList();
var jsonString = JsonConvert.SerializeObject(o);
}
static dynamic MapType(PropertyInfo a)
{
return new
{
PropertyName = a.Name,
Type = a.PropertyType.Name,
IsPrimitive = a.PropertyType != null && a.PropertyType.IsPrimitive
};
}

Try this, concept is get all elements from object to dictionary. Field name and value. For each property create additional elements (using Reflection) in dictionary like Type, IsPrimitive etc. You can use recursion for going throw properties and then serialize this dictionary to JSON.
An example here:
Appending to JSON object using JSON.net
An example of this:
var serialize = new Newtonsoft.Json.JsonSerializer();
var dict = GetDic(new Description());
serialize.Serialize(sr, dict);
And GetDcit implementation:
private List<Dictionary<string, string>> GetDic(object obj)
{
var result= new List<Dictionary<string, string>>();
foreach (var r in obj.GetType().GetProperties())
{
result.Add(new Dictionary<string, string>
{
["PropertyName"] = r.Name,
["Type"] = r.PropertyType.Name,
["IsPrimitive"] = r.GetType().IsPrimitive.ToString(),
});
}
return result;
}

Related

How to remove unwanted string properties from JSON

Suppose my input is following JSON:
{obj: { x: "", y: "test str" }, myStr: "Hi"}
I would like to remove all empty strings and strings with a value of N/A so that the output becomes:
{obj: { y: "test str" }, myStr: "Hi"}
Note the above input is just a sample input file.
I tried to write some code :
var serializerSettings = new JsonSerializerSettings
{
NullValueHandling = NullValueHandling.Ignore,
DefaultValueHandling = DefaultValueHandling.Ignore
};
But I'm not able to get the desired output.
If you're using a class model to deserialize into, you can use a custom ContractResolver to filter out the unwanted properties on serialization:
class CustomContractResolver : DefaultContractResolver
{
protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
{
var prop = base.CreateProperty(member, memberSerialization);
if (prop.PropertyType == typeof(string) && prop.Readable)
{
prop.ShouldSerialize = obj =>
{
string val = (string)prop.ValueProvider.GetValue(obj);
return !string.IsNullOrEmpty(val) && !val.Equals("N/A", StringComparison.OrdinalIgnoreCase);
};
}
return prop;
}
}
Then use it like this:
var obj = JsonConvert.DeserializeObject<YourClass>(inputJson);
var serializerSettings = new JsonSerializerSettings
{
ContractResolver = new CustomContractResolver
{
NamingStrategy = new CamelCaseNamingStrategy()
}
};
string outputJson = JsonConvert.SerializeObject(obj, serializerSettings);
Working demo here: https://dotnetfiddle.net/zwpj7I
Alternatively, you can remove the unwanted properties without a class model like this:
var jo = JObject.Parse(inputJson);
var unwantedProperties = jo.Descendants()
.OfType<JProperty>()
.Where(p => p.Value.Type == JTokenType.Null ||
(p.Value.Type == JTokenType.String &&
((string)p.Value == string.Empty ||
string.Equals((string)p.Value, "N/A", StringComparison.OrdinalIgnoreCase))))
.ToList();
foreach (var prop in unwantedProperties)
{
prop.Remove();
}
string outputJson = jo.ToString(Formatting.None);
Demo: https://dotnetfiddle.net/QLhDK4
to ignore default empty string using DefaultValueHandling you need to add DefaultValue to x property
[DefaultValue("")]
public string x{ get; set; }
Update:
Here is a link to a working dotnetfiddle
using System;
using Newtonsoft.Json;
using System.ComponentModel;
public class Program
{
public static void Main()
{
//var myClass = new MyClasss();
//var myObj = new Obj();
//myObj.x="";
//myObj.y="test str";
//myClass.myStr = "Hi";
//myClass.obj= myObj;
string json = #"{obj: { x: '', y: 'test str' }, myStr: 'Hi'}";
MyClasss myClass = JsonConvert.DeserializeObject<MyClasss>(json);
var settings = new JsonSerializerSettings();
settings.NullValueHandling = NullValueHandling.Ignore;
settings.DefaultValueHandling = DefaultValueHandling.Ignore;
Console.WriteLine(JsonConvert.SerializeObject(myClass, settings));
}
}
public class Obj {
[DefaultValue("")]
public string x { get; set; }
[DefaultValue("")]
public string y { get; set; }
}
public class MyClasss {
public Obj obj { get; set; }
[DefaultValue("")]
public string myStr { get; set; }
}
Please take a look at ShouldSerialize.
You should declare a method like this in the class containing property "x":
public bool ShouldSerializex()
{
return x != "" && x != "N/A";
}

Searching through an array of an array that has a key value object

I am consuming an API that returns JSON that looks like this:
{
"lookup_table_data": [
[
{
"key": "id",
"value": 0
},
{
"key" : "label",
"value" : "something"
}
],
[
{
"key": "id",
"value": 1
},
{
"key" : "label",
"value" : "something_else"
}
]
]
}
I made a class that I deserialize the json object into that looks like this:
public class LookupResponseModel
{
public Lookup_Table_Data[][] Lookup_table_data { get; set; }
public class Lookup_Table_Data
{
public string Key { get; set; }
public object Value { get; set; }
}
}
Now imagine that the JSON response has over 1,000 records instead of the 2 that I gave in my example.
I am wanting to search through my model and be able to find where the value of the key "id" is equal to 1 - because I want to use the label key value of "something_else".
How would I be able to grab the label "something_else" with an id of 1 with this model?
var lookup = model.lookupTableData.Select(data => new { key = (long)data.First(kvp => kvp.Key == "id").Value, value = (string)data.First(kvp => kvp.Key == "label").Value).ToDictionary(kvp => kvp.key, kvp => kvp.value)
var displayText = lookup[1]; // "something_else"
My attempt from a phone, might not be 100% correct syntax.
I would suggest an approach like this:
public class LookupResponseModel
{
public Lookup_Table_Data[][] Lookup_table_data { get; set; }
public class Lookup_Table_Data
{
public string Key { get; set; }
public object Value { get; set; }
}
}
// This will help compare the values and convert if necessary
// This part was missing from my original answer and made it not work
bool ObjectCompare(object a, object b)
{
if (object.Equals(a, b))
{
return true;
}
else
{
var altB = Convert.ChangeType(b, Type.GetTypeCode(a.GetType()));
return object.Equals(a, altB);
}
}
// This will break the result up into an Array of Dictionaries
// that are easier to work with
Dictionary<string, object>[] MakeTables(LookupResponseModel lrm)
{
return lrm.Lookup_table_data.Select( entry => entry.ToDictionary( e => e.Key, e => e.Value ) ).ToArray();
}
// This will help you find the dictionary that has the values you want
Dictionary<string, object> FindTable( Dictionary<string, object>[] tables, string key, object value )
{
return tables.Where( dict => dict.TryGetValue(key, out object val) && ObjectCompare(value, val) ).FirstOrDefault();
}
// Here is how you might use them together
string GetLabel()
{
var lrm = JsonConvert.DeserializeObject<LookupResponseModel>(json);
var lookup = MakeTables(lrm);
var table = FindTable( lookup, "id", 1 );
return table["label"].ToString(); // Returns "something_else"
}
Data :
LookupResponseModel model = new LookupResponseModel();
model.Lookup_table_data = new LookupResponseModel.Lookup_Table_Data[][]
{
new LookupResponseModel.Lookup_Table_Data[]
{
new LookupResponseModel.Lookup_Table_Data(){Key = "id", Value = "0"},
new LookupResponseModel.Lookup_Table_Data(){Key = "label", Value = "hello"},
new LookupResponseModel.Lookup_Table_Data(){Key = "textbox", Value = "bye"}
},
new LookupResponseModel.Lookup_Table_Data[]
{
new LookupResponseModel.Lookup_Table_Data(){Key = "id", Value = "1"},
new LookupResponseModel.Lookup_Table_Data(){Key = "label", Value = "banana"},
new LookupResponseModel.Lookup_Table_Data(){Key = "textbox", Value = "potatoe"}
},
new LookupResponseModel.Lookup_Table_Data[]
{
new LookupResponseModel.Lookup_Table_Data(){Key = "id", Value = "2"},
new LookupResponseModel.Lookup_Table_Data(){Key = "label", Value = "cat"},
new LookupResponseModel.Lookup_Table_Data(){Key = "textbox", Value = "bydog"}
}
};
This query gives us second set (all 3 Lookup_Table_Data where key is ID and value is 1) It still [][] and it can contain more than one result :
var _result = model.Lookup_table_data.Where(x => x.Any(y => y.Key == "id" && y.Value.Equals("1")));
And this one gives you exactly value of "label" key from previous set (Key = "label", Value = "banana") :
var _exactlyLabel = _result.Select(x => x.Where(y => y.Key == "label"));
Or you can make _result.SelectMany(... for [] not for [][]
The simple answer is, you say something like this
public class LookupResponseModel
{
public LookupTableData[][] lookupTableData { get; set; }
public class LookupTableData
{
public string Key { get; set; }
public object Value { get; set; }
}
public LookupTableData[] FindById( int id )
{
if (this.lookupTableData == null) throw new InvalidOperationException();
foreach ( LookupTableData[] entry in lookupTableData )
{
if (entry != null)
{
foreach( LookupTableData item in entry )
{
bool isMatch = "id".Equals( item.Key ?? "", StringComparison.Ordinal )
&& item.Value is int
&& ((int)item.Value) == id
;
if ( isMatch )
{
return entry;
}
}
}
}
return null;
}
}

Set Object null if all its properties are null in C#

I want to write a function which it turns every properies and child properties of an object. And if all properties of one property are null, then I will set that property as null. I will explain with one example.
For example, if both TeacherName and TeacherSurname are null then I want to set Teacher to null. Then, if ExamMark and ExamName and Teacher are null, then Exam will be null.
You can see json version of like my question from this link Json Version
public class Student
{
public string Name { get; set; }
public string Surname { get; set; }
public Address Address { get; set; }
public Exam Exam { get; set; }
}
public class Exam
{
public string ExamMark { get; set; }
public string ExamName { get; set; }
public Teacher Teacher { get; set; }
}
public class Teacher
{
public string TeacherName { get; set; }
public string TeacherSurname { get; set; }
}
public class Address
{
public string Country { get; set; }
public string City { get; set; }
}
I wrote this method. But this doing only first step. But I need recursive for child class. How can I convert this method to recursive?
public static object ConvertToNull(object obj)
{
Type objType = obj.GetType();
PropertyInfo[] properties = objType.GetProperties();
var mainClassProperties = properties.Where(p => p.PropertyType.Assembly == objType.Assembly);
foreach (var mainClassProperty in mainClassProperties)
{
object propValue = mainClassProperty.GetValue(obj, null);
var classAllProperties = propValue.GetType().GetProperties();
if (propValue.GetType().GetProperties().All(propertyInfo => propertyInfo.GetValue(propValue) == null))
{
mainClassProperty.SetValue(obj, null);
}
}
return obj;
}
What you're asking for is actually an anti-pattern. What this does is it propagates the requirement for tons of null checks in your code. Not only do you have to check if all properties are null, you also have to check if your object is null when you want to use it. Also reflection is very slow and if you're doing this often, you'll bog down your application.
You should look into the Null Object Pattern. It basically does what you want it to, and you remove all those pesky null checks:
public class Student
{
public string Name { get; set; }
public string Surname { get; set; }
public Address Address { get; set; }
public Exam Exam { get; set; }
public static Student NullStudent { get; } = new Student
{
Name = null,
Surname = null,
Address = Address.NullAddress,
Exam = Exam.NullExam,
}
}
You can do this for each object in your code that acts this way (you can see that I did it on your nested Address and Exam types as well) and then instead of doing this:
if (Student.EverythingIsNull) { Student = null }
if (Student is null) { //do null stuff }
You can do this:
if (item == Student.NullStudent) { //do null stuff }
This leads to clearer code, your intent stands out more, and you can specifically define in each object what constitutes as null.
using Generics and Reflection.
public T ConvertToNull<T>(T model) where T : class
{
if (model == null) return null;
Type type = model.GetType();
PropertyInfo[] properties = type.GetProperties();
var valueTypes = properties.Where(p => p.PropertyType.Assembly != type.Assembly);
var nonValueTypes = properties.Where(p => p.PropertyType.Assembly == type.Assembly);
foreach (var nonValueType in nonValueTypes)
nonValueType.SetValue(model, ConvertToNull(nonValueType.GetValue(model)));
if (valueTypes.All(z => z.GetValue(model) == null) && nonValueTypes.All(z => z.GetValue(model) == null))
return null;
else
return model;
}
Here you can call it
List<Student> students = new List<Student>();
Student student = new Student() { Name = "StudentName", Surname = "StudentSurname", Address = new Address() { City = "City", Country = "Country" }, Exam = new Exam() { ExamMark = "ExamMark", ExamName = "ExamName", Teacher = new Teacher() { TeacherName = "TeacherName", TeacherSurname = "TeacherSurname" } } };
Student student2 = new Student() { Name = "StudentName", Surname = "StudentSurname", Address = new Address(), Exam = new Exam() { ExamMark = "ExamMark", ExamName = "ExamName", Teacher = new Teacher() } };
students.Add(student);
students.Add(student2);
List<Student> results = new List<Student>();
foreach (var item in students)
{
var result = ConvertToNull(item);
results.Add(result);
}
Have a look at this The GetValueOrNull should work and do what you need, not tested with all possible use cases but it oculd be tweaked a little if doesn't work in all cases
public static bool IsSimpleType(Type type)
{
return
type.IsPrimitive ||
new Type[] {
typeof(Enum),
typeof(String),
typeof(Decimal),
typeof(DateTime),
typeof(DateTimeOffset),
typeof(TimeSpan),
typeof(Guid)
}.Contains(type) ||
Convert.GetTypeCode(type) != TypeCode.Object ||
(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>) && IsSimpleType(type.GetGenericArguments()[0]))
;
}
public object GetValueOrNull(object obj)
{
if (obj == null) return null;
Type objType = obj.GetType();
PropertyInfo[] properties = objType.GetProperties();
var simpleTypes = properties.Where(t => IsSimpleType(t.PropertyType));
var nonValueTypes = properties.Where(p => !simpleTypes.Contains(p));
foreach (var child in nonValueTypes)
{
child.SetValue(obj, GetValueOrNull(child.GetValue(obj)));
}
return simpleTypes.All(z => z.GetValue(obj) == null) && nonValueTypes.All(z => z.GetValue(obj) == null) ? null : obj;
}
call it as follows
var student = new Student { Address = new Address { }, Exam = new Exam { Teacher = new Teacher() } };
var test = GetValueOrNull(student);
hope it helps :)

Cartesian Product of Anonymous type

I am working on code which will give Cartesian product of two anonymous types. These 2 anonymous types are generated from database.
Code for 1st anonymous type:
private IEnumerable<object> GetItem()
{
return _unitOfWork.GetRepository<Item>()
.ListAll()
.Select(x => new
{
itemId = x.Id,
itemName = x.Name
})
}
Code for 2nd anonymous type:
private IEnumerable<object> GetVenue()
{
return _unitOfWork.GetRepository<Venue>()
.ListAll()
.Select(x => new
{
locationName = x.Address.City,
venueId = x.VenueId,
venueName = x.Name
})
}
I have following method to get the data and perform Cartesian product and return the data.
public object GetRestrictLookupInfo(IEnumerable<int> lookupCombinations)
{
IEnumerable<object> restrictList = new List<object>();
if (lookupCombinations.Contains(1))
{
var tempProductProfileList = GetItem();
restrictList = tempProductProfileList.AsEnumerable();
}
if (lookupCombinations.Contains(2))
{
var tempProductGroupList = GetVenue();
restrictList = (from a in restrictList.AsEnumerable()
from b in tempProductGroupList.AsEnumerable()
select new { a, b });
}
return restrictList;
}
I have controller which calls this method and return data in json format.
Controller Code
public HttpResponseMessage GetData(IEnumerable<int> lookupCombinations)
{
var lookupRestrictInfo = _sellerService.GetRestrictLookupInfo(lookupCombinations);
return Request.CreateResponse(HttpStatusCode.OK, lookupRestrictInfo);
}
Response expected is:-
[ {
"itemId": 1,
"itemName": "Music",
"locationName": "Paris",
"venueId": 99,
"venueName": "Royal Festival Hall"
} ]
Response which I receive is
[ {
"a": {
"itemId": 1,
"itemName": "Music"
},
"b": {
"locationName": "Paris",
"venueId": 99,
"venueName": "Royal Festival Hall" } }]
I am not able to get the expected JSON string.
You should start with the simplest possible code that shows your problem; your code above has a lot of complexities that may (or may not) have anything to do with your problem. Is this about manipulating anonymous types? Doing a Cartesian product with LINQ? Converting an object to JSON?
Here's one possible answer to what you might be looking for; notice that you can pass around anonymous types using generics instead of object.
namespace AnonymousTypes
{
class Program
{
static string Serialize(object o)
{
var d = (dynamic)o;
return d.ItemId.ToString() + d.ItemName + d.VenueId.ToString() + d.LocationName + d.VenueName;
}
static string GetData<T>(IEnumerable<T> result)
{
var retval = new StringBuilder();
foreach (var r in result)
retval.Append(Serialize(r));
return retval.ToString();
}
static string GetRestrictLookupInfo()
{
var restrictList = new[] { new { Id = 1, Name = "Music" }, new { Id = 2, Name = "TV" } };
var tempProductGroupList = new[] { new { LocationName = "Paris", Id = 99, Name = "Royal Festival Hall" } };
var result = from item in restrictList
from venue in tempProductGroupList
select new
{
ItemId = item.Id,
ItemName = item.Name,
LocationName = venue.LocationName,
VenueId = venue.Id,
VenueName = venue.Name
};
return GetData(result);
}
public static string GetData()
{
return GetRestrictLookupInfo();
}
static void Main(string[] args)
{
var result = GetData();
}
}
}
If that's not what you're looking for, you might start with code that doesn't use anonymous types, such as
namespace AnonymousTypes
{
sealed class Item
{
public int Id { get; set; }
public string Name { get; set; }
}
sealed class Venue
{
public string LocationName { get; set; }
public int Id { get; set; }
public string Name { get; set; }
}
sealed class ItemAndVenue
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public string LocationName { get; set; }
public int VenueId { get; set; }
public string VenueName { get; set; }
}
class Program
{
static IEnumerable<Item> GetItem()
{
return new[] { new Item { Id = 1, Name = "Music" } };
}
static IEnumerable<Venue> GetVenue()
{
return new[] { new Venue { LocationName = "Paris", Id = 99, Name = "Royal Festival Hall" } };
}
static IEnumerable<ItemAndVenue> GetRestrictLookupInfo()
{
var restrictList = GetItem();
var tempProductGroupList = GetVenue();
var result = from item in restrictList
from venue in tempProductGroupList
select new ItemAndVenue
{
ItemId = item.Id,
ItemName = item.Name,
LocationName = venue.LocationName,
VenueId = venue.Id,
VenueName = venue.Name
};
return result;
}
static string GetData()
{
var v = GetRestrictLookupInfo().First();
return v.ItemId.ToString() + v.ItemName + v.VenueId.ToString() + v.LocationName + v.VenueName;
}
static void Main(string[] args)
{
var result = GetData();
}
}
}
In order to produce a single item in the output you need to create a new type, named or anonymous. Since you are using objects rather than actual types, the quickest approach is to cast them to dynamic:
var tempProductGroupList = GetVenue();
restrictList = (from a in restrictList.Cast<dynamic>()
from b in tempProductGroupList.Cast<dynamic>()
select new {
itemId = (int)a.itemId,
itemName = (string)a.itemName,
locationName = (string)b.locationName,
venueId = (int)b.venueId,
venueName = (string)b.venueName
});
This code is tightly coupled to the code producing both lists, because it assumes the knowledge of the field names of types passed into it dynamically. Any change in the structure of source data must be followed by a change in the code making combinations. In addition, it defeats run-time checking, so you need to be very careful with this code.
Try to create a simple object instead of nesting:
select new { a.itemId, a.itemName, b.locationName }
Like an option:
public object GetRestrictLookupInfo(IEnumerable<int> lookupCombinations)
{
List<Dictionary<string, object>> result = new List<Dictionary<string, object>>();
if (lookupCombinations.Contains(1))
{
var tmp = _unitOfWork.GetRepository<Item>()
.ListAll()
.Select(x => new
{
itemId = x.Id,
itemName = x.Name
})
.Select(x =>
{
var dic = new Dictionary<string, object>();
dic.Add(nameof(x.itemId), x.itemId);
dic.Add(nameof(x.itemName), x.itemName);
return dic;
});
result.AddRange(tmp);
}
if (lookupCombinations.Contains(2))
{
var tmp = _unitOfWork.GetRepository<Venue>()
.ListAll()
.Select(x => new
{
locationName = x.Address.City,
venueId = x.VenueId,
venueName = x.Name
})
.Select(x =>
{
var dic = new Dictionary<string, object>();
dic.Add(nameof(x.locationName), x.locationName);
dic.Add(nameof(x.venueId), x.venueId);
dic.Add(nameof(x.venueName), x.venueName);
return dic;
});
result = result.SelectMany(r => tmp.Select(t => r.Concat(t)));
}
return result;
}
It looks like some magic. I uses dictionary instead of object. It can be make in more clear way (extract few methods), but the idea should be clear.
Then, during serialization it will be presented as you need.

Update/Delete a sub document in mongodb using C# driver

I have 2 classes:
public class Vote
{
public string VoteId { get; set; }
public string Question { get; set; }
public List<VoteAnswer> AnswerList { get; set; }
}
And:
public class VoteOption
{
public string OptionId { get; set; }
public string OptionName { get; set; }
public double VoteCount { get; set; }
}
How can i update/delete a VoteOption in a Vote where VoteId = voteId and OptionId = optionId? Using C# driver.
First I get VoteOption by:
var v = col.FindOneAs<Vote>(Query.EQ("VoteID", voteId));
VoteOption vo = v.AnswerList.Find(x => x.OptionId == optionId);
End set some value to it:
vo.OptionName = "some option chose";
vo.VoteCount = 1000;
But i don't know what next step to update this vo to Vote parent.
And, if i want to delete this vo, show me that way!
Data in MongoDB like that:
{
"_id" : "460b3a7ff100",
"Question" : "this is question?",
"AnswerList" : [{
"OptionId" : "1",
"OptionName" : "Option 1",
"VoteCount" : 0.0
}, {
"OptionId" : "2",
"OptionName" : "Option 2",
"VoteCount" : 0.0
}, {
"OptionId" : "3",
"OptionName" : "Option 3",
"VoteCount" : 0.0
}
}]
}
To update subdocument you can use this:
var update = Update.Set("AnswerList.$.OptionName", "new").Set("AnswerList.$.VoteCount", 5);
collection.Update(Query.And(Query.EQ("_id", new BsonObjectId("50f3c313f216ff18c01d1eb0")), Query.EQ("AnswerList.OptionId", "1")), update);
profiler:
"query" : { "_id" : ObjectId("50f3c313f216ff18c01d1eb0"), "AnswerList.OptionId" : "1" },
"updateobj" : { "$set" : { "AnswerList.$.OptionName" : "new", "AnswerList.$.VoteCount" : 5 } }
And to remove:
var pull = Update<Vote>.Pull(x => x.AnswerList, builder => builder.EQ(q => q.OptionId, "2"));
collection.Update(Query.And(Query.EQ("_id", new BsonObjectId("50f3c313f216ff18c01d1eb0")), Query.EQ("AnswerList.OptionId", "2")), pull);
profiler:
"query" : { "_id" : ObjectId("50f3c313f216ff18c01d1eb0"), "AnswerList.OptionId" : "2" },
"updateobj" : { "$pull" : { "AnswerList" : { "OptionId" : "2" } } }
Another way is to update parent document with modified child collection.
// Example function for update like count add like user using c#
public PostModel LikeComment(LikeModel like)
{
PostModel post = new PostModel();
_client = new MongoClient();
_database = _client.GetDatabase("post");
var collection = _database.GetCollection<PostModel>("post");
var _filter = Builders<PostModel>.Filter.And(
Builders<PostModel>.Filter.Where(x => x.PostId == like.PostId),
Builders<PostModel>.Filter.Eq("Comments.CommentId", like.CommentId));
var _currentLike = collection.Find(Builders<PostModel>.Filter.Eq("PostId", like.PostId)).FirstOrDefault().Comments.Find(f => f.CommentId == like.CommentId).Like;
var update = Builders<PostModel>.Update.Set("Comments.$.Like", _currentLike + 1);
collection.FindOneAndUpdate(_filter, update);
var addUser = Builders<PostModel>.Update.Push("Comments.$.LikeUsers", like.UserId);
collection.FindOneAndUpdate(_filter, addUser);
var _findResult = collection.Find(_filter).FirstOrDefault();
return _findResult;
}
//Delete comment
public PostModel delcomment(int postId, int commentId)
{
_client = new MongoClient();
_database = _client.GetDatabase("post");
var collection = _database.GetCollection<PostModel>("post");
var filter = Builders<PostModel>.Filter.Eq("PostId", postId);
var update = Builders<PostModel>.Update.PullFilter("Comments",
Builders<Comments>.Filter.Eq("CommentId", commentId));
collection.FindOneAndUpdate(filter, update);
var _findResult = collection.Find(filter).FirstOrDefault();
return _findResult;
}
Late answer but this is how you do it without having strings. If you modify your properties code will not compile. First time using expression tries in production code! They are awesome!
Models:
class Phone
{
public string _id { get; set; }
public string Name { get; set; }
public DateTime DateCreated { get; set; }
// Contain multiple lines as subdocument
public List<Line> Lines { get; set; }
}
class Line
{
public string Name { get; set; }
public string PhoneNumber { get; set; }
}
Code: this is how I create my update statements without depending on strings.
var update = new UpdateDocument<Phone>();
// set filter
update.SetFilter(x => x._id == "123456789");
update.AddValueToUpdate(p => p.Name, "New Name");
update.AddValueToUpdate(p => p.Lines[0].Name, "Line 1");
update.AddValueToUpdate(p => p.Lines[1].Name, "Line 2");
update.AddValueToUpdate(p => p.DateCreated, DateTime.UtcNow);
var updateQuery = update.Build();
This creates this! That is what you need to pass to mondo in order to do the update
{ "_id" : "123456789" },
{$set:
{"Name":"New Name","Lines.0.Name":"Line 1","Lines.1.Name":"Line 2","DateCreated":ISODate("2021-04-30T16:04:59.332Z")}
}
If you wish that code to work here are the helper classes:
using MongoDB.Bson;
using System.Linq.Expressions;
using MongoDB.Bson.Serialization;
class UpdateDocument<T>
{
/// <summary>
/// _id of document to update.
/// </summary>
private string _filter;
/// <summary>
/// Example:
/// FirstName, Antonio
/// Education.Elementary.Year, 2004
/// </summary>
private List<KeyValuePair<string, object>> _valuesToUpdate { get; set; } = new List<KeyValuePair<string, object>>();
public void SetFilter(Expression<Func<T, bool>> filterDefinition)
{
var documentSerializer = BsonSerializer.SerializerRegistry.GetSerializer<T>();
var where = Builders<T>.Filter.Where(filterDefinition).Render(documentSerializer, BsonSerializer.SerializerRegistry);
_filter = where.ToJson();
}
public void AddValueToUpdate(string name, object value)
{
_valuesToUpdate.Add(new KeyValuePair<string, object>(name, value));
}
public void AddValueToUpdate(Expression<Func<T, object>> name, object value)
{
var memberExpression = name.Body as MemberExpression;
if (memberExpression == null)
{
var unaryExpression = name.Body as UnaryExpression;
if (unaryExpression != null && unaryExpression.NodeType == ExpressionType.Convert)
memberExpression = unaryExpression.Operand as MemberExpression;
}
var result = memberExpression.ToString();
result = result.Substring(result.IndexOf('.') + 1);
if (result.Contains("get_Item"))
result = Regex.Replace(result, #"(?x) get_Item \( (\d+) \)", m => $"{m.Groups[1].Value}");
AddValueToUpdate(result, value);
}
public string Build()
{
if (_valuesToUpdate.Any() == false)
{
// nothing to update
return null;
}
/*
update({
_id: 7,
"comments._id": ObjectId("4da4e7d1590295d4eb81c0c7")
},{
$set: {"comments.$.type": abc}
}, false, true
);
*/
StringBuilder sb = new StringBuilder();
sb.Append(_filter);
sb.Append(',');
sb.Append("{");
{
sb.Append("$set:{");
foreach (var item in _valuesToUpdate)
{
sb.Append('"');
sb.Append(item.Key);
sb.Append('"');
sb.Append(':');
var value = BsonExtensionMethods.ToJson(item.Value);
sb.Append(value);
sb.Append(',');
}
// remove last comma
sb.Length--;
sb.Append('}');
}
sb.Append("}");
return sb.ToString();
}
}

Categories

Resources