简体   繁体   中英

BsonElement attribute and custom deserialization logic with MongoDB C# driver

Consider the following example:

    public class Foo
    {
        private string _text;

        [BsonElement("text"), BsonRequired]
        public string Text
        {
            get { return _text; }
            set
            {
                _text = value;
                Bar(_text);
            }
        }

        private void Bar(string text)
        {
            //Only relevant when Text is set by the user of the class,
            //not during deserialization
        }
    }

The setter of the Text property and, subsequently, the method Bar are called both when the user of the class assigns a new value to the property and during object deserialization by the MongoDB C# driver. What I need is to ensure that Bar is called only when the Text property is set by the user and not during deserialization.

I see two solutions which don't really suit me:

The first is to move the BsonElement attribute to the backing field. However, as far as I know, the BsonElement attribute is used in query building by the MongoDB C# driver, so I will lose the ability to use the Text property in queries.

The second solution is to make the Text setter private and add a method through which the user of the class will set the Text property, and in which the Bar method would be called. However, the Text setter is used very often in the existing solution, and I'm a bit reluctant to change 70+ calls across all files. Plus, the code will become less readable.

Is there any cleaner way to separate deserialization and user-prompted property change while retaining the BsonElement attribute on the property?

Why not create a seperate property for the users and for the DB for the same private variable, something like this,

public class Foo
{
    private string _text;

    [BsonElement("text"), BsonRequired]
    public string TextDB
    {
        get { return _text; }
        set
        {
            _text = value;
        }
    }

    [BsonIgnore]
    public string Text
    {
        get { return _text; }
        set
        {
            _text = value;
            Bar(_text);
        }
    }

    private void Bar(string text)
    {
        //Only relevant when Text is set by the user of the class,
        //not during deserialization
    }
}

You can use a little trick an implement a kind of property listener.

The usage would be:

// Working with some foo here...
var foo = new Foo();
foo.Text = "Won't fire anything";

using (var propertyListener = new FooPropertiesListener(foo))
{
    foo.Text = "Something will fire you listener";
}

// Some more work with foo here...
foo.Text = "Won't fire anything";

And the implementation behind it, something like:

FooPropertiesListener

public class FooPropertiesListener : IDisposable
{
    private readonly Foo Foo;

    public FooPropertiesListener(Foo foo)
    {
        this.Foo = foo;
        this.Foo.PropertiesListener = this;
    }

    public void Bar(string text)
    {
        //Only relevant when Text is set by the user of the class,
        //not during deserialization
    }

    public void Dispose()
    {
        Foo.PropertiesListener = null;
    }
}

Foo

public class Foo
{
    internal FooPropertiesListener PropertiesListener;

    private string _text;

    [BsonElement("text"), BsonRequired]
    public string Text
    {
        get { return _text; }
        set
        {
            _text = value;
            if (PropertiesListener != null)
            {
                PropertiesListener.Bar(_text);
            }
        }
    }
}

I know this question is old, but I'd still like to help for other people stumbling on this issue as I have done.

It basically boils down to something very simple: serialization and deserialization are not limited to public fields and properties!

The next example will cover the original question without having to invent dubious secondary properties:

public class Foo
{
    [BsonElement("Text"), BsonRequired]
    private string _text;

    [BsonIgnore]
    public string Text
    {
        get { return _text; }
        set
        {
            _text = value;
            Bar(_text);
        }
    }

    private void Bar(string text)
    {
        //Only relevant when Text is set by the user of the class,
        //not during deserialization
    }
}

Simply put your BsonElement class on the backing field and tell it to BsonIgnore the property. You can do whatever you like in the getter and setter without having to worry about deserialization which now occurs on private field level.

Hope this helps somebody!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM