I am using the MongoDB C# Driver v2.7.x, but I was wondering given a POCO class, is there anyway to raise warnings when querying on unmapped properties.
For example, I have the following class:
public class GroceryList
{
public string Name { get; set; }
[BsonIgnore]
public bool IsOwner { get; set; }
}
So whenever I store this object, IsOwner is not inserted into the database (see also the ignore member docs). However when I perform GetCollection<GroceryList>(..).AsQueryable().Where(gl => gl.IsOwner), this gets translated into {$match: {IsOwner: true}}, thus the element is still used in the query (see also the MongoDB LINQ docs).
Now in this trivial example it's easy to spot the problem; however when there's a few layers of indirection and a function starts returning an IQueryable instead of IEnumerable, it gets harder. So I would like to prevent the situation where ones could query on any field that's not mapped. Preferably through some convention (IMemberMapConvention).
After creating a small test project, I found that [BsonIgnore] would do exactly what I expected:
InvalidOperationException: {document}.IsOwner is not supported.
After some digging I found the problem was caused by how we ignore our members. In order to not have to reference Mongo in our domain layer, we introduced custom attributes that would do the same as Mongo's builtin attributes. However our implementation didn't correctly ignore members, but just disabled serialization for them:
if (mm.MemberInfo.IsDefined(typeof(CustomIgnoreMappingAttribute), true))
{
- memberMap.SetShouldSerializeMethod(o => false);
+ classMap.UnmapMember(memberMap.MemberInfo);
}
Related
I'm building a report builder/runner using System.Linq.Dynamic.Core (1.2.20) in an ASP.NET MVC (5.2.9) app and I mostly have it working, except for one annoying issue. I can't get Skip and Take to work. Basically my code is doing this:
_context.SetDynamic("ENTITY_NAME")
.Where(_parsingConfig, WHERE_EXPRESSION)
.OrderBy(_parsingConfig, ORDER_BY_EXPRESSION)
.Skip(???)// exception
.Take(???)// exception
.Select(_parsingConfig, SELECT_EXPRESSION)
.ToDynamicListAsync();
Running that causes this exception:
Unknown LINQ expression of type 'Dynamic'.
When I remove Skip and Take then it works correctly and I see the results, but I lose out on the paging capabilities.
From what I can tell, it has to do with me starting out with SetDynamic which returns an IQueryable<object>. Elsewhere in the app I do the same query, but start out from a Set<T> and there's no problems with it.
What should I do to get Skip and Take to work?
After some more trial and error I got it to work. I manually tested switching to Set(Type) to see if it fixed Skip and Take and it did.
From there I decided to change the model for Report to contain a string Object property. It already had an ObjectType property which was an enum, but I decided to replace it with a string, and I'll probably do that everywhere else I use it and just remove it.
After that, using reflection I got all objects in the DbContext assembly that implement a marker interface called IEntity and projected them into an IList<Entity>:
public sealed class Entity {
public string DisplayName { get; set; }
public string Name { get; set; }
public Type Type { get; set; }
}
... and then stored them as a singleton for dependency injection. In the report runner class since I already know the report Id I just pull it out of the database and then pull out the Entity where Report.Object == Entity.Name from the list and pass the type off to Set(Type).
Kind of long winded, but it works. I had been planning to have something like that list for a little while so I can present it as drop down list when a new report is being created so the report runner knows where to start from when building the query.
I want to use MongoDB to store domain events in a system written with .NET Core and C#.
I've googled a little about this, and it seems it is a common practice to have a single collection called events and simply store all events there. I've also seem people to create one field type to distinguish events. An example of this is Slide 66 of this presentation.
So if I wanted to save one UserCreated event I would add it with type user-created, and so forth.
Now I'm in doubt with respect to the mapping when it comes to using .NET Core.
Two distinct events will in general have different schema, so I think that the automatic mapping would do no good. Of course I could use the option of ignoring extra elements. But it may be the case that two events have subsets of properties which are equal, for example, all of them will have a OccurredOn DateTime. I think this could be an issue.
My idea was to query the field type. Something like:
colection.Find(BsonDocument.Parse("{type: user-created}"))
But I don't know if that is the best option, or if there is a way to set up a mapping so that the MongoDrive knows that whenever we try to get an instance of UserCreated it should look just for that type, and when we try to insert, it should create the correct type field.
In that case: given that we save distinct event types to the same collection, what is the correct approach to map this into the right C# event objects?
You could use a container like this one.
public class DomainEventContainer
{
public ObjectId Id { get; set; }
public string Type { get; set; }
public string EventData { get; set; }
}
And then based on the value of DomainEventContainer.Type, you could deserialize EventData into your desired type.
I'm proposing using AutoFixture and AutoFixture.xUnit at our company, and have gotten the mandate that for certain objects and fields they want random data that is formatted in an expected way. For example, they want PersonName to only populate with realistic names (instead of GUIDs) and PhoneNumber to only make strings that look like phone numbers. But they DON'T want to add data annotations to the actual objects enforcing this, they would just like the test data generated by AutoFixture to be pretty.
I've dealt a bit with ICustomize classes to implement greedy constructor behavior on a few classes. Is there a similar way to override the data generation for specific objects? To (for example) pull names from a list, or generate data to follow a certain regular expression? (keeping in mind that I can't actually add those regular expressions as attributes on the model)
Ok, solved my problem.
Object generation for a given class type can be accomplished via the Fixture.Register method. You can make a method that returns the type you want to override and that will be used instead of the default.
To get meaningful data I just used Faker.Net.
I got the solution Mark pointed out working, and really liked it for general POJOs, but in my case many of my objects had properties that could only be set via the constructor or aggregate setters (like ChangeContactInfo), so unfortunately I needed something a bit more targeted. Here is an example of my solution implementing a name and address generation override:
public class CustomObjectGeneration : ICustomization
{
public void Customize(IFixture fixture)
{
fixture.Register(GenerateAddress);
fixture.Register(GeneratePersonName);
}
private Address GenerateAddress()
{
return new Address(Faker.Address.StreetAddress(), Faker.Address.SecondaryAddress(), Faker.Address.City(),
Faker.Address.ZipCode(), Faker.Address.UsState(), Faker.Address.Country());
}
private PersonName GeneratePersonName()
{
return new PersonName(Faker.Name.Prefix(), Faker.Name.First(), Faker.Name.First(), Faker.Name.Last(), Faker.Name.Suffix());
}
}
I have a Generic Envelope class that i use as the common return object for the WebAPI as follows:
public class ApiEnvelope<T>
{
public bool Success { get; set; }
public Error Error { get; set; }
public Data<T> Data { get; set; }
}
Then I construct a HttpResponseMessage using:
Request.CreateResponse<ApiEnvelope<whatever>>(HttpStatusCude.OK, model);
The problem i have is that i would like the xml to be somewhat standard however the root name of the xml being returned is not standard and is coming through as ApiEnvelopeOfwhatever.
My question is how can i get the root name to be ApiEnvelope regardless of the type?
With generic class you got no chance, remove generic specification and set Data propert type to object.
I had a similar question and I got a decent answer, (I know this is old but it was a good answer): How to resolve Swashbuckle/Swagger -UI model naming issue for generics?
Now this is only part of the solution for your question, so you should look at Github repo: MakeGenericAgain. Use that to "regeneric" the resultant generated code, (big heads up: if you use this on the entire code and not just names of types, you will have a mess if you have properties name things like "NumberOfUnits" because that becomes "Number").
Sidenote:
If you want to "level up" your skills here, then use Rolsyn's SyntaxWalker to apply the renaming, and at the same time "cleanup" duplicated classes, as many design their REST-APIs with few shared "models" so a User and a Company might hace identical "Address" -properties based on identically shaped classes but it they are defined twice your NSwag -generated code will have Address and Address2, however using Roslyn, you can identify these ande rewrite the code to give a "perfect result".
I have started experiencing errors in WCF deserialization today - in code which has been unchanged and working for months.
The issue is that I am getting runtime XmlExceptions saying 'Name cannot begin with the '<' character'. I have debugged into the .NET source, and it seems the error is in deserializing return objects from our WCF service calls. These objects are defined using automatic properties, and it seems the backing fields are given names like <MyProperty>k_BackingField, which is where the XmlException is coming from.
I've seen a couple of other references online where the solution people accept is "I changed my code to not use automatic properties", which isn't really acceptable to me, as I would have 100s of objects to change, (with 1000s of properties amongst them). Also, this same code was working fine when I ran it last week, and doesn't seem to affect all serialized DTOs, only some.
To make it even more frustrating, it seems mildly intermittent. On occasion this morning, there has been no exception thrown...!
Questions;
Why has this problem suddenly appeared in unchanged code and unchanged framework source?
How do I fix this without modifying all the DTOs to use fully implemented properties?
UPDATE: After a day or so of working fine, this issue has reappeared - no reason I can find why it would work/not work/work again, but here we are.
I have tracked the problem down further to be related to some code I have on my ServiceContracts using the ServiceKnownType attribute, which is used to define known types for serialization. It seems that although the types being reported with errors are not even part of the service call I am making at the time, that this error is occurring on types which are part of this known types 'publishing' behaviour.
The problem occurs when I use some proxy creation code to apply some service behaviours;
IOperationBehavior innerBehavior = new PreserveReferencesOperationBehavior(
description, this.preserveReferences, this.maxItemsInObjectGraph);
innerBehavior.ApplyClientBehavior(description, proxy);
I cannot debug the ApplyClientBehavior code as it is part of System.ServiceModel (or can I?), but something in that method is trying to validate all types I have published using my ServiceKnownType attribute, and breaking on some of them with this XmlException. I have NO IDEA why some of the types are failing - and only for some of their properties.
This is an example of the types which are getting errors reported against them;
[Serializable]
public class MyDataObject
{
public ActivitySession(string id)
{
this.Id = id;
this.IsOpen = true;
}
public string Id { get; set; }
public bool IsValid { get; set; }
}
The exception reported an error against Id -> <Id>k_BackingField cannot start with '<'
So nothing controversial in that class, and no inheritance to consider. It's not even part of a service contract, only it was previously published as a known type for serialization.
This is getting quite esoteric now, so I'm not expecting an answer, but just updating where the problem is at.
I think I have found more information to help explain this issue, (at least in so far as why the error is appearing on certain types only).
The DTOs which are getting exceptions reported against them are;
published as part of my [ServiceKnownType] attribute
marked with [Serializable]
NOT marked with [DataContract]
Adding the [DataContract] attribute to the type resolves this issue. I have no idea why, and still no idea why this error is intermittent in when it happens, but consistent in what it affects.
I looked at this question also: WCF Service Reference - Getting "XmlException: Name cannot begin with the '<' character, hexadecimal value 0x3C" on Client Side
regarding this exception:
System.Xml.XmlException: 'Name cannot begin with the '<' character,
hexadecimal value 0x3C.'
Check if you load any xml files that they are valid (e.g. do not contain typo's like < or >
If you are using services + WCF, have a look at your Service interfaces (the interfaces with ServiceContract). This will be a good starting point. Now check to see if you have any DTO parameters in methods from the interface. Go to these DTO's and see if these DTO classes have [Serializable] or [DataContract] or similar attributes. If these classes also contain automatic properties, change them the properties to the notation with your own backing field like:
private Foo _Bar;
public Foo Bar { get { return _Bar; } set { _Bar = value; } }
If you are lucky you will see the errors go away!
There seems to be a problem with automatic properties where the automatically generated backing field has a name similar to e.g. <>something, <>d_whatever or things like that. These names start with '<' character, resulting in that error.
In case of services and WCF, your service interfaces and callbacks (with datacontract) are a good place to start replacing the automatic properties. At least it gives you an idea where to start instead of replacing thousands of automatic properties.
Additionally try to catch FirstChanceExceptions by adding this code at the start of your application and write the messages to the console.
This will help a lot to see if the number of "Name cannot begin with the '<' character" messages is reduced or not.
AppDomain.CurrentDomain.FirstChanceException +=
(object source, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e) =>
{
Console.WriteLine("FirstChanceException event raised in {0}: {1}",
AppDomain.CurrentDomain.FriendlyName, e.Exception.Message);
};
https://learn.microsoft.com/en-us/dotnet/framework/app-domains/how-to-receive-first-chance-exception-notifications
This is what I found so far. Hope it helps.
I have a workaround in place now, however it's not something I can rely on -> the DTOs which are causing the problem have been removed from the [ServiceKnownType] publisher, which makes the error go away.
I'm wondering if the problem is to do with the member names I am getting exceptions on. So far I have seen it complain about;
Id
Address
UserName
It would be reasonable to expect those particular property names are in use somewhere else in the serialization or service model, causing them to be compiled differently, I guess.
I was hitting this issue today (first-chance exception, no apparent problem otherwise). In my case NetDataContractSerializer (NDCS) was serializing IFieldData[] (from CSLA.NET library). NDCS can serialize arrays, it is also able to serialize objects that don’t have [DataContract] attribute applied to it. In that case the serializer infers the contract – all public read/write properties and fields of the type are serialized. It is documented here: https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/using-data-contracts
So in my case, one of the objects in the array had a reference to Fraction (my own class) defined as follows:
public sealed class Fraction
{
public int Numerator { get; private set; }
public int Denominator { get; private set; }
public double Value { get; private set; }
}
It is causing WCF to throw the "Name cannot begin..." exception, which is caused by the fact that the automatic properties are using generated private fields named like <Numerator>k__BackingField. If you add [DataContract] attribute to the class, then you must explicitly mark what needs to be serialized by [DataMember] attribute. That makes the exception go away. The serializer is not touching the private fields anymore.
In my opinion, it is a bug in WCF. The inferred contract should be only using the public surface of the class, which does not have any naming problems. It should not be snooping on private fields (compiler-generated or not).
My answer supports/supplements what RJ Lohan and juFo said earlier and I upvoted their answers.
The best way to figure out which field is giving you the problem is to inspect the StackTrace as the error comes up:
The answer, in my case, was to change the auto property to have explicitly declared backing fields to avoid the possibility of this naming. So
public string ScreenName { get; set; }
becomes:
private string _screenName;
public string ScreenName { get { return _screenName; } set { _screenName = value; } }
For anyone else having this issue: If you have XmlException checked in Visual Studio's exception settings, it'll throw even if the exception will get handled in System.Runtime.Serialization. I spent a good 20 or so hours trying to work out why my code had suddenly stopped working when I turned on all exceptions - it wasn't actually a fatal exception, it was just ~1200 caught XmlExceptions.