MongoDB c# Deserialization - property not recognized - mongodb-.net-driver

I'm upgrading from using BsonDocument everywhere, to using deserialization on POCO objects.
Overall the objects are populated with correct values, but i'm having a problem with the code below, values on the EventReferenceEntry.Id property - which are always 0.
Perhaps worth knowing, is that if i remove "BsonIgnoreExtraElements" attribute from the EventReferenceEntry class, i get an error "Element 'i' does not match any field or property of class "
I've also tried setting EventReferenceEntry.Id to Int64 and UInt64, but no difference.
The driver is version 2.7.3, i tried it with a fresh installation of the latest version, but it's the same problem.
The database is a series of events. An event has:
_id = Int64
_t = Int32 (the type of the event)
_r = an array of objects (references to other objects, entities or events, that are relevant.)
C# Code of the POCO objects
[BsonIgnoreExtraElements]
public class EventEntry
{
[BsonElement("_id")]
public ulong Id { get; set; }
[BsonElement("_t")]
public int Type { get; set; }
public DateTime Time { get { return new DateTime((long)Id, DateTimeKind.Utc); } }
[BsonElement("_r")]
public List<EventReferenceEntry> References { get; set; }
}
[BsonIgnoreExtraElements]
public class EventReferenceEntry
{
[BsonElement("i")]
public UInt64 Id { get; set; }
[BsonElement("n")]
public string Name { get; set; }
[BsonElement("a")]
public int Asset { get; set; }
public EventReferenceEntry()
{
}
}
An example database entry
{
"_id" : NumberLong(637684658186492532),
"_t" : 1058,
"_r" : [
{
"n" : "p",
"i" : NumberLong(637662370697662760)
},
{
"n" : "a",
"a" : 1202
},
{
"n" : "o",
"i" : NumberLong(637684655676255124),
"a" : 2934
}
]
}

Found the problem and solution.
A property in the class (in my case EventReferenceEntry) cannot be named “Id” or “id”, I guess that a property named that is assumed to be bound to the _id bson property. It can however be named “ID” or “Ident” or anything else, and the value gets assigned.

Related

How can I convert class properties into uppercase and rename few without Attribute in elasticsearch with c#

I am using elasticsearch in C# to put some data into elasticsearch. I have kept my C# class & properties as per database (PascalCase). However, my requirement is to convert all properties into Uppercase and also change the name of few to another name. Same should be possible for class name as well.
I do not want to achieve this via Data annotations. Is there any way which can be made generic for all C# classes?
I am using NEST 5.X version.
For example,
class Foo
{
public string thisMessage {get; set; }
public string anotherMessage {get; set; }
}
should convert into
class FOOABC
{
public string THISMESSAGE {get; set; }
public string ANOTHER {get; set; }
}
It's possible to do both with NEST without using attributes:
to change the casing of POCO property names when serialized and sent to Elasticsearch, use DefaultFieldNameInferrer(Func<string, string>)
to change the name of the type and rename properties, use InferMappingFor<T>() with TypeName() and Rename()
Here's an example
void Main()
{
var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));
var defaultIndex = "default-index";
var connectionSettings = new ConnectionSettings(pool)
.DefaultFieldNameInferrer(s => s.ToUpperInvariant())
.InferMappingFor<Foo>(m => m
.TypeName("FOOABC")
.Rename(p => p.anotherMessage, "ANOTHER")
)
.DefaultIndex(defaultIndex);
var client = new ElasticClient(connectionSettings);
if (client.IndexExists(defaultIndex).Exists)
client.DeleteIndex(defaultIndex);
var indexResponse = client.Index(new Foo
{
thisMessage = "this message",
anotherMessage = "another message"
});
}
class Foo
{
public string thisMessage { get; set; }
public string anotherMessage { get; set; }
}
the index request and response
POST http://localhost:9200/default-index/FOOABC?pretty=true
{
"THISMESSAGE": "this message",
"ANOTHER": "another message"
}
Status: 201
{
"_index" : "default-index",
"_type" : "FOOABC",
"_id" : "AVtaHKDcii5CLKx9KIcZ",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : true
}

Linq Many to Many query

