I have below the object and its internal objects and I have added custom attributes to the property names which are required for the report.
public class Space
{
public SpaceIdentity SpaceIdentity { get; set; } = new();
public SpaceGeometry SpaceGeometry { get; set; } = new();
public AirBalance AirBalance { get; set; } = new();
public EngineeringChecks EngineeringChecks { get; set; } = new();
}
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number", IsIncludedInReport2 = true)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name", IsIncludedInReport2 = true, IsIncludedInReport1 = true)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number", IsIncludedInReport1 = true)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name", IsIncludedInReport1 = true)]
public string RoomName { get; set; }
}
public class SpaceGeometry
{
public Vertex LocationPoint { get; set; } // this is not required
[DisplayNameWithUnits(DisplayName = "Space Area", Units = "(ft²)", IsIncludedInReport1 = true)]
public double FloorArea { get; set; }
}
.....
Here I am building an excel report, which I want to use property display name's as header column names of that report. Here are some of the properties attribute information used in multiple reports. What I did was I added a bool condition attribute like (isIncludedInReport1) and loop through the properties of space and loop through the properties of inner object(SpaceGeometry) to get a particular property name and its attribute values based on this boolean condition.
What I am looking for here is without adding these bool attributes, is there any way to access the property names based on condition. I thought about adding interfaces, but that is not possible here because I have multiple inner classes having properties that I need to include in a single report.
Could anyone please let me know is there any other way to achieve this?
Update:
var columnResult = new OrderedDictionary();
GetReportHeaderColumnName(typeof(Space), columnResult);
public static void GetReportHeaderColumnName(Type type, OrderedDictionary headerNameByUnit)
{
var properties = type.GetProperties();
foreach (var propertyInfo in properties)
{
if (propertyInfo.PropertyType.IsClass && !propertyInfo.PropertyType.FullName.StartsWith("System."))
{
if (propertyInfo.PropertyType == typeof(Overridable<double>))
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
else
{
GetReportHeaderColumnName(propertyInfo.PropertyType, headerNameByUnit);
}
}
else
{
AddReportHeaderName(headerNameByUnit, propertyInfo);
}
}
}
protected static void AddReportHeaderName(OrderedDictionary columnResult, PropertyInfo propertyInfo)
{
if (propertyInfo.GetCustomAttributes(typeof(DisplayNameWithUnitsAttribute), true).Any())
{
var displayNameWithUnitsAttribute = (DisplayNameWithUnitsAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(DisplayNameWithUnitsAttribute));
if (displayNameWithUnitsAttribute.IsIncludedInReport2)
{
columnResult.Add(displayNameWithUnitsAttribute.DisplayName, displayNameWithUnitsAttribute.Units);
}
}
}
Another way that doesn't use reflection is to just use a list of ReportProperty
public record ReportProperty<T>(
Func<T, string> ValueFunc,
string DisplayName,
string? Unit = null
);
List<ReportProperty<Space>> report1 = new(){
new( s => s.SpaceIdentity.Number, "Space Number"),
new( s => s.SpaceIdentity.RoomNumber, "Room Number"),
new( s => s.SpaceIdentity.RoomName, "Room Name"),
new( s => s.SpaceGeometry.FloorArea.ToString(), "Floor Area", "Ft2"),
};
What do you want to achieve by making it another way? I assume you want to make your code scalable so adding more reports is easier.
Do you still want to use attributes on the classes themselves? If so, you could make a new attribute, and tag your properties like so:
public class SpaceIdentity
{
public int ElementId { get; set; } // Not required
[DisplayNameWithUnits(DisplayName = "Space Number")]
[IncludeInReport(2)]
public string Number { get; set; }
[DisplayNameWithUnits(DisplayName = "Space Name")]
[IncludeInReport(1)]
[IncludeInReport(2)]
public string Name { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Number")]
[IncludeInReport(1)]
public string RoomNumber { get; set; }
[DisplayNameWithUnits(DisplayName = "Room Name")]
[IncludeInReport(1)]
public string RoomName { get; set; }
}
Or do you want to separate the report from the classes, so your report is defined in another file? If so, maybe a 2-dimensional list of property names would do the trick:
List<List<string>> Report1 = new()
{
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.Name)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomNumber)},
new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomName)},
new(){nameof(Space.SpaceGeometry), nameof(SpaceGeometry.FloorArea)}
};
Related
I'm basically trying to use reflection to flatten any class into a dictionary so that I can generically use and bind them in Blazor. I then need to be able to create an instance of the class and populate it with the data from the dictionary (which will have been updated by a component).
e.g
public class Order
{
public Guid Id { get; set; }
public Customer Customer { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public List<string> Test { get; set; }
public List<Test> Test2 { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
public Gender Gender { get; set; }
public List<string> Test { get; set; }
}
Should become:
{
"Id": "",
"Customer.FirstName": "",
"Customer.LastName": "",
"Customer.Gender": "",
"Customer.Test": "",
"Address": "",
"Postcode": "",
"Test": "",
"Test2": ""
}
For some reason when I iterate the properties of the Order class, Test2 is missed. The loop shows the property in the collection when I put a breakpoint, it just seems to skip it. I've never seen this happen before.
Code: https://dotnetfiddle.net/g1qyVQ
I also don't think the current code with handle further nested depth which I would like it to be able to work with any POCO object really.
Also if anyone knows a better way to do what I'm trying, I would love to find an easier way. Thanks
First of all, good job on linking the code sample. Without that, I would have passed by this question in about three seconds. :D
In GetAllProperties(), your entire loop is inside a giant try catch block, where the catch returns the dictionary as it is so far, without checking what the exception is. So if you don't get everything you expect, you've probably hit an error.
Amend the catch block:
catch (Exception ex) { Console.WriteLine(ex.ToString()); return result; }
Now, you can see the problem:
System.ArgumentException: An item with the same key has already been added. Key: Test
Your object has more than one property named "Test," but Keys in a Dictionary must be unique.
Summary: Errors aren't the enemy, they're your best friend. Don't use try / catch to bypass errors. If you do, you may get "mysterious, never seen that happen before!" results.
For anyone interested, here is where I'm at now:
https://dotnetfiddle.net/3ORKNs
using JsonFlatten;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text.Json;
namespace RecursiveClassProperties
{
public static class Program
{
static void Main(string[] args)
{
var item = CreateDefaultItem(typeof(Order));
Console.WriteLine(JsonSerializer.Serialize(item, new JsonSerializerOptions { WriteIndented = true }));
var json = JsonSerializer.Serialize(item);
var properties = JObject.Parse(json).Flatten();
Console.WriteLine(JsonSerializer.Serialize(properties, new JsonSerializerOptions { WriteIndented = true }));
var formProperties = properties.ToDictionary(x => x.Key, x => new FormResponse(string.Empty));
Console.WriteLine(JsonSerializer.Serialize(formProperties, new JsonSerializerOptions { WriteIndented = true }));
}
private static object CreateFormItem(Type type, Dictionary<string, FormResponse> formProperties, object result = null)
{
result = CreateDefaultItem(type);
return result;
}
private static object CreateDefaultItem(Type type, object result = null, object nested = null, bool isBase = false)
{
void SetProperty(PropertyInfo property, object instance)
{
if (property.PropertyType == typeof(string)) property.SetValue(instance, string.Empty);
if (property.PropertyType.IsEnum) property.SetValue(instance, 0);
if (property.PropertyType == typeof(Guid)) property.SetValue(instance, Guid.Empty);
}
if (result is null)
{
result = Activator.CreateInstance(type);
isBase = true;
}
var properties = type.GetProperties();
foreach (var property in properties)
{
if (!Attribute.IsDefined(property, typeof(FormIgnoreAttribute)) && property.GetSetMethod() is not null)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsEnum || property.PropertyType == typeof(Guid))
{
if (isBase) SetProperty(property, result);
else if (nested is not null && nested.GetType() is not IList && !nested.GetType().IsGenericType) SetProperty(property, nested);
}
else
{
var _nested = default(object);
if (isBase)
{
property.SetValue(result, Activator.CreateInstance(property.PropertyType));
_nested = property.GetValue(result);
}
if (nested is not null)
{
property.SetValue(nested, Activator.CreateInstance(property.PropertyType));
_nested = property.GetValue(nested);
}
CreateDefaultItem(property.PropertyType, result, _nested);
}
}
}
return result;
}
}
[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
public class FormIgnoreAttribute : Attribute { }
public class FormResponse
{
public FormResponse(string value) => Value = value;
public string Value { get; set; }
}
public class Order
{
public Guid Id { get; set; }
public Customer Customer { get; set; }
public string Address { get; set; }
public string Postcode { get; set; }
public Test Test { get; set; }
public List<Gender> Genders { get; set; }
public List<string> Tests { get; set; }
}
public enum Gender
{
Male,
Female
}
public class Test
{
public string Value { get; set; }
public List<Gender> Genders { get; set; }
public List<string> Tests { get; set; }
}
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string FullName => $"{FirstName} {LastName}";
public Gender Gender { get; set; }
public Test Test { get; set; }
public List<Gender> Genders { get; set; }
public List<string> Tests { get; set; }
}
}
The idea is that I can assign values to formProperties, pass it to CreateFormItem() and get a populated object back. The reason I'm doing this is because I have a Blazor component Table which has a typeparam TItem, basically think of it as Table<TItem> for those unfamiliar with Blazor. The table is then supplied a list of objects which it can then render.
Flattening the object in this way will both allow me to easily display all properties and subproperties of the class in the table, but most importantly bind the input of a "new item" form which will return the new object to a delegate outside of the component (back in normal .NET) to submit to a creation controller (to put it in the DB). The reason having a Dictionary<string, FormResponse> is important is that with a generic type, you aren't able to bind the input of the form to the "model". You are however able to bind the input to a string property of a class, even if it's not a string. Hence FormResponse.Value.
I will next need to have CreateFormItem() return the object with the actual data from the form. Sorry if this is a bit longwinded, couldn't think of a more concise way to explain it.
Thanks :)
This question already has answers here:
multiple JsonProperty Name assigned to single property
(5 answers)
Deserializing polymorphic json classes without type information using json.net
(6 answers)
Closed 2 years ago.
So, I try to deserialize JSON file that has a format
"farmableitem": [
{
"#uniquename": "T1_FARM_CARROT_SEED",
"#tier": "1",
"#kind": "plant",
"#weight": "0.1",
"harvest": {
"#growtime": "79200",
"#lootlist": "T1_CARROT_LOOT",
"#lootchance": "1",
"#fame": "100",
"seed": {
"#chance": "0.0000",
"#amount": "1"
}
}
},
{
"#uniquename": "T5_FARM_OX_BABY",
"#tier": "5",
"#kind": "animal",
"#weight": "1.5",
"grownitem": {
"#uniquename": "T5_FARM_OX_GROWN",
"#growtime": "504000",
"#fame": "300",
"offspring": {
"#chance": "0.7867",
"#amount": "1"
}
}
]
As you can see, "harvest" and "grownitem", as well as, "seed" and "offspring" are basically the same...
So there is no reason for me to create two different classes.
I know that if you want to give a specific field two different PropertyNames you can do the next:
[JsonProperty("first")]
public string Test { get; set; }
[JsonProperty("second")]
private string _test { set { Test = value; } }
However, I'm not sure that I can do the same thing for the classes... At least I couldn't find a solution.
So, long story short, I want to have two different JsonPropertyName for one class. Is it possible?
Long story short... I fixed it, however, my solution looks like a F*** Band-Aid... So, if you have any suggestions on how I can optimize the snippet that I've written, I would really appreciate your help!
public class Item {
[JsonProperty(PropertyName = "#uniquename")]
public string UniqueName { get; set; }
[JsonProperty(PropertyName = "#tier")]
public string Tier { get; set; }
[JsonProperty(PropertyName = "#weight")]
public string Weight { get; set; }
[JsonProperty(PropertyName = "#shopcategory")]
public string Category { get; set; }
[JsonProperty(PropertyName = "#shopsubcategory1")]
public string SubCategory { get; set; }
}
public class FarmableItem : Item
{
[JsonProperty(PropertyName = "#kind")]
public string Kind { get; set; }
private class HarvestModel
{
[JsonProperty(PropertyName = "#growtime")]
public string GrowTime { get; set; }
[JsonProperty(PropertyName = "#lootlist")]
public string UniqueName { get; set; }
[JsonProperty(PropertyName = "#fame")]
public string Fame { get; set; }
public class HarvestedItemModel
{
[JsonProperty(PropertyName = "#chance")]
public string SuccessRate { get; set; }
[JsonProperty(PropertyName = "#amount")]
public string Amount { get; set; }
}
public class GrownItemModel : HarvestedItemModel {}
[JsonProperty(PropertyName = "seed")]
public HarvestedItemModel HarvestedItem = new HarvestedItemModel();
[JsonProperty(PropertyName = "offspring")]
public GrownItemModel GrownItem = new GrownItemModel();
}
private class GrowModel : HarvestModel {}
[JsonProperty("harvest")]
private HarvestModel Harvest = new HarvestModel();
[JsonProperty("grownitem")]
private GrowModel Grow = new GrowModel();
public string GrowTime => Harvest.UniqueName == null ? Grow.GrowTime : Harvest.GrowTime;
public string Loot => Harvest.UniqueName == null ? Grow.UniqueName : Harvest.UniqueName;
public string Fame => Harvest.UniqueName == null ? Grow.Fame : Harvest.Fame;
public string SuccessChance => Harvest.UniqueName == null ? Grow.GrownItem.SuccessRate : Harvest.HarvestedItem.SuccessRate;
public string Amount => Harvest.UniqueName == null ? Grow.GrownItem.Amount : Harvest.HarvestedItem.Amount;
}
My logic => UniqueName always exists, so we can use it as a test to differentiate between "harvest" and "grownitem", and since "seed" and "offspring" corresponds to "harvest" and "grownitem" consequently, we can also use it to check for "seed" and "offspring".
The JSON data is as follows:
{"Sucess":true,
"Code":0,
"Msg":"Sucess",
"Data":{
"UserDayRanking":
{
"UserID":11452112,
"UserCharm":0,
"UserName":"gay",
"UserGender":1,
"UserLevel":36,
"UserPhoto":"http://res.xxx.com/2020/3/16/63719926625601201487545U11452112.jpeg",
"Ranking":0,
"IsNobility":0,
"NobilityType":0,
"NobilityLevel":0,
"UserShowStyle":null,
"LiveLevelUrl":null,
"IsStealth":false},
"DayRankingList":[
{
"UserID":3974854,
"UserCharm":114858,
"UserName":"jack",
"UserGender":1,
"UserLevel":91,
"UserPhoto":"http://res.xxx.com/2020/2/15/63717400601924412312384U3974854.jpeg",
"Ranking":2,
"IsNobility":1,
"NobilityType":1,
"NobilityLevel":3,
"UserShowStyle":
{
"NameColor":100102,
"BorderColor":100403,
"LiangMedal":0,
"DztCountDown":0,
"Mounts":100204,
"LiveLevelCode":0,
"LiveRights":null
},
"LiveLevelUrl":null,
"IsStealth":false
},
{"UserID":6231512,
"UserCharm":22644,
"UserName":"red.girl",
"UserGender":1,
"UserLevel":57,
"UserPhoto":"http://res.xxx.com/2019/11/20/63709843050801519858823U6231512.jpeg",
"Ranking":3,
"IsNobility":0,
"NobilityType":0,
"NobilityLevel":0,
"UserShowStyle":{
"NameColor":0,
"BorderColor":0,
"LiangMedal":0,
"DztCountDown":0,
"Mounts":0,
"LiveLevelCode":0,
"LiveRights":null
},
"LiveLevelUrl":null,
"IsStealth":false}
],
"LiveCharmSwitch":1,
"IsSelf":false
}
}
I want to use c # extraction
"UserID": 3974854,
"UserCharm": 114858,
"UserName": "jack",
"UserID":6231512,
"UserCharm":22644,
"UserName":"red.girl",
That is to extract UserID, UserCharm, UserName,This json has many layers,
What I want after the extraction is,id is sorted in order
id = 1, UserID = 3974854, UserCharm = 114858, UserName = jack
id = 2, UserID = 6231512, UserCharm = 22644, UserName = red.girl
I use the following code, but only extract the first one
string json = #"{"Sucess":true,"Code":0,"Msg":"Sucess","Data":{"UserDayRanking":{"UserID":11452112,"UserCharm":0,"UserName":"gay","UserGender":1,"UserLevel":36,"UserPhoto":"http://res.xxx.com/2020/3/16/63719926625601201487545U11452112.jpeg","Ranking":0,"IsNobility":0,"NobilityType":0,"NobilityLevel":0,"UserShowStyle":null,"LiveLevelUrl":null,"IsStealth":false},"DayRankingList":[{"UserID":3974854,"UserCharm":114858,"UserName":"jack","UserGender":1,"UserLevel":91,"UserPhoto":"http://res.xxx.com/2020/2/15/63717400601924412312384U3974854.jpeg","Ranking":2,"IsNobility":1,"NobilityType":1,"NobilityLevel":3,"UserShowStyle":{"NameColor":100102,"BorderColor":100403,"LiangMedal":0,"DztCountDown":0,"Mounts":100204,"LiveLevelCode":0,"LiveRights":null},"LiveLevelUrl":null,"IsStealth":false},{"UserID":6231512,"UserCharm":22644,"UserName":"red.girl","UserGender":1,"UserLevel":57,"UserPhoto":"http://res.xxx.com/2019/11/20/63709843050801519858823U6231512.jpeg","Ranking":3,"IsNobility":0,"NobilityType":0,"NobilityLevel":0,"UserShowStyle":{"NameColor":0,"BorderColor":0,"LiangMedal":0,"DztCountDown":0,"Mounts":0,"LiveLevelCode":0,"LiveRights":null},"LiveLevelUrl":null,"IsStealth":false}],"LiveCharmSwitch":1,"IsSelf":false}}";
List<Info> jobInfoList = JsonConvert.DeserializeObject<List<Info>>(z);
foreach (Info jobInfo in jobInfoList)
{
//Console.WriteLine("UserName:" + jobInfo.UserName);
}
public class Info
{
public string UserCharm { get; set; }
public string UserName { get; set; }
public data DayRankingList { get; set; }
}
public class data
{
public int UserID { get; set; }
public string UserCharm { get; set; }
public string UserName { get; set; }
public string UserGender { get; set; }
public string UserLevel { get; set; }
}
The above code only shows username = jack,Never show username = red.girl
As it looks to me then you want some details from your JSON has the which is in DayRankingList. As you only want some data then we can use a tool like http://json2csharp.com/ to create our classes and then remove what we don't need. Then we end up with the following classes.
public class DayRankingList
{
public int UserID { get; set; }
public int UserCharm { get; set; }
public string UserName { get; set; }
}
public class Data
{
public List<DayRankingList> DayRankingList { get; set; }
}
public class RootObject
{
public Data Data { get; set; }
}
Which you can deserialise like this
string json = .....
var root = JsonConvert.DeserializeObject<RootObject>(json);
Then if you wish, you can extract the inner data into a new List<> and then just work on that.
List<DayRankingList> rankingLists = root.Data.DayRankingList;
//Do something with this, such as output it
foreach(DayRankingList drl in rankingLists)
{
Console.WriteLine(String.Format("UserId {0} UserCharm {1} UserName {2}",drl.UserId, drl.UserCharm, drl.UserName));
}
You can use Json.Linq to parse your JSON into JObject and enumerate DayRankingList items (since it's an array). Then convert every item into data class and order the result sequence by UserID
var jObject = JObject.Parse(json);
var rankingList = (jObject["Data"] as JObject)?.Property("DayRankingList");
var list = rankingList.Value
.Select(rank => rank.ToObject<data>())
.OrderBy(item => item?.UserID);
foreach (var user in list)
Console.WriteLine($"{user.UserID} {user.UserName}");
Another way is copy your JSON, go to Edit->Paste Special->Paste JSON as classes menu in Visual Studio and generate a proper class hierarchy (I've got 5 classes, they are quite long to post here), then use them during deserialization
The most type-safe way is to define the class structure that you want, like jason.kaisersmith suggested.
To have the final format you need, though, you might want to do an extra Linq Order and Select, to include the id:
var finalList = rankingLists.OrderBy(rl => rl.UserId).Select((value, index) => new
{
id = index,
value.UserId,
value.UserCharm,
value.UserName
});
foreach (var drl in finalList)
{
Console.WriteLine($"Id = {drl.id}, UserId = {drl.UserId}, UserCharm = {drl.UserCharm}, UserName = {drl.UserName}");
}
I am cassandra for custom logging my .netcore project, i am using CassandraCSharpDriver.
Problem:
I have created UDT for params in log, and added list of paramUDT in Log table as frozen.
But i am getting error: Non-frozen UDTs are not allowed inside collections. I don't know why ia m getting this error because i am using Frozen attribute on list i am using in Log Model.
logSession.Execute($"CREATE TYPE IF NOT EXISTS {options.Keyspaces.Log}.{nameof(LogParamsCUDT)} (Key text, ValueString text);");
Here is model:
public class Log
{
public int LoggingLevel { get; set; }
public Guid UserId { get; set; }
public string TimeZone { get; set; }
public string Text { get; set; }
[Frozen]
public IEnumerable<LogParamsCUDT> LogParams { get; set; }
}
Question where i am doing wrong, is my UDT script not correct or need to change in model.
Thanks in advance
I've tried using that model and Table.CreateIfNotExists ran successfully.
Here is the the code:
public class Program
{
public static void Main()
{
var cluster = Cluster.Builder().AddContactPoint("127.0.0.1").Build();
var session = cluster.Connect();
session.CreateKeyspaceIfNotExists("testks");
session.ChangeKeyspace("testks");
session.Execute($"CREATE TYPE IF NOT EXISTS testks.{nameof(LogParamsCUDT)} (Key text, ValueString text);");
session.UserDefinedTypes.Define(UdtMap.For<LogParamsCUDT>($"{nameof(LogParamsCUDT)}", "testks"));
var table = new Table<Log>(session);
table.CreateIfNotExists();
table.Insert(new Log
{
LoggingLevel = 1,
UserId = Guid.NewGuid(),
TimeZone = "123",
Text = "123",
LogParams = new List<LogParamsCUDT>
{
new LogParamsCUDT
{
Key = "123",
ValueString = "321"
}
}
}).Execute();
var result = table.First(l => l.Text == "123").Execute();
Console.WriteLine(JsonConvert.SerializeObject(result));
Console.ReadLine();
table.Where(l => l.Text == "123").Delete().Execute();
}
}
public class Log
{
public int LoggingLevel { get; set; }
public Guid UserId { get; set; }
public string TimeZone { get; set; }
[Cassandra.Mapping.Attributes.PartitionKey]
public string Text { get; set; }
[Frozen]
public IEnumerable<LogParamsCUDT> LogParams { get; set; }
}
public class LogParamsCUDT
{
public string Key { get; set; }
public string ValueString { get; set; }
}
Note that I had to add the PartitionKey attribute or else it wouldn't run.
Here is the CQL statement that it generated:
CREATE TABLE Log (
LoggingLevel int,
UserId uuid,
TimeZone text,
Text text,
LogParams frozen<list<"testks"."logparamscudt">>,
PRIMARY KEY (Text)
)
If I remove the Frozen attribute, then this error occurs: Cassandra.InvalidQueryException: 'Non-frozen collections are not allowed inside collections: list<testks.logparamscudt>'.
If your intention is to have a column like this LogParams frozen<list<"testks"."logparamscudt">> then the Frozen attribute will work. If instead you want only the UDT to be frozen, i.e., LogParams list<frozen<"testks"."logparamscudt">>, then AFAIK the Frozen attribute won't work and you can't rely on the driver to generate the CREATE statement for you.
All my testing was done against cassandra 3.0.18 using the latest C# driver (3.10.1).
My data is having following structure
public enum ParamType
{
Integer=1,
String=2,
Boolean=3,
Double=4
}
public class Gateway
{
public int _id { get; set; }
public string SerialNumber { get; set; }
public List<Device> Devices { get; set; }
}
public class Device
{
public string DeviceName { get; set; }
public List<Parameter> Parameters { get; set; }
}
public class Parameter
{
public string ParamName { get; set; }
public ParamType ParamType { get; set; }
public string Value { get; set; }
}
I filled 10 document objects of Gateway in a MongoDB database.
Now I want to query all those gateways which contains a device having Parameter with ParamName as "Target Temperature" and whose Value > 15.
I created following queries
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => int.Parse(p.Value), 15));
var deviceQuery = Query<Device>.ElemMatch(d => d.Parameters, builder => parameterQuery);
var finalQuery = Query<Gateway>.ElemMatch(g => g.Devices, builder => deviceQuery);
But when I run this, it is giving an exception
Unable to determine the serialization information for the expression: (Parameter p) => Int32.Parse(p.Value)
Please suggest where I am wrong.
As the error suggests, you can't use Int32.Parse inside your query. This lambda expression is used to get out the name of the property and it doesn't understand what Int32.Parse is.
If you are querying a string, you need to use a string value for comparison:
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => p.Value, "15"));
However, that's probably not what you want to do since you're using GT. To be treated as a number for this comparison you need the value to actually be an int in mongo, so you would need to change the type of your property:
public class Parameter
{
public string ParamName { get; set; }
public ParamType ParamType { get; set; }
public int Value { get; set; }
}
var parameterQuery = Query.And(Query<Parameter>.EQ(p => p.ParamName, "Target Temperature"), Query<Parameter>.GT(p => p.Value, 15));
Summary
I ran into this when I was modifying a list. It appears that Linq First/FirstOrDefault was not handled well by MongoDB for me. I changed to be an array index var update = Builders<Movie>.Update.Set(movie => movie.Movies[0].MovieName, "Star Wars: A New Hope"); Note: This is in Asp.Net 5 using MongoDB.Driver 2.2.0.
Full Example
public static void TypedUpdateExample() {
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("test");
var collection = database.GetCollection<Movie>("samples");
//Create some sample data
var movies = new Movie {
Name = "TJ",
Movies = new List<MovieData>
{
new MovieData {
MovieName = "Star Wars: The force awakens"
}
}
};
collection.InsertOne(movies);
//create a filter to retreive the sample data
var filter = Builders<Movie>.Filter.Eq("_id", movies.Id);
//var update = Builders<Movie>.Update.Set("name", "A Different Name");
//TODO:LP:TSTUDE:Check for empty movies
var update = Builders<Movie>.Update.Set(movie => movie.Movies[0].MovieName, "Star Wars: A New Hope");
collection.UpdateOne(filter, update);
}
public class Movie {
[BsonId]
public ObjectId Id { get; set; }
public string Name { get; set; }
public List<MovieData> Movies { get; set; }
}
public class MovieData {
[BsonId]
public ObjectId Id { get; set; }
public string MovieName { get; set; }
}