Store Object with Dynamic property in Cosmos DB - c#

I have a message processor where I would like to take a lump of json with a wrapper of known schema, but with a property that is a dynamic object like the following:
public class NotificationDetails
{
[JsonProperty(PropertyName = "id")]
public string NotificationID { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateSent { get; set; }
public string TemplateUrl { get; set; }
public dynamic Model { get; set; }
}
as you can see, the last property is of dynamic. the notifications will all have different Model schemas, so I would like it to just be stored as a nested object.
That said, when I attempt to Create the object via
client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, collectionId), item)
I get the following error message:
The best overloaded method match for 'MyClass.CreateNotification(NotificationDetails))' has some invalid arguments
I thought I could throw anything into these docs. what am I doing wrong? should I use something other than dynamic for this Model property?
UPDATE I figured out it was something about how I was calling the Wait() method on the task returned from the DocumentClient. Once I reverted to the async await strategy it started working correctly.

According to your description. I have tested your code and it worked as below. You can refer to what I did:
public static void CreateCosmosDocument()
{
DocumentClient client = new DocumentClient(new Uri("https://xxxxx/"), "C2y6yDjf5/R+ob0N8A7Cgv30VRDJxxxxM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==", new ConnectionPolicy { EnableEndpointDiscovery = false });
TestEntity testEntity = new TestEntity { x = 11, y = 11, name = "wakaka", dynam = "hello dynam" };
var createdItem = client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri("ToDoList", "Items"), new NotificationDetails { DateCreated=DateTime.Now, DateSent=DateTime.Now, TemplateUrl="www.baidu.com", Model= testEntity });
}
Class of NotificationDetails:
public class NotificationDetails
{
[JsonProperty(PropertyName = "id")]
public string NotificationID { get; set; }
public DateTime? DateCreated { get; set; }
public DateTime? DateSent { get; set; }
public string TemplateUrl { get; set; }
public dynamic Model { get; set; }
}
Class of TestEntity which act as a nested object:
class TestEntity
{
public ObjectId _id { get; set; }
public string name { get; set; }
public double x { get; set; }
public double y { get; set; }
public double z { get; set; }
public dynamic dynam { get; set; }
}
Screenshot of result:
If the error still occured, you'd better share your more detailed code with us for further research.

Related

Automapper executes without error, but no data being copied from source to destination

I have a class like this
public class ListOfBMTTeamMapping
{
public class TeamMapping
{
public List<TeamMappings> results { get; set; }
}
public class TeamMappings
{
public int id { get; set; }
public string areaPath { get; set; }
public string agileReleaseTrainName { get; set; }
public string deliveryTeamName { get; set; }
public string keyedInTeamCode { get; set; }
public string deliveryTeamId { get; set; }
public bool isDeleted { get; set; }
public string modified { get; set; }
public string modifiedBy { get; set; }
}
}
And here is my model class to which I need the above API class to get copied
public class JsonBmtAdoMapping
{
public int? Id { get; set; }
public string AreaPath { get; set; }
public string AgileReleaseTrainName { get; set; }
public string DeliveryTeamName { get; set; }
public string KeyedInTeamCode { get; set; }
public string DeliveryTeamId { get; set; }
public string IsDeleted { get; set; }
public DateTime? Modified { get; set; }
public string ModifiedBy { get; set; }
}
So here is my code I tried
var format = "dd/MM/yyyy";
var dateTimeConverter = new IsoDateTimeConverter { DateTimeFormat = format };
ListOfBMTTeamMapping.TeamMapping Results = new ListOfBMTTeamMapping.TeamMapping();
Results = JsonConvert.DeserializeObject<ListOfBMTTeamMapping.TeamMapping>(responseBody);
List<JsonBmtAdoMapping> jM = new List<JsonBmtAdoMapping>();
jM = _mapper.Map<ListOfBMTTeamMapping.TeamMapping,List<JsonBmtAdoMapping>>(Results);
int n = 10;
And here is my automapper profile
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>();
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
But when the code executes, Ofcourse I am getting the data in results variable without any trouble
But when the mapper code fires, it execute the line without any error, but no data being copied from source to my model class which is the destination
jM.count is always 0 when Results hold 124 rows of data
What I did wrong
Your mapping from TeamMapping to List<JsonBmtAdoMapping> can't be done out of the box by AutoMapper, because your source is an object with a property that contains the list and the destination is a list on itself.
So you have to tell him, how this conversion from a single object to a list can be done. Due to the fact, that you already have a mapping for each individual item, we can use that recursively within our mapping method.
By using this mapping, it should work:
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>()
.ConvertUsing((src, _, context) => src.results.Select(context.Mapper.Map<JsonBmtAdoMapping>).ToList());
Update
Cause a mapper is already defined for the individual items and lists are handled automatically by AutoMapper we can even make it shorter (thanks for Lucian for the hint in the comments):
CreateMap<ListOfBMTTeamMapping.TeamMappings, JsonBmtAdoMapping>();
CreateMap<ListOfBMTTeamMapping.TeamMapping, List<JsonBmtAdoMapping>>()
.ConvertUsing((src, _, context) => context.Mapper.Map<List<JsonBmtAdoMapping>>(src.results));

JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than

In my web API when I run project to get data from the database got this error
.net core 3.1
JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
These are my codes:
my Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
my productCategory class is:
public class ProductCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string CatText { get; set; }
public string ImagePath { get; set; }
public int Priority { get; set; }
public int Viewd { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
public DateTime CreateDate { get; set; }
public DateTime ModifyDate { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
my repo is
public async Task<IList<Product>> GetAllProductAsync()
{
return await _context.Products.Include(p => p.ProductCategory).ToListAsync();
}
my interface
public interface IProductRepository
{
...
Task<IList<Product>> GetAllProductAsync();
...
}
and this is my controller in api project
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductsController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
[HttpGet]
public ActionResult Get()
{
return Ok(_productRepository.GetAllProduct());
}
}
When I run API project and put this URL: https://localhost:44397/api/products
I got that error,
I can't resolve it
this is happening because your data have a reference loop.
e.g
// this example creates a reference loop
var p = new Product()
{
ProductCategory = new ProductCategory()
{ products = new List<Product>() }
};
p.ProductCategory.products.Add(p); // <- this create the loop
var x = JsonSerializer.Serialize(p); // A possible object cycle was detected ...
You can not handle the reference loop situation in the new System.Text.Json yet (netcore 3.1.1) unless you completely ignore a reference and its not a good idea always. (using [JsonIgnore] attribute)
but you have two options to fix this.
you can use Newtonsoft.Json in your project instead of System.Text.Json (i linked an article for you)
Download the System.Text.Json preview package version 5.0.0-alpha.1.20071.1 from dotnet5 gallery (through Visual Studio's NuGet client):
option 1 usage:
services.AddMvc()
.AddNewtonsoftJson(
options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
// if you not using .AddMvc use these methods instead
//services.AddControllers().AddNewtonsoftJson(...);
//services.AddControllersWithViews().AddNewtonsoftJson(...);
//services.AddRazorPages().AddNewtonsoftJson(...);
option 2 usage:
// for manual serializer
var options = new JsonSerializerOptions
{
ReferenceHandling = ReferenceHandling.Preserve
};
string json = JsonSerializer.Serialize(objectWithLoops, options);
// -----------------------------------------
// for asp.net core 3.1 (globaly)
services.AddMvc()
.AddJsonOptions(o => {
o.JsonSerializerOptions
.ReferenceHandling = ReferenceHandling.Preserve
});
these serializers have ReferenceLoopHandling feature.
Edit : ReferenceHandling changed to ReferenceHandler in DotNet 5
but if you decide to just ignore one reference use [JsonIgnore] on one of these properties. but it causes null result on your API response for that field even when you don't have a reference loop.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
// [JsonIgnore] HERE or
public virtual ProductCategory ProductCategory { get; set; }
}
public class ProductCategory
{
public int Id { get; set; }
// [JsonIgnore] or HERE
public ICollection<Product> products {get;set;}
}
.NET 5 Web API
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddControllers()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
}
I have the same issue, my fix was to add async and await keyword since I am calling an async method on my business logic.
Here is my original code:
[HttpGet]
public IActionResult Get()
{
//This is async method and I am not using await and async feature .NET which triggers the error
var results = _repository.GetAllDataAsync();
return Ok(results);
}
To this one:
HttpGet]
public async Task<IActionResult> Get()
{
var results = await _repository.GetAllDataAsync();
return Ok(results);
}
In .Net 6, you can use System.Text.Json to initialize a startup action with AddControllersWithViews like this in Program.cs,
using System.Text.Json.Serialization;
builder.Services.AddControllersWithViews()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
also you can use AddMvc like this,
builder.Services.AddMvc()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
but quote from Ryan
asp.net core 3.0+ template use these new
methodsAddControllersWithViews,AddRazorPages,AddControllers instead of
AddMvc.
I will recommend to use the first solution.
Ensure you have [JsonIgnore] on the correct fields to avoid a circular reference.
In this case you will need
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
You probably don't need the ProductCategoryId field (depends if you are using EF and code first to define your DB)
Edit - In answer to noruk
There is often confusion in connected objects and navigation properties. You can get the data you want in JSON but also define the EF structures to get the correct DB structure (foreign keys, indexes, etc).
Take this simple example. A Product (for example a T-Shirt) has many sizes or SKUs (e.g. Small, Large, etc)
public class Product
{
[Key]
[MaxLength(50)]
public string Style { get; set; }
[MaxLength(255)]
public string Description { get; set; }
public List<Sku> Skus { get; set; }
}
public class Sku
{
[Key]
[MaxLength(50)]
public string Sku { get; set; }
[MaxLength(50)]
public string Barcode { get; set; }
public string Size { get; set; }
public decimal Price { get; set; }
// One to Many for Product
[JsonIgnore]
public Product Product { get; set; }
}
Here you can serialise a Product and the JSON data will include the SKUs. This is the normal way of doing things.
However if you serialise a SKU you will NOT get it's parent product. Including the navigation property will send you into the dreaded loop and throw the "object cycle was detected" error.
I know this is limiting in some use cases but I would suggest you follow this pattern and if you want the parent object available you fetch it separately based on the child.
var parent = dbContext.SKUs.Include(p => p.Product).First(s => s.Sku == "MY SKU").Product
I fixed my API Core Net6.0 adding [JsonIgnore]:
public class SubCategoryDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Image { get; set; }
public int CategoryId { get; set; }
[JsonIgnore]
public Category Category { get; set; }
}
For net core 3.1 you have to add in Startup.cs:
services.AddMvc.AddJsonOptions(o => {
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
o.JsonSerializerOptions.MaxDepth = 0;
})
and import at least this package using nuget.org include prerelease:
<PackageReference Include="System.Text.Json" Version="5.0.0-rc.1.20451.14" />
following code is working for me in dotnet 5.0 :
services.AddControllersWithViews()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
Finally fixed mine with System.Text.Json not NewtonSoft.Json using
var options = new JsonSerializerOptions()
{
MaxDepth = 0,
IgnoreNullValues = true,
IgnoreReadOnlyProperties = true
};
Using options to serialize
objstr = JsonSerializer.Serialize(obj,options);
My project built with a similar error.
Here's the code before
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
public PrimaryClass primaryClass { get; set; }
}
I took away the PrimaryClass object from the DependentClass model.
Code after
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
}
I also had to adjust the OnModelCreating method from
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses).WithOne(d => d.primaryClass).HasForeignKey(d => d.PrimaryClassId);
to
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses);
The DbSet query that's running is
public async Task<List<DependentClass>> GetPrimaryClassDependentClasses(PrimaryClass p)
{
return await _dbContext.DependentClass.Where(dep => dep.PrimaryClassId == p.PrimaryClassId).ToListAsync();
}
The error could have been with any of these 3 sections of code, but removing the primary object reference from the dependent class and adjusting the OnModelCreating resolved the error, I'm just not sure why that would cause a cycle.
In my case the problem was when creating the entity relationships. I linked the main entity using a foreign key inside the dependent entity like this
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
also I referred the dipendend entity inside the main entity as well.
public List<Device> devices { get; set; }
which created a cycle.
Dependent Entity
public class Device
{
[Key]
public int id { get; set; }
public int asset_number { get; set; }
public string brand { get; set; }
public string model_name { get; set; }
public string model_no { get; set; }
public string serial_no { get; set; }
public string os { get; set; }
public string os_version { get; set; }
public string note { get; set; }
public bool shared { get; set; }
public int week_limit { get; set; }
public bool auto_acceptance { get; set; }
public bool booking_availability { get; set; }
public bool hide_device { get; set; }
public bool last_booked_id { get; set; }
//getting the relationships category 1 to many
public int category_id { get; set; }
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
public List<Booking> Bookings { get; set; }
}
Main Entity
public class Device_Category
{
public int id { get; set; }
public string name { get; set; }
public List<Device> devices { get; set; }
}
}
So I commented the
public List<Device> devices { get; set; }
inside main entity (Device_Category) and problem solved

