I have a model which stores Id
(Int64
; for SQLite id column), Description
(string
), DueDate
(DateTime?
; null if not specified), and IsFinished
(bool
), such as
TodoItem.cs
public record TodoItem(Int64 Id, string Description, DateTime? DueDate = null, bool IsFinished = false);
and a ViewModel for the model.
TodoItemViewModel.cs
public class TodoItemViewModel
{
Int64 _id;
string _description;
DateTime? _dueDate;
bool _isFinished;
public Int64 Id => _id;
public string Description
{
get => _description;
set
{
this.RaiseAndSetIfChanged(ref _description, value);
}
}
// omits for DueDate, IsFinished properties
}
I want call the Database's update command when each property's setter executed. I am looking for some fancy way to pass 'which property to update'. Is there a feature for to pass property of the generic type T
like an enum? Code below for the example.
// The Type PropertyField<T> is my virtual thought
void Update(Int64 id, PropertyField<TodoItem> property, object value)
{
string sql = $"UPDATE tablename SET {property.PropertyName} = {value} WHERE id = {id}";
// execute sql command...
}
And call it by
TodoItemViewModel.cs
// ...
public string Description
{
get => _description;
set
{
this.RaiseAndSetIfChanged(ref _description, value);
Update(Id, TodoItem.Description, value); // update the description as new value for current todo item
}
}
// ...
It would be great if I can specify the corresponding type of the value (not object
, but string
for the above example).
CodePudding user response:
One solution is to take an expression tree Expression<Func<TodoItem, T>>
as the parameter.
using System.Linq.Expressions;
void Update<T>(Int64 id, Expression<Func<TodoItem, T>> property, T value) {
...
}
// example usage:
Update(someId, x => x.Description, newDescription);
Note that you get autocomplete for the properties of the record when you are typing x => x.
, and you'd get a compiler error if you make a typo. I'm assuming this is what you mean by "like an enum".
To get the name of the property from the expression tree, you can do:
if (property.Body is MemberExpression memberExpression) {
var name = memberExpression.Member.Name
// use name in the SQL query...
} else {
// the root of the expression isn't a member expression!
// perhaps throw an exception?
}
Note that it is possible to pass an expression tree whose root isn't a member access, and the compiler won't complain. For example:
x => x.Description x.IsFinished
In this case, the root of the expression tree is a " " expression, and it is unclear what the "name" of this expression is. This is currently handled by the else
branch in the code above.
See also this post for other ways of getting the name from an expression tree.