I need help to query this three tables. RentCommunityFeature and RentPropertyFeature has a many to many relationship with RentUnitListing. My problem is i can't get these three tables to query. What i want is all those rentlistings that has certain features. for example if RentCommunityFeature has a "pool" and RentPropertyFeature has a "parking", i want all the records in RentUnitListing that has "Pool" and "Parking". If no parking than result should show only with "Pool".
I tried the below query but it gives incorrect results. It shows duplicate results when myCommunityFeatureId or myPropertyFeatureId is -1. I have initializes them to -1 if they are empty in DB.
Any help would be greatly appreciated.
var AllAds = from r in _db.RentUnitListings
from cf in r.RentCommunityFeatures
from pf in r.RentPropertyFeatures
where (myCommunityFeatureId > 0) ? (cf.RentCommunityFeatureID == myCommunityFeatureId && cf.RentUnitListings.) : (myCommunityFeatureId == -1)
where (myPropertyFeatureId > 0) ? (pf.RentPropertyFeatureID == myPropertyFeatureId) : (myPropertyFeatureId == -1)
select r;
public partial class RentCommunityFeature
{
public int RentCommunityFeatureID { get; set; }
public string RentCommunityFeatureDescription { get; set; }
public virtual ICollection<RentUnitListing> RentUnitListings { get; set; }
}
public partial class RentPropertyFeature
{
public int RentPropertyFeatureID { get; set; }
public string RentPropertyFeatureDescription { get; set; }
public virtual ICollection<RentUnitListing> RentUnitListings { get; set; }
}
public partial class RentUnitListing
{
public Guid RentUnitListingID { get; set; }
public string RentUnitListingShortDescription { get; set; }
public virtual ICollection<RentCommunityFeature> RentCommunityFeatures { get; set; }
public virtual ICollection<RentPropertyFeature> RentPropertyFeatures { get; set; }
}
var listings = _db.RentUnitListings
.Where(rul => rul.RentCommunityFeatures
.Any(rcf => rcf.RentCommunityFeatureID == myCommunityFeatureId)
|| rul.RentPropertyFeatures
.Any(rpf => rpf.RentPropertyFeatureID == myPropertyFeatureId))
.ToList();
It means: Return all listings that have at least one (Any) RentCommunityFeature with the myCommunityFeatureId OR at least one (Any) RentPropertyFeature with the myPropertyFeatureId. The "OR" is not exclusive, so a returned listing may have a "Pool" without a "Parking" feature or a "Parking" without a "Pool" feature or it might have both. In any case a returned listing might have a lot of other features in addition to "Pool" or "Parking".

AutoMapper doesn't map calculated field to scalar

I am trying to separate my MVC3 project into a proper DAL/Domain/ViewModel architecture, but I'm running into a problem with AutoMapper and mapping calculated fields from my domain to my view model.
Here's an example of what I'm trying to do:
Interface
public interface IRequirement
{
int Id { get; set; }
... bunch of others
public decimal PlanOct { get; set; }
public decimal PlanNov { get; set; }
public decimal PlanDec { get; set; }
... and so on
decimal PlanQ1 { get; }
... etc
decimal PlanYear { get; }
... repeat for ActualOct, ActualNov ... ActualQ1 ... ActualYear...
}
Domain Model
public class Requirement : IRequirement
{
public int Id { get; set; }
... bunch of others
public decimal PlanOct { get; set; }
public decimal PlanNov { get; set; }
public decimal PlanDec { get; set; }
... and so on
public decimal PlanQ1 { get { return PlanOct + PlanNov + PlanDec; } }
... etc
public decimal PlanYear { get { return PlanQ1 + PlanQ2 + PlanQ3 + PlanQ4; } }
... repeat for ActualOct, ActualNov ... ActualQ1 ... ActualYear...
}
There are also VarianceX properties, i.e. VarianceOct which is calculated as (PlanOct - ActualOct), etc.
My view model looks almost exactly the same, except instead of calculated fields it has the default getter/setter syntax, for example:
public decimal PlanQ1 { get; set; }
My AutoMapper config in Global.asax looks like this:
Mapper.CreateMap<Domain.Abstract.IRequirement, Models.Requirement.Details>();
This works fine on all properties except the calculated ones. None of my calculated fields (i.e. *Q1, *Q2, *Q3, *Q4, *Year, and all the Variance* fields) are actually mapped -- they all show up with the default value of 0.00.
I'm pretty stumped on this, and I'm also a novice at this and AutoMapper, so maybe I missed something. My intuition is that since the property signatures aren't identical (i.e. the domain object has only a non-default getter and no setter, while the view model has default getter and setter) then AutoMapper isn't picking it up. But I also did this:
Mapper.CreateMap<Domain.Abstract.IRequirement, Models.Requirement.Details>()
.ForMember(dest => dest.PlanQ1, opt => opt.MapFrom(src => src.PlanQ1);
And it still resolved to 0. I confirmed this in the debugger as well.
What am I doing wrong?
Thanks in advance.
EDIT 1
After following Wal's advice I ran the test and it worked, so I began working backwards one step at a time, first pasting in the Field1/Field2/Field3 parts into the interface/domain/view model classes and verifying it worked in my controller, then changing one thing at a time. What I found is that, since I am dealing with decimal types, if I hard-code in integer or double values then I get zero, but if I cast to a decimal or use a decimal literal then it works. But only if I manually set them, not if I pull the values from the database.
In other words, this works (i.e. PlanQ1 = 6):
var D = new Requirement { PlanOct = (decimal) 1.0, PlanNov = (decimal) 2.0, PlanDec = (decimal) 3.0 };
var V = Mapper.Map<IRequirement, Details>(D);
And this works:
var D = new Requirement { PlanOct = 1M, PlanNov = 2M, PlanDec = 3M };
var V = Mapper.Map<IRequirement, Details>(D);
But this does not (pulling a single domain object from a repository object, that in turn pulls from SQL Server using Entity Framework):
var D = requirementRepository.Requirement(5);
var V = Mapper.Map<IRequirement, Details>(D);
With the above all I get is 0 for PlanQ1 and PlanYear. I verified that PlanOct = 1, PlanNov = 2, and PlanDec = 3 in the domain object (D). I also verified that the type in all objects, including the EF generated object, is decimal, and the SQL Server type is decimal. I even tried mapping to a created view model, just to rule that out, and I still get 0 for PlanQ1 and PlanYear:
var D = requirementRepository.Requirement(5);
var V = new Details();
Mapper.Map<IRequirement, Details>(D, V);
is PlanQ1 a member of IRequirement ? you have implied it is by your last code snippet but if it isn't then you will get the behavior exactly as you describe.
Consider a simplied example of what you are doing:
public interface IFoo
{
string Field1 { get; set; }
string Field2 { get; set; }
//string Field3 { get; }
}
public class Foo1 : IFoo
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get { return Field1 + Field2; } }
}
public class Foo2
{
public string Field1 { get; set; }
public string Field2 { get; set; }
public string Field3 { get; set; }
}
Note in this instance I have omitted Field3 from the interface; now when I run the following the mapping fails
[TestMethod]
public void Map()
{
Mapper.CreateMap<IFoo, Foo2>();
var foo1 = new Foo1() { Field1 = "field1", Field2 = "field2" };
var foo2 = new Foo2();
Mapper.Map(foo1, foo2);
Assert.AreEqual("field1field2", foo2.Field3);//fails, not mapped
}
So if I comment in Field3 from IFoo everything works again. Check this simplified example with your code.
Consider #Wal post, Try this Map,
Mapper.CreateMap<IFoo, Foo2>()
.ForMember(destination => destination.Field3, options => options.MapFrom(source => source.Field1 + source.Field2));
And
[TestMethod]
public void Map()
{
Mapper.CreateMap<IFoo, Foo2>()
.ForMember(destination => destination.Field3, options => options.MapFrom(source => source.Field1 + source.Field2));
var foo1 = new Foo1() { Field1 = "field1", Field2 = "field2" };
var foo2 = new Foo2();
Mapper.Map(foo1, foo2);
Assert.AreEqual("field1field2", foo2.Field3); // True
}
Just realized this was left unanswered so I wanted to close it out. Technically it is unanswered because I couldn't get Automapper to play nice in this scenario for some reason. What I wound up doing is going back and creating a couple of mapping methods inside my repository, one to map a single instance of the DAL object to the IRequirement object, and one to map a collection. Then in the repository instead of calling Mapper.Map I just call my custom mapping methods and it works perfectly.
I still don't understand why this doesn't work, but I've run into a few other classes where Automapper just throws up and I have to manually map at least one or two fields, though Automapper does take care of the rest in those cases.
I'm sure there's something about it I just don't see yet. But in any case, falling back to partial or fully manual mapping was my workaround.