Mapping from JSON body to model issue because of system naming

In the request body I there is a property named systemDate. This property is always set to 0 in my model and I thought it was because of the variable type (long , double, etc) but after I changed the name from systemDate to someDate in the request body and from SystemDate to SomeDate in the model class the value is passed from the request body to the model instance just as it is supposed to be.
Why is this happening and is there a way to keep the request json naming and make it passing its value to the model?
{
"category":"some_category",
"level":5,
"source":"some_source",
"location":"some_location",
"date":2793455394017,
"message":"some_message",
"id":3295830,
"systemDate":1533114073596991534
}
Here is how my model class looks like:
public class MyModel
{
public MyModel()
{
}
public string Category { get; set; }
public int Level { get; set; }
public string Source { get; set; }
public string Location { get; set; }
public double Date { get; set; }
public string Message { get; set; }
public long Id { get; set; }
public double SystemDate { get; set; }
}
And the Controller method:
[HttpPost(EndpointUrlConstants.MY_ENDPOINT)]
public async Task<IActionResult> DoSomething([FromBody] MyModel myModel)
{
// Some Code
return this.Ok();
}
For Asp.Net Core, we could configure the Json Serialize Settings by AddJsonOptions in Startup.
And the root cause for this issue is related with NamingStrategy = new SnakeCaseNamingStrategy().
I'm not sure if I understand your problem, but you can control the serialization using attributes, i.e. property names in the json string don't have to match the property names in the model.
public class MyModel
{
public MyModel()
{
}
[JsonProperty("category")]
public string Category { get; set; }
[JsonProperty("level")]
public int Level { get; set; }
[JsonProperty("source")]
public string Source { get; set; }
[JsonProperty("location")]
public string Location { get; set; }
[JsonProperty("date")]
public double Date { get; set; }
[JsonProperty("message")]
public string Message { get; set; }
[JsonProperty("id")]
public long Id { get; set; }
[JsonProperty("systemDate")]
public double SomeDate { get; set; }
}
Test code, using Newtonsoft.Json nuget package:
string json = #"{
""category"":""some_category"",
""level"":5,
""source"":""some_source"",
""location"":""some_location"",
""date"":2793455394017,
""message"":""some_message"",
""id"":3295830,
""systemDate"":1533114073596991534
}";
MyModel model = JsonConvert.DeserializeObject<MyModel>(json);
Object is deserialized correctly. As you can see, SomeDate property in the model is mapped to match systemDate property in the json string.

