LINQ to Entities does not recognize the method 'System.Object GetValue(...)' - c#

My issue is I need to query on the value of a property in a generic class. The property is tagged with an attribute.
See the following code:
var rowKeyProperty = EFUtil.GetClassPropertyForRowKey<T>();
var tenantKeyProperty = EFUtil.GetClassPropertyForTenantKey<T>();
var queryResult =
objContext.CreateObjectSet<T>().Single(l => (((int) tenantKeyProperty.GetValue(l, null)) == tenantKey) &&
(((int)rowKeyProperty.GetValue(l, null)) == KeyValue));
The rowKeyProperty and tenantKeyProperty are of type System.Reflection.PropertyInfo.
I understand why I am getting the error. When the linq query is translated to SQL, it can't understand the property.GetValue.
However, I'm completely stumped as to a work around here. Does anyone have any ideas how to achieve this? Thx.

You need to actually build up the Expression objects to represent the expression that you want this to mimic, in this case the expression you want to represent is:
l => l.SomeProperty == SomeValue
So you need to build up each component of that bit by bit, from creating the parameter, defining the equality operator, the property access, the constant value, etc.
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
PropertyInfo property, TValue value)
{
var param = Expression.Parameter(typeof(TItem));
var body = Expression.Equal(Expression.Property(param, property),
Expression.Constant(value));
return Expression.Lambda<Func<TItem, bool>>(body, param);
}
Once you have all of that you can call it using the data that you have:
var queryResult = objContext.CreateObjectSet<T>()
.Where(PropertyEquals<T, int>(tenantKeyProperty, tenantKey))
.Where(PropertyEquals<T, int>(rowKeyProperty, KeyValue))
.Single();

Appendix here... Following #Servy answer and based on this topic with a nice answer by #TomBrothers, you can use the same logic to make a StartsWith (or similar) function:
public static Expression<Func<TItem, bool>> PropertyStartsWith<TItem>(PropertyInfo propertyInfo, string value)
{
var param = Expression.Parameter(typeof(TItem));
var m = Expression.MakeMemberAccess(param, propertyInfo);
var c = Expression.Constant(value, typeof(string));
var mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
var body = Expression.Call(m, mi, c);
return Expression.Lambda<Func<TItem, bool>>(body, param);
}
In this case, it forces value to be a string.

It is more correct to specify the type in Expression.Constant(value, typeof(TValue)))
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
string property, TValue value)
{
var xParameter = Expression.Parameter(typeof(TItem));
var body = Expression.Equal(Expression.Property(xParameter, property), Expression.Constant(value, typeof(TValue)));
return Expression.Lambda<Func<TItem, bool>>(body, xParameter);
}
Or, like this, to check the property. ChangeType
public static Expression<Func<TItem, bool>> PropertyEquals<TItem, TValue>(
string property, TValue value)
{
var xParameter = Expression.Parameter(typeof(TItem));
var type = typeof(TItem).GetProperty(property).PropertyType;
value = ChangeType<TValue>(value);
BinaryExpression body = Expression.Equal(Expression.Property(xParameter, property), Expression.Constant(value, type));
return Expression.Lambda<Func<TItem, bool>>(body, xParameter);
}
What is it for. I check all class references to classes, I look for "..ID" entries. Somewhere I have a type "int" and "int?".
public class BudgetLimit : BaseRecord
{
[Required]
public int DepartmentID { get; set; }
public virtual Department Department { get; set;}
public int? ProjectID { get; set; }
public virtual Project Project { get; set; }
}

You add .AsEnableable after the LINQ statement.
e.g objectdata.AsEnumerable()
enter link description here

Related

Make dynamic expression of EF core "Like" function

I've written some codes to make dynamic expressions for filtering my pagination.
I'm trying to make a dynamic expression of EF Core built-in functions for searching (EF.Functions.Like).
I've tried a way like bottom but it is an extension method and first parameters is not used when calling the method. I don't know how to follow the way ==> Ef => Function => Like.
The method should be used like this => Ef.Functions.Like("Property to search", "%Some Pattern")
var likeMethod = typeof(DbFunctionsExtensions)
.GetMethods()
.Where(p => p.Name == "Like")
.First();
string pattern = $"%{finalConstant}%";
ConstantExpression likeConstant = Expression.Constant(pattern,typeof(string));
// the member expression is the property expression for example p.Name
var likeMethodCall = Expression.Call(method: likeMethod, arguments: new[] { memberExpression, likeConstant });
var searchLambda = Expression.Lambda<Func<T, bool>>(likeMethodCall, parameter);
query = query.Where(searchLambda);
but it throw exception saying
Incorrect number of arguments supplied for call to method 'Boolean Like(Microsoft.EntityFrameworkCore.DbFunctions, System.String,
System.String)'\r\nParameter name: method
I implemented a dynamic search based on this article
.NET Core Npgsql.EntityFrameworkCore ILikeExpression
That's what I did:
I implement the [Searchable] attribute, with which I will mark the properties by which the search will be performed. Properties are only of type string, if necessary I can explain how to search for properties of type long and int.
[AttributeUsage(AttributeTargets.Property)]
public class SearchableAttribute : Attribute
{
}
An extension has been created for IQueryable , which takes the input string from the search and implements the Like function according to the specified properties
public static class QueryableExtension
{
public static IQueryable<TEntityDto> ExecuteQueryFilter<TEntityDto>(this IQueryable<TEntityDto> queryable, string query)
where TEntityDto : class, IEntityDto
{
// If the incoming request is empty, skip the search
if (string.IsNullOrEmpty(query))
{
return queryable;
}
// We get all properties with type of string marked with our attribute
var properties = typeof(TEntityDto).GetProperties()
.Where(p => p.PropertyType == typeof(string) &&
p.GetCustomAttributes(typeof(SearchableAttribute), true).FirstOrDefault() != null)
.Select(x => x.Name).ToList();
// If there are no such properties, skip the search
if (!properties.Any())
{
return queryable;
}
// Get our generic object
ParameterExpression entity = Expression.Parameter(typeof(TEntityDto), "entity");
// Get the Like Method from EF.Functions
var efLikeMethod = typeof(DbFunctionsExtensions).GetMethod("Like",
BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic,
null,
new[] { typeof(DbFunctions), typeof(string), typeof(string) },
null);
// We make a pattern for the search
var pattern = Expression.Constant($"%{query}%", typeof(string));
// Here we will collect a single search request for all properties
Expression body = Expression.Constant(false);
foreach (var propertyName in properties)
{
// Get property from our object
var property = Expression.Property(entity, propertyName);
// Сall the method with all the required arguments
Expression expr = Expression.Call(efLikeMethod,
Expression.Property(null, typeof(EF), nameof(EF.Functions)), property, pattern);
// Add to the main request
body = Expression.OrElse(body, expr);
}
// Compose and pass the expression to Where
var expression = Expression.Lambda<Func<TEntityDto, bool>>(body, entity);
return queryable.Where(expression);
}
}
The Dto object itself looks like this:
public class CategoryDto : IEntityDto
{
public long Id { get; set; }
[Searchable]
public string Name { get; set; }
[Searchable]
public string IconKey { get; set; }
public long UploadId { get; private set; }
[Searchable]
public string UploadFileName { get; set; }
[Searchable]
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
}
I tested this search method on one million records, with objects name in one to five words. The search process very fast. The performance benefit here is that Expression is converted on the database side as LINQ to SQL
Here's a working example
public static Expression<Func<T, bool>> Like<T>(Expression<Func<T, string>> prop, string keyword)
{
var concatMethod = typeof(string).GetMethod(nameof(string.Concat), new[] { typeof(string), typeof(string) });
return Expression.Lambda<Func<T, bool>>(
Expression.Call(
typeof(DbFunctionsExtensions),
nameof(DbFunctionsExtensions.Like),
null,
Expression.Constant(EF.Functions),
prop.Body,
Expression.Add(
Expression.Add(
Expression.Constant("%"),
Expression.Constant(keyword),
concatMethod),
Expression.Constant("%"),
concatMethod)),
prop.Parameters);
}
query = query.Where(Like<User>(u => u.UserName, "angel"));
As mentioned in the comment, you need to include EF.Functions as the first parameter:
var likeMethodCall = Expression.Call(likeMethod, new []
{
Expression.Property(null, typeof(EF).GetProperty("Functions")),
memberExpression,
likeConstant
});

Dynamically create lambda search on IQueryable on nested objects

