I'm having big problems when I'm overriding the Id property in a derived class in my MongoDB storage setup.
The base class that all of my data models inherit looks like this:
public abstract class DataModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
public virtual string Id { get; set; }
public DateTime Created { get; set; }
public DateTime Modified { get; set; }
public DateTime Deleted { get; set; }
}
Then there are a few particular child data models that use upserts. This requires me to annotate the overridden Id property with [BsonIgnoreIfDefault] as per this answer:
public class ChildDataModel : DataModel
{
[BsonId]
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnoreIfDefault] // <---- need this for Upserts to work
public override string Id { get; set; }
... // child-specific properties
}
But unfortunately it results in this error:
The property 'Id' of type
'Namespace.ChildDataModel' cannot
use element name '_id' because it is already being used by property
'Id' of type 'Namespace.DataModel'.
I've tried registering class maps, adding a type discriminator with RootClass = true on the base model, as well as defining my child data models on the base class with the special [BsonKnownTypes(typeof(ChildDataModel), ...)] attribute, but to no avail.
What am I doing wrong here?
MongoDB driver by convention tries to map all the properties named Id into _id in class map. As you have two classes it registers the _id twice. What's more BsonIgnoreIfDefault would work fine if the Id was null but here it's not since the driver automatically will generate the value when you insert new document.
To fix that you can use BsonIgnore if you want to have single _id in MongoDB
public class ChildDataModel : DataModel
{
[BsonRepresentation(BsonType.ObjectId)]
[BsonIgnore]
public override string Id { get; set; }
}
will be stored as:
{
"_id" : ObjectId("5cb5fe72e2a22b3848b6a1f6"),
"Created" : ISODate("2019-04-16T16:10:25.908Z"),
"Modified" : ISODate("2019-04-16T16:10:25.914Z"),
"Deleted" : ISODate("2019-04-16T16:10:25.914Z")
}
or you can use BsonNoId attribute if you want to have two values stored separately:
[BsonNoId]
public class ChildDataModel : DataModel
{
[BsonRepresentation(BsonType.ObjectId)]
public override string Id { get; set; }
}
will be:
{
"_id" : ObjectId("5cb5fecde2a22b3088ef731c"),
"Created" : ISODate("2019-04-16T16:11:56.810Z"),
"Modified" : ISODate("2019-04-16T16:11:56.822Z"),
"Deleted" : ISODate("2019-04-16T16:11:56.822Z"),
"Id" : ObjectId("5cb5fecde2a22b3088ef731c")
}
however it's still the same value from application point of view
Related
I am trying to retrieve data from local MongoDb with JSON documents like this:
{
"teryt_num_code" : "AB",
"id": 1,
"name": "Alabama"
}
I have created a POCO class as following:
public class Sensor
{
public int id { get; set; }
public string name { get; set; }
public string teryt_num_code { get; set; }
}
To retrieve all data from my database I use a method below:
public async Task<ICollection<Sensor>> GetAllAsync() => await _collection.Find(_ => true).ToListAsync();
but the exception was thrown calling that method:
An error occurred while deserializing the property of class Sensor:
Element 'id' does not match any field or property of class Sensor.
What I am doing wrong?
By default, the id property of your class is mapped to the BSON id (property _id on the document). This leaves the id property in the MongoDB document without a corresponding property in the POCO. Therefore, the error is raised.
In order to fix the deserialization error, you can apply both a BsonNoId and a BsonIgnoreExtraElements attribute to the POCO:
[BsonNoId]
[BsonIgnoreExtraElements]
public class Sensor
{
public int id { get; set; }
public string name { get; set; }
public string teryt_num_code { get; set; }
}
BsonNoId disables the convention; therefore BSON id is mapped to POCO id. BsonIgnoreExtraElements fixes the error that the driver does not know how to deserialize the _id property that every MongoDB document contains.
So, I'm not sure where my head is at today but I can't wrap my head around a decent solution to this issue and I was hoping you guys can help.
We are building a .NET Core / EF Core / SQL application that hands a base record type, and then we layer on metadata objects on top of that base record to enrich the datasets. So my base class looks something like this:
public class Record
{
[NotNull]
public Guid Id { get; set }
[CanBeNull]
public virtual string InvoiceNum { get; set; }
[CanBeNull]
public virtual string Notes { get; set; }
//Enum that defines the extra properties
public virtual RecordType RecordType { get; set; }
public Dictionary<string,object> ExtraProperties { get; set; }
}
and my MetaData class looks something like this:
public class MetaData
{
[NotNull]
public Guid Id { get; set; }
[NotNull]
public Guid RecordId { get; set; }
[NotNull]
public virtual string Description { get; set; }
//Enum that defines the extra properties
public virtual MetaDataTypes MetaDataType { get; set; }
public Dictionary<string,object> ExtraProperties { get; set; }
}
I am using the ExtraProperties field in each object to store a JSON encoded string of additional data for each type, determined by the Enum values noted in the objects above.
What I am trying to find out, is how do I consistently (and efficiently) define those metadata fields on an incoming JSON without needing to hard code each individual type?
For example, I was thinking the JSON for a complete incoming record could be something like this:
{
"Record" : {
"InvoiceNum":"12345",
"Notes":"Notes go here",
"MetaData" : [
{
"MetaDataType" : "Address",
"Properties" :
{
"Address01":"123 ABC Ave",
"City":"New York City",
"Country":"US"
}
},
{
"MetaDataType":"ClientContact",
"Properties" :
{
"FirstName":"John",
"LastName":"Doe",
"Email":"jdoe#example.com",
"Phone":"8675309"
}
}]
}
}
But I want the "Properties" objects to have consistent values based on what the MetaDataType object is defined as and I only want to store the properties for that particular object type in the database attached to that object (there are, across all metadata types, about a hundred unique values).
Hopefully that makes sense, like I said earlier my brain is fried a little today so maybe I am missing something really obvious, but I would like to get your thoughts.
The issue:
Entity object has it properties related to databases on its own, but the needs in the programming area is differ, sometimes we want to add it some more:
Properties – that is for temporary logic.
Methods – for clean code and for programming necessaries.
Finally yet importantly – Attribute for authorization, display, filters etc.
However, obviously we do not want our program to be maintainability without needs to rewrite code just after we update the model.
For properties and methods, the Entity Framework platform generated all the object from model as partial classes and the .NET environment allow us to extend them as we wish:
Remember to check that our partial sit in same namespaces (Notice that when we create them in model directory or in them own directory Visual Studio create addition namespace).
public partial class ErrorLog
{
public long pk { get; set; }
public int lineNumber { get; set; }
public Nullable<int> error { get; set; }
}
Our partial:
public partial class ErrorLog
{
public string getErrorDescription()
{
return d[(int)error];
}
private static Dictionary<int, string> d = new Dictionary<int, string>()
{
{1,"desc1" },
{2,"desc2" },
{3,"desc3" },
{4,"desc4" }
};
}
For attributes:
We can add new interface
public interface IErrorLogsMetaData
{
[Display(Name = "Id")]
long pk { get; set; }
[Display(Name = "The line Number")]
int lineNumber { get; set; }
[Display(Name = "The Error")]
Nullable<int> error { get; set; }
}
Implement them on our Entity (even extended) object.
For that we need to reflect and book it in global.asax by using:
TypeDescriptor.AddProviderTransparent(
new AssociatedMetadataTypeTypeDescriptionProvider(typeof(ErrorLog), typeof(IErrorLogsMetaData)), typeof(ErrorLog));
TypeDescriptor – familiar for us from reflection, its get information about type.
AddProviderTransparent – is the method called from my partially trusted code and get metadata from associated class.
The first parameter is the provider and it TypeDescriptionProvider from the type we want to decorate and the attributed interface, the second parameter is the target type for decription.
Another Option
Make your partial view to implement the IErrorLogsMetaData and then you don't need to associate at Global.asax
As you can see, the database first entity model classes are partial, so you can create your own partial class, for example if you have:
public partial class SomeClass
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
You can do something like this:
Add some class to your project, name it SomeClassPartial:
//SomeClassPartial.cs
namespace YourNamespace
{
[MetadataType(typeof(SomeClassMetadata))]
public partial class SomeClass
{
//add your new properties/some_logic here
public string NewPropX { get; set; }
public string NewPropY { get; set; }
}
public partial class SomeClassMetadata
{
//metadata for your existing model properties
[Display(Name = "Property 1")]
public string Prop1 { get; set; }
[Display(Name = "Property 2")]
public string Prop2 { get; set; }
}
}
In your SomeClassMetadata class you can add data annotation attributes to your existing properties with MetadataType attribute, which will specify the metadata class to associate with a data model class, and with that you can tell you partial SomeClass class to get that attributes from SomeClassMetadata class. To add new custom properties, you can use SomeClass partial class.
MSDN Link: MetadataTypeAttribute Class
I want to hide a certain property from the inherited class of another property.
Class I'm inheriting:
public class Sensor
{
public Guid ID { get; set; }
}
Another class where the property is inheriting from Sensor:
public class TargetDeck
{
public Sensor TargetSensor { get; set; }
}
TargetDeck does not inherit from Sensor, only TargetSensor. I am getting Guid ID conflicts because of the Guid ID in Sensor. My idea was to hide that property for TargetSensor. How can I do this if it is possible?
Just override the property getter in the subclass.
public class TargetSensor : Sensor
{
public Guid ID {
get {
return new Guid("This is your default/overridden property");
}
set;
}
}
After reading the most recent comments, this may not be what OP is after.
EDIT: This may be an instance of the xy problem
I have some issue with mongo c# driver. I have such class:
class MongoEntity<T>
{
public ObjectId Id {get; set;}
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public int Version { get; set; }
public T Entity { get; set; }
}
And during serialization my entities to database I have such document:
"_id" : "510654cf33d22e1774d5a2a9",
"CreatedAt" : {
"DateTime" : ISODate("2013-01-28T10:37:02.932Z"),
"Ticks" : NumberLong("634949662229321756")
},
"UpdatedAt" : {
"DateTime" : ISODate("2013-01-28T10:37:02.932Z"),
"Ticks" : NumberLong("634949662229321756")
},
"Version" : 1,
"Entity" : {
"EntityKey" : "tom#gmail.com",
"Password" : "ICy5YqxZB1uWSwcVLSNLcA==",
"Email" : "tom#gmail.com",
"Name" : "Tom Anderson"
}
What I really want is to have all properties of entity object in my document in the same level as properties of MongoEntity object like this:
"_id" : "510654cf33d22e1774d5a2a9",
"CreatedAt" : {
"DateTime" : ISODate("2013-01-28T10:37:02.932Z"),
"Ticks" : NumberLong("634949662229321756")
},
"UpdatedAt" : {
"DateTime" : ISODate("2013-01-28T10:37:02.932Z"),
"Ticks" : NumberLong("634949662229321756")
},
"Version" : 1,
"EntityKey" : "tom#gmail.com",
"Password" : "ICy5YqxZB1uWSwcVLSNLcA==",
"Email" : "tom#gmail.com",
"Name" : "Tom Anderson"
without Entity embedded object. How can I implement this in easiest way ?
P.S. What I am really looking for is some configuration of driver or writing custom serializer, maybe some workaround with dynamic, I do not want to change current class structure Enteties <-> MongoEntity
Thx for help.
There is no way to configure the built-in serializers to produce the document format you want (where the Entity fields are lifted one level up).
You would have to write a custom serializer, but it would be difficult because you either have to write a new serializer for each <T>, or you would have to write a sophisticated serializer that works for any <T>.
WiredPrairie's suggestion (Person : MongoEntityBase) is the recommended solution.
Just use a subclass of a base type that includes all of your required DB fields.
public class abstract MongoEntityBase
{
public ObjectId Id {get; set;}
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public int Version { get; set; }
}
Then, use it:
public class Person : MongoEntityBase
{
public string Email { get; set; }
public string Name { get; set; }
}
The 10gen provided MongoDB C# driver works fine with that pattern.