MVC Model Range Validator?

i wnat to validate the datetime, My Code is:
[Range(typeof(DateTime),
DateTime.Now.AddYears(-65).ToShortDateString(),
DateTime.Now.AddYears(-18).ToShortDateString(),
ErrorMessage = "Value for {0} must be between {1} and {2}")]
public DateTime Birthday { get; set; }
but i get the error:
An attribute argument must be a constant expression, typeof expression or array creation expression of an attribute parameter type
please help me?
This means the values for the Range attribute can't be determined at some later time, it has to be determined at compile time. DateTime.Now isn't a constant, it changes depending on when the code runs.
What you want is a custom DataAnnotation validator. Here's an example of how to build one:
How to create Custom Data Annotation Validators
Put your date validation logic in IsValid()
Here's an implementation. I also am using DateTime.Subtract() as opposed to negative years.
public class DateRangeAttribute : ValidationAttribute
{
public int FirstDateYears { get; set; }
public int SecondDateYears { get; set; }
public DateRangeAttribute()
{
FirstDateYears = 65;
SecondDateYears = 18;
}
public override bool IsValid(object value)
{
DateTime date = DateTime.Parse(value); // assuming it's in a parsable string format
if (date >= DateTime.Now.AddYears(-FirstDateYears)) && date <= DateTime.Now.AddYears(-SecondDateYears)))
{
return true;
}
return false;
}
}
Usage is:
[DateRange(ErrorMessage = "Must be between 18 and 65 years ago")]
public DateTime Birthday { get; set; }
It's also generic so you can specify new range values for the years.
[DateRange(FirstDateYears = 20, SecondDateYears = 10, ErrorMessage = "Must be between 10 and 20 years ago")]
public DateTime Birthday { get; set; }

Access MongoDB Rest service with C#

I'm a beginner with mongoDb, I would like to access MongoDb rest service, the data I retrieved is json type. My question is, how do you parse this data ? I don't find any MongoDb api which allows me to query it easily. So what would you do ?
Here's an example of the data, I queried the key "Name" which returned me on row thanks to this url:
http://localhost:28017/MyDatabase/MyCollection/?filter_Key=Name
{
"offset" : 0,
"rows": [
{ "_id" : { "$binary" : "fXvnbtlMhU24EWg9NiY5QQ==", "$type" : "03" }, "Key" : "Name", "Value" : "John Smith" }
],
"total_rows" : 1 ,
"query" : { "Key" : "Name" } ,
"millis" : 0
}
And I would like to retrieve the Value "John Smith"
Thank's
[EDIT]
I've managed to get {"Value": "John Smith"} out of my json. oh!! See this ugly code:
var urlToFetch = "http://`localhost`:28017/MyDatabase/MyCollection/?filter_Key=Name";
var jsonData = GetDataFrom(urlToFetch);
var value = JsonConvert.DeserializeObject(jsonData);
foreach (var key in ((JObject)value)["rows"].Values())
{
key.Parent.Last;
}
It's not perfect, I still don't get my John Smith But there're must be a better way without manually parsing, aren't there ?
Here the solution guys:
public class yourclass
{
public void transformJsonToObject()
{
JsonSerializer serializer = new JsonSerializer();
var value = JsonConvert.DeserializeObject<ResultViewModel>(jsonData);
}
}
public class ResultViewModel
{
[JsonProperty("offset")]
public int Offset;
[JsonProperty("rows")]
public TestViewModel[] Rows;
}
public class TestViewModel
{
[JsonProperty("_id")]
public TestArray Id { get; set; }
public string Key { get; set; }
public string Value { get; set; }
}
public class TestArray
{
[JsonProperty("$binary")]
public string Binary { get; set; }
[JsonProperty("$type")]
public string Type { get; set; }
}

Resources