I'm trying to build a dynamic search on nested objects, which will later be sent to EF and SQL Server. So far, I'm able to search on all properties of the first object. Here's a very simplified version:
public class User
{
public string Name { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string City { get; set; }
}
public class MyClass<TEntity> where TEntity : class {
public IQueryable<TEntity> applySearch(IQueryable<TEntity> originalList, string propName, string valueToSearch) {
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "p");
PropertyInfo propertyInfo = typeof(TEntity).GetProperty(propName);
MemberExpression member = Expression.MakeMemberAccess(parameterExpression, propertyInfo);
lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(member, Expression.Constant(valueToSearch)), parameterExpression);
return originalList.Where(expression);
}
}
When propName = "Name" everything is fine, but when propName = "Address.City", the propertyInfo is null, and I get this error on the member assignment line:
System.ArgumentNullException: Value cannot be null
I was able to obtain the propertyInfo of the nested property using the solution from this answer:
PropertyInfo propertyInfo = GetPropertyRecursive(typeof(TEntity), propName);
...
private PropertyInfo GetPropertyRecursive(Type baseType, string propertyName)
{
string[] parts = propertyName.Split('.');
return (parts.Length > 1)
? GetPropertyRecursive(baseType.GetProperty(parts[0]).PropertyType, parts.Skip(1).Aggregate((a, i) => a + "." + i))
: baseType.GetProperty(propertyName);
}
But then I get this error on member assignment:
System.ArgumentException: Property 'System.String City' is not defined for type 'User'
This should point to Address instead of User, but I don't know if I'm on right track here, I mean, should I change parameterExpression now?
How can I make a dynamic search on nested objects, so that this can be turned into a lambda expression and later sent to SQL?
After Kobi's advice and a lot of trial and error, I finally got this working. This uses the Universal PredicateBuilder. Here it is:
public class MyClass<TEntity> where TEntity : class
{
public IQueryable<TEntity> ApplySearch(IQueryable<TEntity> originalList, string valueToSearch, string[] columnsToSearch)
{
Expression<Func<TEntity, bool>> expression = null;
foreach (var propName in columnsToSearch)
{
Expression<Func<TEntity, bool>> lambda = null;
ParameterExpression parameterExpression = Expression.Parameter(typeof(TEntity), "p");
string[] nestedProperties = propName.Split('.');
Expression member = parameterExpression;
foreach (string prop in nestedProperties)
{
member = Expression.PropertyOrField(member, prop);
}
var canConvert = CanConvertToType(valueToSearch, member.Type.FullName);
if (canConvert)
{
var value = ConvertToType(valueToSearch, member.Type.FullName);
if (member.Type.Name == "String")
{
ConstantExpression constant = Expression.Constant(value);
MethodInfo mi = typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) });
Expression call = Expression.Call(member, mi, constant);
lambda = Expression.Lambda<Func<TEntity, bool>>(call, parameterExpression);
}
else
{
lambda = Expression.Lambda<Func<TEntity, bool>>(Expression.Equal(member, Expression.Constant(value)), parameterExpression);
}
}
if (lambda != null)
{
if (expression == null)
{
expression = lambda;
}
else
{
expression = expression.Or(lambda);
}
}
}
if (expression != null)
{
return originalList.Where(expression);
}
return originalList;
}
}
private bool CanConvertToType(object value, string type)
{
bool canConvert;
try
{
var cValue = ConvertToType(value, type);
canConvert = true;
}
catch
{
canConvert = false;
}
return canConvert;
}
private dynamic ConvertToType(object value, string type)
{
return Convert.ChangeType(value, Type.GetType(type));
}
Warning in advance - I'm not building the expression, just inspecting its structure.
When I need to dynamically create Expressions, I find it useful to inspect an Expression and copy its structure:
Expression<Func<User, string>> getCity = user => user.Address.City;
Now you can simply debug it, for example in the immediate window (ctrlalti here):
getCity
{user => user.Address.City}
Body: {user.Address.City}
CanReduce: false
DebugView: ".Lambda #Lambda1<System.Func`2[ConsoleApplication1.User,System.String]>(ConsoleApplication1.User $user) {\r\n ($user.Address).City\r\n}"
Name: null
NodeType: Lambda
Parameters: Count = 1
ReturnType: {Name = "String" FullName = "System.String"}
TailCall: false
Here we can see getCity is a Lambda with one parameter. Let's inspect it's body:
getCity.Body
{user.Address.City}
CanReduce: false
DebugView: "($user.Address).City"
Expression: {user.Address}
Member: {System.String City}
NodeType: MemberAccess
Type: {Name = "String" FullName = "System.String"}
getCity.Body is a member access - it accesses the member City of the Expression user.Address. Technically that's a PropertyExpression, which is an internal class so we can't even cast to it, but that's OK.
Finally, let's look at that inner expression:
((MemberExpression)getCity.Body).Expression
{user.Address}
CanReduce: false
DebugView: "$user.Address"
Expression: {user}
Member: {ConsoleApplication1.Address Address}
NodeType: MemberAccess
Type: {Name = "Address" FullName = "ConsoleApplication1.Address"}
That's just user.Address.
Now we can build an identical expression:
var addressProperty = typeof (User).GetProperty("Address");
var cityProperty = typeof(Address).GetProperty("City");
var userParameter = Expression.Parameter(typeof (User), "user");
var getCityFromUserParameter = Expression.Property(Expression.Property(userParameter, addressProperty), cityProperty);
var lambdaGetCity = Expression.Lambda<Func<User, string>>(getCityFromUserParameter, userParameter);
Expression.MakeMemberAccess works too, instead of Expression.Property.
Obviously, you'd need to build your expression in a loop, and more dynamically, but the structure is the same.
It might be worth taking a look at Linqkit's predicate builder...
http://www.albahari.com/nutshell/predicatebuilder.aspx
I'd also take a look at Entity SQL...
https://msdn.microsoft.com/en-us/library/vstudio/bb387145(v=vs.100).aspx
You might be reinventing a wheel with the code you're writing.
Also I should comment, in terms of the SQL Server plan caching, unless you have no other choice I wouldn't dynamically build queries. You're better off creating a single query that handles all your cases that SQL Server can cache a plan for, your queries will run a lot slower if every time they're executed no plan is hit in SQL Server's plan cache.

