MongoDocument Object only gets partially populated when querying - c#

I have a collection of users that I'm trying to read from the database, but for some reason some strange behavior takes place that I can't really figure out. Hopefully, somebody can suggest or help me find the root of this problem.
So basically, what happens is that whenever I call this code in my HomeController.cs:
var users = await _database.GetCollection<User>("ApplicationUsers").FindAsync(_ => true);
var userList = users.ToList();
it only populates userList partially, meaning only the ID and ConcurrencyStamp properties get filled, but the other properties always end up being null (as seen in: https://i.imgur.com/RTF8ljL.png)
But whenever I add this line right after the database connection initialization in the Startup.cs:
database.GetCollection<User>("ApplicationUsers");
Then suddenly userList does get populated with all the other information (as seen in https://i.imgur.com/f5IV7fh.png)
So in order for it to work, I have to get the collection right after the connection gets initialized which I'm not really fond of, because I don't have to do this for other collections. So my mongo connection code would have to look like this in the Startup.cs:
var mongoUrl = new MongoUrl(config.GetSection("DatabaseSettings:ConnectionString").Value);
var mongoClientSettings = MongoClientSettings.FromUrl(mongoUrl);
mongoClientSettings.ClusterConfigurator = cb => ConfigureCluster(cb);
var client = new MongoClient(mongoClientSettings);
var database = client.GetDatabase(config.GetSection("DatabaseSettings:DatabaseName").Value);
database.GetCollection<User>("ApplicationUsers"); // TODO: This is needed just to let Mongo Driver know to which class to deserialize this collection
services.AddSingleton<IMongoDatabase>(database);
var pack = new ConventionPack()
{
new CamelCaseElementNameConvention(),
new IgnoreExtraElementsConvention(true),
new DictionaryRepresentationConvention(DictionaryRepresentation.ArrayOfArrays)
};
ConventionRegistry.Register("DatabaseConventions", pack, t => true);
I'm guessing something happens between the execution of Startup.cs and HomeController.cs that causes the deserialization to mess up?
Update:
The same behavior seems to happen on a clean project, the only nuget packages I installed are the official mongodb driver and AspNetCore.Identity.Mongo by Matteo Fabbri. This strange deserialize behavior does not happen when I use getCollection for other classes. The problem lies with ApplicationUser which extends from MongoUser (a class made available by the AspNetCore.Identity.Mongo library)
Update 2:
Turns out that MongoUser class from the AspNetCore.Identity.Mongo library is allergic to the ConventionPack that was registered. I tested this by getting the collection before and after the registration of the database conventions. Now finding a proper solution for this.
Update 3:
Also I found out that documents are saved with properties named in Upper Camel Case, which could be the cause for mongodb driver's confusion. It seems that the conventions set to save them in CamelCase is being ignored for this particular class (ApplicationUser).

I managed to solve the problem. The issue was the order of code execution. The convention pack was registered after MongoIdentity and the database was initialized. During the initialization of MongoIdentity and the Database, the ApplicationUser was configured to not have the CamelCaseElementNameConvention in the pack, which led to the class being saved in UpperCamelCase and therefore cause confusion when retrieving the collection when the registered ConventionPack was active.
For whoever struggles with strange behavior where you may think your code should work, remember that the order of code execution is very important and could very well be the cause of the behavior. Good luck to y'all!

Related

Add field to a Mongo document using a string for its name

I'd like to create a new field on an existing document. I'm using the following line to get all the documents out of the database into POCOs:
var collection = _database.GetCollection<Address>(collectionName);
I then make some changes to each and save them back into the database using this line:
collection.ReplaceOne(
filter: new BsonDocument("_id", a.id),
options: new UpdateOptions { IsUpsert = true },
replacement: a);
That all works nicely.
To add a field, I've tried the following:
a.ToBsonDocument().Add("RainfallMM", rainfallMM);
But it doesn't make it to the database. Is there a trick to it?
I asked you in comment did you add new property to address model and you said you didn't and you want to add it dynamically. Trick here is you initialize your collection as collection of addresses and mongo ignores all properties that are not part of address model by default.
If you want to add it dynamically you need to change how you start your collection to:
var collection = _database.GetCollection<BsonDocument>("addresses");
Now your collection is not tied to address model and you can work with it as you wish. Everything is BSON document now!
For example you can do this:
var inserted = MongoCollection.Find(x => true).FirstOrDefault();
inserted.Add(new BsonElement("RainfallMM", rainfallMM);
MongoCollection.ReplaceOne(new BsonDocument("_id", inserted["_id"]), inserted);
PS there are some other workarounds and if this one you don't like I can show you the rest =)
Hope it helps! Cheers!
As #Fidel asked me to I will try to briefly summarize other solutions. The problem in accepted answer is that while it works it loses it's connection to Address model and OP is stuck with working with BSON documents.
IMO working with plain BSON documents is pain.
If he ever wishes to change back to initializing collection as collection of Addresses and tries to get anything from db he will encounter an error saying something like:
Missing serialization information for rainfallMM
He can fix that by including tag above his Address class like this:
[BsonIgnoreExtraElements]
public class Address
{
...fluff and stuff...
}
Problem now is if he is not careful he can lose all his info in dynamically added properties.
Other problem is if he adds another property dynamically. Now he has to remember there are 2 properties which are not in model and the hell breaks loose.
Weather he likes it or not, to make his life easier he will probably have to modify Address model. There are 2 approaches and their official documentation is great (I think), so I will just link it here:
http://mongodb.github.io/mongo-csharp-driver/2.4/examples/mixing_static_and_dynamic/
IF you ask me which one is better I will honestly tell you it depends on you. From documentation you will see that if you use an extra BSON document property you don't have to worry about naming your extra properties.
That would be all I can think of now!
Hope it helps you!
try with
a.Add({"RainfallMM", rainfallMM});

Can't query/order on built-in rally fields "could not read all instances of class com.f4tech.slm.domain.Artifact"

I'm using v2.0 of the API via the C# dll. But this problem also happens when I pass a Query String to the v2.0 API via https://rally1.rallydev.com/slm/doc/webservice/
I'm querying at the Artifact level because I need both Defects and Stories. I tried to see what kind of query string the Rally front end is using, and it passes custom fields and built-in fields to the artifact query. I am doing the same thing, but am not finding any luck getting it to work.
I need to be able to filter out the released items from my query. Furthermore, I also need to sort by the custom c_ReleaseType field as well as the built-in DragAndDropRank field. I'm guessing this is a problem because those built-in fields are not actually on the Artifact object, but why would the custom fields work? They're not on the Artifact object either. It might just be a problem I'm not able to guess at hidden in the API. If I can query these objects based on custom fields, I would expect the ability would exist to query them by built-in fields as well, even if those fields don't exist on the Ancestor object.
For the sake of the example, I am leaving out a bunch of the setup code... and only leaving in the code that causes the issues.
var request = new Request("Artifact");
request.Order = "DragAndDropRank";
//"Could not read: could not read all instances of class com.f4tech.slm.domain.Artifact"
When I comment the Order by DragAndDropRank line, it works.
var request = new Request("Artifact");
request.Query = (new Query("c_SomeCustomField", Query.Operator.Equals, "somevalue").
And(new Query("Release", Query.Operator.Equals, "null")));
//"Could not read: could not read all instances of class com.f4tech.slm.domain.Artifact"
When I take the Release part out of the query, it works.
var request = new Request("Artifact");
request.Query = (((new Query("TypeDefOid", Query.Operator.Equals, "someID").
And(new Query("c_SomeCustomField", Query.Operator.Equals, "somevalue"))).
And(new Query("DirectChildrenCount", Query.Operator.Equals, "0"))));
//"Could not read: could not read all instances of class com.f4tech.slm.domain.Artifact"
When I take the DirectChildrenCount part out of the query, it works.
Here's an example of the problem demonstrated by an API call.
https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(c_KanbanState%20%3D%20%22Backlog%22)&order=DragAndDropRank&start=1&pagesize=20
When I remove the Order by DragAndDropRank querystring, it works.
I think most of your trouble is due to the fact that in order to use the Artifact endpoint you need to specify a types parameter so it knows which artifact sub classes to include.
Simply adding that to your example WSAPI query above causes it to return successfully:
https://rally1.rallydev.com/slm/webservice/v2.0/artifact?query=(c_KanbanState = "Backlog")&order=DragAndDropRank&start=1&pagesize=20&types=hierarchicalrequirement,defect
However I'm not tally sure if the C# API allows you to encode additional custom parameters onto the request...
Your question already contains the answer.
UserStory (HierarchicalRequirement in WS API) and Defect inherit some of their fields from Artifact, e.g. FormattedID, Name, Description, LastUpdateDate, etc. You may use those fields in the context of Artifact type.
The fields that you are trying to access on Artifact object do not exist on it. They exist on a child level, e.g. DragAndDropRank, Release, Iteration. It is not possible to use those fields in the context of Artifact type.
Parent objects don't have access to attributes specific to child object.
Artifact is an abstract type.
If you need to filter by Release, you need to make two separate requests - one for stories, the other for defects.

What is the difference between AddObject and AddToMyTable (specific table name)?

I'm very newbie in MVC 4 and Entity-Framework
If this question does not make sense, please let me explain better.
During the example I'm working on, I have noticed that I can make an insertion to database either using AddObject or AddToMyTableName.(my specific table name which is exist in database)
So I'm kinda confused what is the difference between these?
And which one I should use in what cases?
Here is very simplified example:
In the controller:
This is example for AddObject:
using (myProj.Models.myProjEntities db = new Models.myProjEntities())
{
myProj.Models.TestClass myTestClass = new myProj.Models.TestClass();
myTestClass.prop1 = "test1";
myTestClass.prop2 = "test2";
db.MyTable.AddObject(myTestClass);
db.SaveChanges();
}
And here is the example for AddToSpecificTable:
using (myProj.Models.myProjEntities db = new Models.myProjEntities())
{
myProj.Models.TestClass myTestClass = new myProj.Models.TestClass();
myTestClass.prop1 = "test1";
myTestClass.prop2 = "test2";
db.AddToMyTable(myTestClass);
db.SaveChanges();
}
Both of them are inserting the values to db and both are working same in my example. I'm pretty sure there are some cases when one of them is working , the other will not.
Can anyone please explain the difference?
Thanks
The AddTo<TEntity> method is now considered deprecated and has been used with EF4 and previous versions. With EF4.x the new AddObject() was introduced as replacement method for AddTo<TEntity> but for some (unknown) reason the AddTo<TEntity> method was still available even though it's was recommended to use the new AddObject() method. As of now, with EF5+ you can just use Add(). It's hard to find the exact reasons for the replacement of the methods but I guess this is most probably historical reasons and as the EF evolved so the methods were changed to reflect the new versions.
So if you consider why (if in any scenario) the one is better than the other, than just use the most common one (Of course if you don't have some version restrictions like, if you are using EF 4 for example). Otherwise you are not gaining anything and usually after some method is marked as deprecated sooner or later it's removed, so if you use AddTo<TEntity> you might need to rewrite parts of your code due to the fact that in some future version this method is no longer presented.

How to extend Breeze MetaData for Unmapped Entity Property without KO

There are some posts about this, but nothing specific that works in my project.
I read the docs on Breeze about Extending Entities, but they use knockout and I am using Angular.
I have defined a custom property on the server and it is being passed down in my JSON.
However, Breeze js ingnores it because there is no meta data for it.
I need to define the meta data on the client so that Breeze can read the property.
Here is what I have the client so far, but it does not work. By not working... I mean when I call it with {{item.MyProp}} nothing ever shows up on the screen. However, all the other properties from the actual meta data show up just fine.
configureBreeze();
var serviceName = 'api/Entity';
var manager = new breeze.EntityManager(serviceName);
manager.enableSaveQueuing(true);
var store = manager.metadataStore;
addMyPropType(store);
function addMyPropType(store) {
store.registerEntityTypeCtor("Merchant", MyProp);
}
// custom Merchant constructor
var MyProp= function () {
//'MyProp' is a server-side calculated property of the Merchant class
// This unmapped property will be empty for new entities
// but will be set for existing entities during query materialization
this.MyProp= "test";
};
var dataservice = {
store: store,
List: List,
Create: Create,
ListDetail: ListDetail,
Save: Save
};
return dataservice;
I have ready the NODB sample, but I do have a DB and it also uses KO.
UPDATE:
Ok. So I found something that partially works. The default value is now getting displayed on the view. However, the value from the JSON is not being filled in. It always uses the default value.
This is what I have now:
var Merchant = function () {
this.MyProp = "5";
};
store.registerEntityTypeCtor("Merchant", Merchant);
What needs to happen for MyProp to be filled by the actual value from the JSON?
If camelCasing is not your case as PWKad suggested then check breeze documentation for the Breeze Angular SPA template here http://www.breezejs.com/samples/breezeangular-template . There is a section in that link called "Extending entity definitions on the client" .
If I understand correctly you have a "Merchant" object with a "MyProp" calculated property. Try this
store.registerEntityTypeCtor("Merchant",Merchant, merchantInitializer);
function Merchant(){
this.MyProp="";
}
Well, it seems this is a bug in Breeze. You actually have to edit the breeze.js file to get it working. I never would have thought it was a bug.
I found the answer here:
Breeze Extended Entity Property Only Loads on Second Query
UPDATE:
Today I updated to the latest version of breeze.js and the bug does not exist anymore. So this was basically a lot of pain for no reason. Thanks everyone for the help. If you cannot update for some reason use the link above.

Retrieving related entities with early bound classes returns null

Some background: I'm writing a custom workflow activity for CRM 2011 in C# and I am using early bound classes generated by CrmSvcUtil.exe. My plugin takes an opportunity as its only input and is supposed to check its related activities, then set a field on the opportunity to denote whether the opportunity needs more follow-up. My problem currently is that whenever I attempt to retrieve the related activities, the result is null. Here's the relevant part of my code:
Opportunity currentOpportunity = (Opportunity) service.Retrieve(context.PrimaryEntityName, context.PrimaryEntityId, new ColumnSet(true));
currentOpportunity.Opportunity_ActivityPointers
I was under the impression that since there is a one-to-many relationship between Opportunity and Activity that this would grab all the relevant activities, but it doesn't seem to be doing that.
I'm still new to CRM and C#, so any insight as to what I'm doing wrong is appreciated!
if you are using early bound classes, first create data context (in my case it is XrmServiceContext). You can retrieve all ActivityPointers where Regarding object is your opportunity.
OrganizationServiceProxy orgserv;
using(var xrm = new XrmServiceContext(orgserv))
{
//Opportunity currentOpportunity = ...
IQueryable<ActivityPointer> activityPointers = xrm.ActivityPointerSet.Where(a =>
a.RegardingObjectId == currentOpportunity.ToEntityReference());
}
ActivityPointer contains ActivityId and ActivityTypeCode if you need some specific activity from this set. More details here.
Hope it helps :)

Categories

Resources