2

I am not sure how to word this question properly, so apologies at the very beginning.

I am working with MongoDb and at some point, I need to design a lambda expression as follows:

...Set(g => g.Profile.First_Name, "Test");

Now, g refers to a class called User, which has a property of type Profile(a different class), with the same name Profile. As you can see, in the lambda expression, I am selecting the First_Name property and then passing the value "Test" to it.

My Question: Is there a way to select the property by name?

In more detail - is something like this possible? :

 .... g.Profile. ("First_Name")

As I am typing this, even to me it sounds ridiculous, but I need to dynamically select the particular properties therefore I need to select them via their names.

How do I actually achieve this?

I tried :

g.Profile.GetType().GetProperty("First_Name")

But it doesn't seem to be equivalent to g.Profile.First_Name.

Any ideas on what can be done?

codlix
  • 858
  • 1
  • 8
  • 24
Software Dev
  • 5,368
  • 5
  • 22
  • 45
  • You need to provide a bit more information about what you're trying to do. You mention MongoDb. Are you trying to update a document in MongoDb using MongoDb Driver? – Steve Aug 06 '19 at 21:03
  • Why is that not equivalent? Seems like you are on the right road to me... `GetProperty` will return a `PropertyInfo`, you then need to call `GetValue` or `SetValue` if you want to do something with it (passing the object to modify, `g.Profile` in this case). I'd put that as an answer but not sure where you are stuck – BradleyDotNET Aug 06 '19 at 21:06
  • @Steve, yes. The document structure is of the structure of the `User` class, means that the document has fields same as the `User` class'es properties. Now, inside the `User` class, there's a `Profile` class, defined as a property as well. What i am trying to do is update only particular fields of the `Property` class, which is inside the `User` class. The issue here is that, i need to dynamically select ONLY the given properties. The property names would be served as string, so i need to find a way to select them based on the name provided. – Software Dev Aug 06 '19 at 21:07
  • @BradleyDotNET, okay, how can i explain ? Okay, read my other comment and you'll get the idea here. – Software Dev Aug 06 '19 at 21:08
  • 1
    @BradleyDotNET I think the issue is the fact that that `Set` takes a lambda of the property name and a value to set it to (which it then does in Mongo-speak). – Heretic Monkey Aug 06 '19 at 21:08
  • @HereticMonkey, Not quite actually. Rather it works if i pass only a string instead of the lambda expression, like `.Set("First_Name" , "Test"). The problem here is that, in this way, MongoDb creates a new field called `First_Name` inside the `User` document, where as it had to create/set value for the field, which is in the `Profile` object, which is in the `User` object/document. – Software Dev Aug 06 '19 at 21:09
  • I think the answers to [LINQ select property by name](https://stackoverflow.com/a/47781545/215552) might get you going in the right direction. Basically, you have to write the `Expression` manually rather than relying on the syntax. That can get tricky real quick; [Marc's answer is for a simple case where it's always a string property](https://stackoverflow.com/a/47781545/215552). If you have a number of properties and they have different types, it can be daunting... – Heretic Monkey Aug 06 '19 at 21:11
  • @RufusL, sorry, that won't work. – Software Dev Aug 06 '19 at 21:12
  • @Aousaf I figured there'd be an overload like that, with just that kind of limitation :(. I think my last comment might get you part way there; I'm still looking to see if there's a question where the property is nested like yours is. I think I've seen one, but apparently I didn't bookmark it. – Heretic Monkey Aug 06 '19 at 21:15
  • 1
    Finally fixed it! Just did this : `.Set("Profile.First_Name", valueGoesHere)` and MongoDb worked as expected! – Software Dev Aug 06 '19 at 21:17

1 Answers1

3

I probably don't actually want to use reflection here.

The g => ... is not actually a lambda function, but an expression tree — it's some fancy syntactic sugar for describing a process, in this case, a process for getting a certain property given a record of type User. MongoDB API provides overloads that allow expression so you can code 90% of your operations more gracefully, but if you really need to be dynamic you can of course bypass this, for example:

var setName = Builders<User>.Update.Set(g => g.Profile.First_Name, "Test");

Is roughly equivalent to something like:

var setName = Builders<User>.Update.Set("profile.first_name", "Test");

Of course, if you have BsonProperty attributes on your properties, custom conventions, etc. that can change the path of the BSON being updated.

Now, suppose you don't want to have to hard-code the BSON paths. Well, you can generate dynamic expression trees, but it's not very elegant or performant. You'd use something along the lines of this:

ParameterExpression param = Expression.Parameter(typeof(User), "g");
Expression<Func<User, String>> expr =
    Expression.Lambda<Func<User, String>>(
        Expression.Property(
            Expression.Property(
                param,
                "Profile"
            ),
            "First_Name"
        ),
        new[] { param }
    );

 var setName = Builders<User>.Update.Set(expr, "Test");
p.s.w.g
  • 146,324
  • 30
  • 291
  • 331
  • `var setName = Builders.Update.Set("profile.first_name", "Test");` i figured this part out and it works perfectly! Thanks – Software Dev Aug 06 '19 at 21:20