Expression.Like in C#

eg: x=> x.Name = "g"
I have code block like this
public Expression<Func<TEntity, bool>> SearchExpression()
{
var c = new ConstantExpression[_paramList.Count];
var b = new BinaryExpression[_paramList.Count];
BinaryExpression comparisonExpression = null;
var entity = Expression.Parameter(typeof(TEntity));
for (int i = 0; i < _paramList.Count; i++)
{
var value = Convert.ChangeType(_paramList[i].Item2 /*"g"*/, _paramList[i].Item3 /*System.String*/);
c[i] = Expression.Constant(value); //"g"
// PROBLEM IS HERE
b[i] = Expression.Equal(Expression.Property(entity, _paramList[i].Item1 /*Name*/, c[i]);
// PROBLEM IS HERE
}
_paramList.Clear();
comparisonExpression = b.Aggregate(Expression.And);
return Expression.Lambda<Func<TEntity, bool>>(comparisonExpression, entity);
}
works like charm but I need Expression.Like (Like "g" not Equal "g")
Expression.Like(Expression.Property(entity, _paramList[i].Item1), c[i])
but C# expression tree does not support Like method
UPDATE :
I wrote something like this :
Expression.Call(Expression.Property(entity, _paramList[i].Item1),
typeof(String).GetMethod("Contains"), new Expression[] { c[i] });
but I need BinaryExpression not MethodCallExpression
You can make your code work by adding an equals expression over the method call, like so:
b[i] = Expression.Equal(
Expression.Call(Expression.Property(entity, _paramList[i].Item1),
typeof (String).GetMethod("Contains"),
new Expression[] {c[i]}), Expression.Constant(true));
In pseudo code this reads as:
b[i] = entity => entity.someProperty.Contains(c[i]) == true;
Which will return a binary expression for you.
This answer does not consider your array and the 'and' aggregation, but this should be considered as a separate issue.
Consider this class:
class MyEntity { string Name { get; set; } }
We want to query:
select ... from MyEntity where Name like '%query%';
The following method is a general implementation of the above query pattern:
static Expression<Func<TEntity, bool>> Like<TEntity>(string propertyName, string queryText)
{
var parameter = Expression.Parameter(typeof (TEntity), "entity");
var getter = Expression.Property(parameter, propertyName);
//ToString is not supported in Linq-To-Entities, throw an exception if the property is not a string.
if (getter.Type != typeof (string))
throw new ArgumentException("Property must be a string");
//string.Contains with string parameter.
var stringContainsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});
var containsCall = Expression.Call(getter, stringContainsMethod,
Expression.Constant(queryText, typeof (string)));
return Expression.Lambda<Func<TEntity, bool>>(containsCall, parameter);
}
If you want to have a pattern of query% or %query you can use string.StartsWith and string.EndsWith instead of Contains.
Also, you can share the parameter across multiple calls if you adjust the signature.
The current implementation throws an exception if the data type of the property is not a string. Look at this answer https://stackoverflow.com/a/3292773/668272 for converting numbers to strings.
I've done this in a scripting language I wrote, which allows you to say things like name like 'bob%'. The trick is that you need to map it to a method call which takes the value and regular expression and call this from within the Expression.
If you take a look at the LikeEvaluator class in my Wire scripting language you'll see how I did it:
static class LikeEvaluator
{
private static readonly MethodInfo ApplyLikeMethodInfo=typeof(LikeEvaluator).GetMethod("ApplyLike");
private static readonly MethodInfo ApplyLikeNoCaseMethodInfo=typeof(LikeEvaluator).GetMethod("ApplyLikeNoCase");
public static Expression Like(CaseMode caseMode, Expression lhs, Expression pattern)
{
Expression x=null;
if(caseMode==CaseMode.Sensitive)
{
x=Expression.Call(ApplyLikeMethodInfo,lhs,pattern);
}
else
{
x=Expression.Call(ApplyLikeNoCaseMethodInfo,lhs,pattern);
}
return x;
}
public static bool ApplyLike(string text, string likePattern)
{
string pattern=PatternToRegex(likePattern);
return Regex.IsMatch(text,pattern,RegexOptions.None);
}
public static bool ApplyLikeNoCase(string text, string likePattern)
{
string pattern=PatternToRegex(likePattern);
return Regex.IsMatch(text,pattern,RegexOptions.IgnoreCase);
}
public static string PatternToRegex(string pattern)
{
pattern=Regex.Escape(pattern);
pattern=pattern.Replace("%",#".*");
pattern=string.Format("^{0}$",pattern);
return pattern;
}
}

Using reflection to obtain values based on a specific query condition

I have a slightly different issue to the one answered here (Using reflection to retrieve a value from a list)
While the approved answer here works fine for select, I'd like to extend it so that I can obtain data from a query based on a condition type. Currently, my adapted code looks like this
public static async Task<T> GetDataFromTable<T>(string paramName, string condition="")
{
var k = Activator.CreateInstance(typeof(T));
var mn = typeof(T).GetProperty(paramName);
var tc = typeof(T).GetProperty(condition);
if (mn == null || !ftrackData.Online)
return (T)k;
var data = GetTableData<T>();
if (!string.IsNullOrEmpty(paramName))
{
var retval = data.Select(t => mn.GetValue(t, null));
return (T)retval;
}
else
return (T)data.FirstOrDefault(t => mn.GetValue(t, null) > tc.GetType(t, null)).ToList();
}
I'd be looking to have the ">" in the final return change depending on an additional parameter passed into the arguments list. I know I can do a simple switch after the else, but is there some way to change the condition by insertion?
Your code doesn't really make any sense. The Select extension method takes a Func<T, TResult> which implies the return type should be IEnumerable<TResult> whereas you specify it as T.
In your question you want to do a Select and FirstOrDefault in the same method, but that is not possible since the result types will be different.
Select:
public static async Task<IEnumerable<TResult>> SelectData<T, TResult>(
string propertyName
)
{
if(string.IsNullOrWhiteSpace(propertyName))
{
return Enumerable.Empty<TResult>();
}
var dataTask = GetTableData<T>();
var tType = Expression.Parameter(typeof(T), "t");
var property = Expression.Property(tType, propertyName);
var selectExpression =
Expression.Lambda<Func<T, TResult>>(property, tType)
.Compile();
return (await dataTask).Select(selectExpression);
}
FirstOrDefault:
public static async Task<T> FirstOrDefaultData<T>(
string propertyName,
string conditionName,
Func<MemberExpression, MemberExpression, BinaryExpression> comparer
)
{
if(string.IsNullOrWhiteSpace(propertyName) ||
string.IsNullOrWhileSpace(conditionName) ||
comparer == null
{
return default(T);
}
var dataTask = GetTableData<T>();
var tType = Expression.Parameter(typeof(T), "t");
var property = Expression.Property(tType, propertyName);
var condition = Expression.Property(tType, conditionName);
var binaryExpression =
Expression.Lambda<Func<T, bool>>(comparer(property, condition), tType)
.Compile();
return (await dataTask).FirstOrDefault(binaryExpression);
}
Usage:
public class Foo
{
public string Bar { get; set; }
public bool Flag { get; set; }
}
var bars = SelectData<Foo, string>("Bar");
var foo = FirstOrDefaultData<Foo>("Bar",
"Flag",
(p, c) => Expression.GreaterThan(p, c));

How set value a property selector Expression<Func<T,TResult>>

i need associate a entity property Address in my Person class entity with expressions linq in my FactoryEntities class using pattern factory idea, look this is what I have and I want to do:
Address address = new Address();
address.Country = "Chile";
address.City = "Santiago";
address.ZipCode = "43532";
//Factory instance creation object
//This is idea
Person person = new FactoryEntity<Person>().AssociateWithEntity(p=>p.Address, address);
public class Person: Entity
{
public string Name{ get; set; }
public string LastName{ get; set; }
public Address Address{ get; set; }
}
public class Address: Entity
{
public string Country{ get; set; }
public string City{ get; set; }
public string ZipCode{ get; set; }
}
public class FactoryEntity<TEntity> where TEntity : Entity
{
public void AssociateWithEntity<TProperty>(Expression<Func<TEntity, TProperty>> entityExpression, TProperty newValueEntity) where TProperty : Entity
{
if (instanceEntity == null || instanceEntity.IsTransient())
throw new ArgumentNullException();
/*TODO: Logic the association and validation
How set the newValueEntity into the property of entityExpression (x=>x.Direccion = direccion*/
}
}
This works:
The following helper method converts a getter expression into a setter delegate. If you want to return an Expression<Action<T,TProperty>> instead of an Action<T,TProperty>, just don't call the Compile() method at the end.
Note: The code is from Ian Mercer's blog: http://blog.abodit.com/2011/09/convert-a-property-getter-to-a-setter/
/// <summary>
/// Convert a lambda expression for a getter into a setter
/// </summary>
public static Action<T, TProperty> GetSetter<T, TProperty>(Expression<Func<T, TProperty>> expression)
{
var memberExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberExpression.Member;
var setMethod = property.GetSetMethod();
var parameterT = Expression.Parameter(typeof(T), "x");
var parameterTProperty = Expression.Parameter(typeof(TProperty), "y");
var newExpression =
Expression.Lambda<Action<T, TProperty>>(
Expression.Call(parameterT, setMethod, parameterTProperty),
parameterT,
parameterTProperty
);
return newExpression.Compile();
}
You can set the property like this:
public void AssociateWithEntity<TProperty>(
Expression<Func<TEntity, TProperty>> entityExpression,
TProperty newValueEntity)
where TProperty : Entity
{
if (instanceEntity == null)
throw new ArgumentNullException();
var memberExpression = (MemberExpression)entityExpression.Body;
var property = (PropertyInfo)memberExpression.Member;
property.SetValue(instanceEntity, newValueEntity, null);
}
This will work only for properties, not fields, although adding support for fields should be easy.
But the code you have for getting the person won't work. If you want to keep the void return type of AssociateWithEntity(), you could do it like this:
var factory = new FactoryEntity<Person>();
factory.AssociateWithEntity(p => p.Address, address);
Person person = factory.InstanceEntity;
Another option is a fluent interface:
Person person = new FactoryEntity<Person>()
.AssociateWithEntity(p => p.Address, address)
.InstanceEntity;
Another solution is to get the property owner and invoke the property setter using reflection. The advantage of this solution is that it does not use extension methods and can be called with any type.
private void SetPropertyValue(Expression<Func<object, object>> lambda, object value)
{
var memberExpression = (MemberExpression)lambda.Body;
var propertyInfo = (PropertyInfo)memberExpression.Member;
var propertyOwnerExpression = (MemberExpression)memberExpression.Expression;
var propertyOwner = Expression.Lambda(propertyOwnerExpression).Compile().DynamicInvoke();
propertyInfo.SetValue(propertyOwner, value, null);
}
...
SetPropertyValue(s => myStuff.MyPropy, newValue);
This is my solution that uses Expression.Assign, but after looking more closely, the accepted answer is just as good.
// optionally or additionally put in a class<T> to capture the object type once
// and then you don't have to repeat it if you have a lot of properties
public Action<T, TProperty> GetSetter<T, TProperty>(
Expression<Func<T, TProperty>> pExpression
) {
var parameter1 = Expression.Parameter(typeof(T));
var parameter2 = Expression.Parameter(typeof(TProperty));
// turning an expression body into a PropertyInfo is common enough
// that it's a good idea to extract this to a reusable method
var member = (MemberExpression)pExpression.Body;
var propertyInfo = (PropertyInfo)member.Member;
// use the PropertyInfo to make a property expression
// for the first parameter (the object)
var property = Expression.Property(parameter1, propertyInfo);
// assignment expression that assigns the second parameter (value) to the property
var assignment = Expression.Assign(property, parameter2);
// then just build the lambda, which takes 2 parameters, and has the assignment
// expression for its body
var setter = Expression.Lambda<Action<T, TProperty>>(
assignment,
parameter1,
parameter2
);
return setter.Compile();
}
Another thing you can do is encapsulate them:
public sealed class StrongProperty<TObject, TProperty> {
readonly PropertyInfo mPropertyInfo;
public string Name => mPropertyInfo.Name;
public Func<TObject, TProperty> Get { get; }
public Action<TObject, TProperty> Set { get; }
// maybe other useful properties
internal StrongProperty(
PropertyInfo pPropertyInfo,
Func<TObject, TProperty> pGet,
Action<TObject, TProperty> pSet
) {
mPropertyInfo = pPropertyInfo;
Get = pGet;
Set = pSet;
}
}
And now you can pass these around, similar to delegates, and write code whose logic can vary by property. This gets around the fact that you can't pass properties by reference.
That's the idea, i'm worked for me with this code, taking into account the contribution of svick:
public class FactoryEntity<TEntity> where TEntity : Entity, new()
{
private TEntity _Entity;
public FactoryEntity()
{
_Entity = new TEntity();
}
public TEntity Build()
{
if (_Entity.IsValid())
throw new Exception("_Entity.Id");
return _Entity;
}
public FactoryEntity<TEntity> AssociateWithEntity<TProperty>(Expression<Func<TEntity, TProperty>> foreignEntity, TProperty instanceEntity) where TProperty : Entity
{
if (instanceEntity == null || instanceEntity.IsTransient())
throw new ArgumentNullException();
SetObjectValue<TEntity, TProperty>(_Entity, foreignEntity, instanceEntity);
return this;
}
private void SetObjectValue<T, TResult>(object target, Expression<Func<T, TResult>> expression, TResult value)
{
var memberExpression = (MemberExpression)expression.Body;
var propertyInfo = (PropertyInfo)memberExpression.Member;
var newValue = Convert.ChangeType(value, value.GetType());
propertyInfo.SetValue(target, newValue, null);
}
}
Here I call the factory for me to build the Person object in a valid
Person person = new FactoryEntity<Person>().AssociateWithEntity(p=>p.Address, address).Build();
But I do not know if this code is optimal or not, at least I do not make a call to compile() method, what are saying?
thanks
I've made mixed Rytis I solution and https://stackoverflow.com/a/12423256/254109
private static void SetPropertyValue<T>(Expression<Func<T>> lambda, object value)
{
var memberExpression = (MemberExpression)lambda.Body;
var propertyInfo = (PropertyInfo)memberExpression.Member;
var propertyOwnerExpression = (MemberExpression)memberExpression.Expression;
var propertyOwner = Expression.Lambda(propertyOwnerExpression).Compile().DynamicInvoke();
propertyInfo.SetValue(propertyOwner, value, null);
}
And call it
SetPropertyValue(() => myStuff.MyProp, newValue);
Everything is much simpler:
public static Action<T, TValue> GetSetter<T, TValue>(
Expression<Func<T, TValue>> expression)
{
var parameter = Expression.Parameter(typeof(TValue), "value");
var setterLambda = Expression.Lambda<Action<T, TValue>>(
Expression.Assign(expression.Body, parameter),
expression.Parameters[0],
parameter);
return setterLambda.Compile();
}

Categories

Resources