[CSHARP-731] Cannot register my serizlizer in 1.8.1 Created: 19/Apr/13  Updated: 20/Mar/14  Resolved: 20/Mar/14

Status: Closed
Project: C# Driver
Component/s: None
Affects Version/s: 1.8.1
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: Roman Kuzmin Assignee: Unassigned
Resolution: Cannot Reproduce Votes: 1
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

This line

BsonSerializer.RegisterSerializer(typeof(PSObject), new PSObjectSerializer());

where PSObjectSerializer is my custom serializer for PSObject, throws an exception:

There is already a serializer registered for type System.Management.Automation.PSObject.

In 1.8 this code works fine. Is anything perhaps changed in ways of how custom serializers should be registered?



 Comments   
Comment by Craig Wilson [ 20/Mar/14 ]

Ok. Thanks Roman. I'm going to close this issue. My apologies for never figuring out where the change happened.

Craig

Comment by Roman Kuzmin [ 20/Mar/14 ]

I found a workaround. I register my serializers ASAP in several places. This is not neat perhaps but it works.

Comment by Craig Wilson [ 20/Mar/14 ]

Roman,

Is this still an issue? I was never able to figure out what changed. Were you able to workaround this issue from your side?

Craig

Comment by Craig Wilson [ 19/Apr/13 ]

That is definitely an option, but I think the downsides to allowing the overriding of a serializer are too great.

Let's take an example where someone is working with a custom class "Person". They have built a custom serializer and register it at some point in their application. Sometime later, they add some code that inserts a bunch of people into the collection. This code ran before the custom serializer was registered.

The outcome of this is horrible: They have saved a bunch of Person entities with the default conventions that we built for them. Then a custom serializer is registered. All sorts of errors ensue because not all the saved Person entities look the same in MongoDB. They could have different casings, different field names, different types, etc... The custom Person serializer doesn't know how to deserialize this rogue information that was persisted prior to the serializer registration.

Hence, our solution was to throw when you attempted to register a serializer for a type that already has one registered. Sure, there are trade-offs, but we have elected to force a failure as soon as we see a problem as opposed to let your app continue to run and for you to fail later.

It's perfectly fine for you to disagree with this decision, but it has been the defacto rule for serialization for as long as this code base has existed and changing it is not likely to happen anytime soon.

Like I said, I'm investigating what has changed to cause your codebase to need to register earlier.

Comment by Roman Kuzmin [ 19/Apr/13 ]

From the top of my head I would recommend to think of simply allowing to override a serializer in RegisterSerializer instead of an extra check and throwing an exception on presence. This approach seems to be more flexible. After all an application on adding a serializer presumably really needs this serializer, not anything else that for some reason is already registered. As for adding it once, it's an application itself that should care, for example, like in my code precisely.

All in all, it would be nice to be able to register different serializers for the same class. Practical use cases are presumably rare but I can see them possible. As an alternative, an extra method RegisterSerializer(..., bool force) could do this if "force" is true.

Comment by Craig Wilson [ 19/Apr/13 ]

Agreed. However, there is no good way for us to know that. There are probably a lot of classes like that, but no way for us to tell the difference.

A possible solution for us is to not automatically register a serializer and require a user to register/enable a serializer for every "custom" type it wants to use. Custom = type we don't have a pre-created serializer for. PSObject would fall into this camp. There are some obvious downsides to both strategies.

Regardless, I'm trying to pinpoint in your code using 1.8.1 where a serializer for PSObject is getting requested prior to the registration of its serializer.

Comment by Roman Kuzmin [ 19/Apr/13 ]

A thought of some not standard but possible scenarios. You cannot build any useful default map for a PSObject because it basically has no standard properties in C# sense, they are all dynamic. Only a custom serializer will do for such a class. So that if for some reason (even a mistake in a bad 3rd party module, not even the app itself) an application tried to serialize PSObject before registering a proper serializer then it is not possible to add a proper serializer at all by any means.

Comment by Craig Wilson [ 19/Apr/13 ]

Not disagreeing. I would call it a regression as well. Just trying to figure out what is different.

Comment by Roman Kuzmin [ 19/Apr/13 ]

As I wrote, in 1.8 my application works fine. The same its code (a trivial test) fails in 1.8.1. I would call this a regression.

Comment by Craig Wilson [ 19/Apr/13 ]

Before use = before the use of the object with a custom serializer. In other words, you can use the bson library as long as, before using PSObject, you have registered its serializer. A little confusing, but perhaps I can explain better based on how we handle this.

The serializer map is a global static object. When a serializer is necessary for PSObject, we consult this map. If we find one registered, we use it. Otherwise, we'll attempt to build one automatically and register it. If you attempt to register a serializer that is already present in the map, we'll throw an exception. The reason for this is obvious. It would be horrible for your application to serializer PSObject one way for a while and then switch over to using a new serializer half-way through.

So, as I alluded to above, the only way this could be happening is if something was requesting a serializer for PSObject before you registered the PSObjectSerializer.

I'm still a little confused by your statement that this is a regression. What version of the driver were you using before where this was not an issue? We literally haven't changed this behavior in a very long time.

Comment by Roman Kuzmin [ 19/Apr/13 ]

To understand the problem better try to add a unit test for RegisterSerializer. Correct me if I am wrong but there is no one. You will see a problem I am talking about. Your test should be called ASAP before any use of bson library. This is at least inconvenient. Are such conventions and difficulties really necessary for bson users now?

Comment by Roman Kuzmin [ 19/Apr/13 ]

I tried to register my serializer before connecting. This fixes the problem, I can live with this. But this is still a regression and going backwards. An application had an ability to register some serializers on demand. Now this ability has gone. This forces an application to register all possible serializers ASAP. In some cases this might be not even possible. Imagine a use case when an application loads child modules with serizlizers dynamically and at the start it does not even know which modules are going to be loaded in a session.

Thank you for the tip. My particular problem seems to be solved. But please think of my remarks.

Comment by Roman Kuzmin [ 19/Apr/13 ]

I call BsonSerializer.RegisterSerializer(typeof(PSObject), new PSObjectSerializer())
on the first need and once. The code is
https://github.com/nightroman/Mdbc/blob/master/Src/PSObjectSerializer.cs

I also described how to reproduce the problem (all you need is to have PowerShell installed). If you need more details let me know.

Comment by Craig Wilson [ 19/Apr/13 ]

Hi Roman,

Could you indicate where you are attempting to register this serializer in the lifecycle of your application. Perhaps post your code... Nothing significant related to this changed between 1.8 and 1.8.1. Registering serializers needs to happen before any attempt to use the bson library, which includes connecting to the server.

Regarding 2012, yes, we need to fix that. Changing the top 2 lines in the .sln file to 11.00 and 2010 respectively should allow it to be opened.

Comment by Roman Kuzmin [ 19/Apr/13 ]

I would debug this but you moved to VS 2012 (btw, the file name CSharpDriver-2010.sln is misleading) and I do not use VS 2012 yet.

In order to reproduce the issue you can use this PowerShell module
https://github.com/nightroman/Mdbc

import it and invoke, for example, this script
https://github.com/nightroman/Mdbc/blob/master/Tests/Test-About.ps1

Generated at Wed Feb 07 21:37:40 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.