Server not receiving lists inside of JSON object passed in

These are the data contracts that are being used in the function.
public class ResumeSkillsListDataContract : IResumeSkillsListDataContract
{
public IList<ISkillDataContract> KnownSkillsList { get; set; }
public IList<ISkillDataContract> BadSkillsList { get; set; }
public IList<ISkillDataContract> NewSkillsList { get; set; }
public Int32 PersonId { get; set; }
}
public class SkillDataContract : ISkillDataContract
{
public String Name { get; set; }
public Nullable<Int32> Id { get; set; }
public Nullable<Boolean> IsAssigned { get; set; }
public Nullable<Int32> SkillCategoryId { get; set; }
public Nullable<Int32> SkillCategoryMappingId { get; set; }
}
This is the function in the controller. I am expecting three populated lists and a PersonId to be passed in. However, I am only receiving the PersonId. In my Post, I see the data I am expecting to see in the console but when debugging the controller, item.List is empty every time.
public IList<ISkillDataContract> PostResumePersonSkills(ResumeSkillsListDataContract item)
{
var newList = item.KnownSkillsList;
var ignoreList = item.BadSkillsList;
var existingList = item.NewSkillsList;
var personId = item.PersonId;
return resumePersonSkillsBusinessLibrary.PostSkills(newList, ignoreList, existingList, personId);
}
Here is a quick snapshot of what im sending to the server. Any idea what could be wrong? Thanks.
$scope.doneWithSkills = function () {
var resumeCollection = {
KnownSkillsList: $scope.KnownSkillsList, BadSkillsList: $scope.IgnoredSkillsList,
NewSkillsList: $scope.SaveAsSkillsList, PersonId:$scope.ParsedPerson.Person.PersonId
};
resumeParserService.PostResumeSkills(resumeCollection);
};
Function in the resumeParserService
self.PostResumeSkills = function (skills) {
var url = 'ResumeSkill/PostResumePersonSkills';
console.log(skills);
webApiService.Post(url, skills);
};
Sample JSON being passed.
{"KnownSkillsList":[{"Name":"C++","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":154},{"Name":"Unix","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":219},{"Name":".Net","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":139},{"Name":"Clearcase","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":155},{"Name":"Uml","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":218},{"Name":"Xml","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":239},{"Name":"Java","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":173},{"Name":"Python","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":199},{"Name":"Visual Basic","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":223}],"BadSkillsList":[],"NewSkillsList":[{"Name":"Algorithms","Id":null,"IsAssigned":null,"SkillCategoryId":3,"SkillCategoryMappingId":null}],"PersonId":1203}
I would expect this is caused by your lists ResumeSkillsListDataContract being lists of an interface. The problem is going to be that when the JSON is deserialized the deserializer does not know what concrete type to instantiate.
Try changing to this and see if it resolves the problem
public class ResumeSkillsListDataContract : IResumeSkillsListDataContract
{
public IList<SkillDataContract> KnownSkillsList { get; set; }
public IList<SkillDataContract> BadSkillsList { get; set; }
public IList<SkillDataContract> NewSkillsList { get; set; }
public Int32 PersonId { get; set; }
}

Why can't I send my custom class through my webservice?

I have these classes:
public abstract class CustomField
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public FieldType Type { get; set; }
public enum FieldType
{
String = 0,
Integer = 1,
Boolean = 2,
List = 3
}
}
public class StringCustomField:CustomField
{
public String Value { get; set; }
public Int32 MinLenght { get; set; }
public Int32 MaxLenght { get; set; }
public StringCustomField()
{
this.Type = FieldType.String;
}
}
public class CustomGroup
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public List<CustomField> FieldList = new List<CustomField>();
}
When I try to transfer CustomGroup through my webservice I get this error:
The remote server returned an error: NotFound
Serialization is failing when C# tries to transfer my StringField through my CustomField.
What am I doing wrong?
Marc Gravel tell me to do that and i understand the solution but some thing is wrong, no effects, cath the same error!! , help!!
[XmlInclude(typeof(StringCustomField))]
[XmlInclude(typeof(IntegerCustomField))]
[XmlInclude(typeof(BooleanCustomField))]
[XmlInclude(typeof(ListCustomField))]
public abstract class CustomField
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public FieldType Type { get; set; }
public enum FieldType
{
String = 0,
Integer = 1,
Boolean = 2,
List = 3
}
}
If you are sending subclasses as xml, you will need [XmlInclude]:
[XmlInclude(typeof(StringCustomField))]
public abstract class CustomField
{...}
You can add multiple [XmlInclude(...)] markers for any other subclasses in the model.
List<CustomField> will serialize and deserialize to a CustomField[] if you're using a web service, won't it?
use
public class CustomGroup
{
public String Id { get; set; }
public String Name { get; set; }
public String Description { get; set; }
public List<CustomField> FieldList = new List< StringCustomField >();
}
instead
If i understand you correctly, you should
1. connect your web service to your app
2. use the namespace of the WS, so all the classes will be used from the Proxy
i don't think that the local class will be understood by the remote web serivce correctly, even if you're using the same assembly on both parties

Categories

Resources