Entity Framework code first, set column name on a Boolean property - c#

Im attempting to set the column name on a boolean property using reflection.
This works for the standard String, Int etc but there is no Property method that takes a Expression<Func<Object,Boolean>>
Essentially its this modelBuilder.Entity<Object>().Property(g => g.boolDeleted).HasColumnName("BooleanColumn");
ParameterExpression parameter = Expression.Parameter(entityObject, "t");
var expressionProperty = Expression.Property(parameter, propertyInfo.Name);
const string methodSignature = "System.Linq.Expressions.Expression`1[TDelegate] Lambda[TDelegate]" + "(System.Linq.Expressions.Expression, System.Linq.Expressions.ParameterExpression[])";
var generatedLambdaMethod = typeof(Expression).GetMethods()
.Single(mi => mi.ToString() == methodSignature);
var func = typeof(Func<,>).MakeGenericType(new[] { entityObject, typeof(Boolean) });
var genericLambda = generatedLambdaMethod.MakeGenericMethod(func);
var generatedLambdaInvoked = genericLambda.Invoke(null,
new object[] { expressionProperty, new[] { parameter } });
var method = modelBuilder.GetType().GetMethod("Entity");
var genericMethod = method.MakeGenericMethod(new[] { entityObject })
.Invoke(modelBuilder, new object[] { });
var propertyMethod = genericMethod.GetType()
.GetMethods()
.Where(m => m.Name == "Property" && m.GetParameters().Where(p => p.ParameterType == generatedLambdaInvoked .GetType()).Any() == true)
.Single();
var propInvoked = propertyMethod.Invoke(genericMethod, new[] { generatedLambdaInvoked });
var hasColumnNameInvoked = propInvoked.GetType()
.GetMethods()
.Where(m => m.Name == "HasColumnName")
.First()
.Invoke(propInvoked, new object[] { "BooleanColumn" });
What do I do to set the name of this column?
EF can read the column so this cant be out of the question.

var propertyMethods = genericMethod.GetType()
.GetMethods()
.Where(m => m.Name == "Property" && m.IsGenericMethodDefinition)
.First()
.MakeGenericMethod(new[] { typeof(Boolean) });
I hope this helps someone in the future, Theres an optional generic constructor for the method Property(). A problem arises if there's a nullable boolean
this is what I did.
var propertyMethods = genericMethod.GetType()
.GetMethods()
.Where(m => m.Name == "Property" && m.IsGenericMethodDefinition)
.ToList();
if (Nullable.GetUnderlyingType(typeof(Boolean?)) != null)
{
propertyMethod = propertyMethods.ElementAt(1)
.MakeGenericMethod(new[] { Nullable.GetUnderlyingType(typeof(Boolean?)) });
}
else
{
propertyMethod = propertyMethods.ElementAt(0)
.MakeGenericMethod(new[] { typeof(Boolean) });
}
So really you call the generic method that takes a Nullable, but you make the generic with the non-nullable type.

Related

Entity Framework C# queries from strings

Is it possible to get elements in a sqlite table from a string with the name of the table (using Entity Framework)? How?
And how can I get only the value of a property? (I need to get a list of IDs to create a in html that's used to choose which element in a table the user wants to delete)
using using Microsoft.EntityFrameworkCore;
public static List<string> GetAllIdsFromTableName(string tableName)
{
var db = new dbContext();
// What I would like to do:
// return db.tableName.Select(x => x.id).ToList<string>();
}
The following extension returns IQueryable<string> and you can materialise arrays, lists, or you can do it asynchronously:
var result = context.GetAllIdsFromTable("SomeTable", "Id")
.ToList();
And implementation:
public static class QueryableExtensions
{
private static readonly MethodInfo _toStringMethod = typeof(Convert).GetMethods()
.Single(m =>
m.Name == nameof(Convert.ToString) && m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType == typeof(object)
);
public static IQueryable<string> GetAllIdsFromTable(this DbContext ctx, string tableName, string idColumnName = "Id")
{
var model = ctx.Model;
var entityType = model.GetEntityTypes().FirstOrDefault(et =>
tableName.Equals(et.GetTableName(), StringComparison.InvariantCultureIgnoreCase));
if (entityType == null)
throw new InvalidOperationException($"Entity for table '{tableName}' not found.");
// GetColumnName() can be obsolete, it depends on EF Core version.
var prop = entityType.GetProperties().FirstOrDefault(p =>
idColumnName.Equals(p.GetColumnName(), StringComparison.InvariantCultureIgnoreCase));
if (prop == null)
throw new InvalidOperationException($"Property for column '{tableName}'.'{idColumnName}' not found.");
var entityParam = Expression.Parameter(entityType.ClrType, "e");
var ctxParam = Expression.Parameter(typeof(DbContext), "ctx");
// ctx.Set<entityType>()
var setQuery = Expression.Call(ctxParam, nameof(DbContext.Set), new[] { entityType.ClrType });
Expression propExpression;
if (prop.PropertyInfo == null)
// 'prop' is Shadow property, so call via EF.Property(e, "name")
propExpression = Expression.Call(typeof(EF), nameof(EF.Property), new[] { prop.ClrType },
entityParam, Expression.Constant(prop.Name));
else
propExpression = Expression.MakeMemberAccess(entityParam, prop.PropertyInfo);
propExpression = EnsureString(propExpression);
// e => e.Prop
var propLambda = Expression.Lambda(propExpression, entityParam);
// ctx.Set<entityType>().Select(e => e.Prop)
Expression selectAll = Expression.Call(typeof(Queryable), nameof(Queryable.Select),
new[] { entityType.ClrType, typeof(string) },
setQuery, Expression.Quote(propLambda));
var constructQuery = Expression.Lambda<Func<DbContext, IQueryable<string>>>(selectAll, ctxParam);
return constructQuery.Compile()(ctx);
}
private static Expression EnsureString(Expression expression)
{
if (expression.Type == typeof(string))
return expression;
if (expression.Type != typeof(object))
expression = Expression.Convert(expression, typeof(object));
expression = Expression.Call(_toStringMethod, expression);
return expression;
}
}

Multiple condition using Lambda Expression in C#

The following code works for me if I need to filter a generic query for a single parameter, like Status=1.
public static IQueryable<T> FilterBy<T>(this IQueryable<T> query)
{
var propertyName = "Status";
var param_1 = "1";
//var param_2 = 2;
//var param_3 = "DE";
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
var converter = TypeDescriptor.GetConverter(propertyExp.Type);
var result = converter.ConvertFrom(param_1);
var value = Expression.Constant(result);
var equalBinaryExp = Expression.Equal(propertyExp, value);
query = query.Where(Expression.Lambda<Func<T, bool>>(equalBinaryExp, parameterExp));
return query;
}
This is like filtering a list with a value.
var list = new List<Employee> {
new Employee { Id = 1, Name = "Phil", Status=1, Country="DE" } ,
new Employee { Id = 2, Name = "Kristina", Status=2, Country="DE" },
new Employee { Id = 3, Name = "Mia", Status=1, Country="US" }
};
list = list.Where(x => (x.Status == 1)).ToList();
But how should I modify the method FilterBy, so that it will work for filtering multiple values? Like (Status=1 or Status=2) and Country="DE",
list = list.Where(x => (x.Status == 1 || x.Status == 2) && x.Country == "DE").ToList();
Thanks for your time.
The trick when writing lambdas by hand is to write them how you would in regular C#, and decompile them, perhaps using sharplab like this. If you look on the right, you can see that it is doing something like:
Expression.AndAlso(
Expression.OrElse(
Expression.Equal(idPropExp, Expression.Constant(1)),
Expression.Equal(idPropExp, Expression.Constant(2))
),
Expression.Equal(countryPropExp, Expression.Constant("DE"))
)

Looping through members of a defined concrete type?

I'm doing comparisons across databases (with about 20 fields) and I've defined a concrete type to handle the comparison.
If my comparison fails, I want to loop through the individual items in the catch block and provide the user a list errors. Below, I've done it manually through the first few variables. Is there a more efficient way to loop this through all 20 fields? I started with a foreach (Object objectItem .. but not sure if that's the right way to go.
Any thoughts or much needed guidance?
try {
CollectionAssert.IsSubsetOf(orgs, members, "Error Matching testlist Fields");
}
catch
{
//OrgID
var sourceOrgID = orgs.Select(o => o.OrgID);
var destOrgID = members.Select(o => o.OrgID);
var errorList1 = sourceOrgID.Except(destOrgID);
string failedTests = null;
failedTests = string.Join("\n", errorList1);
Assert.IsTrue(0 == failedTests.Length, "The following Org IDs are not contained in the source: \n" + failedTests);
//DealerCode
var sourceDealerCode = orgs.Select(o => o.DealerCode);
var destDealerCode = members.Select(o => o.DealerCode);
var errorList2 = sourceDealerCode.Except(destDealerCode);
failedTests = null;
failedTests = string.Join("\n", errorList2);
Assert.IsTrue(0 == failedTests.Length, "The following Dealer Codes are not contained in the source: \n" + failedTests);
//orgkey
var sourceOrgKey = orgs.Select(o => o.OrgKey);
var destOrgKey = members.Select(o => o.OrgKey);
var errorList3 = sourceOrgKey.Except(destOrgKey);
failedTests = null;
failedTests = string.Join("\n", errorList3);
Assert.IsTrue(0 == failedTests.Length, "The following Org Keys are not contained in the source: \n" + failedTests);
You need reflection to do this:
Type type = obj.GetType();
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
GetColumn(orgList,property.Name);
}
var names = items.Select(x => x.GetType().GetProperty("prpname").GetValue(x));
public IEnumerable<object> GetColumn(List<Item> items, string columnName)
{
var values = items.Select(x =>
x.GetType().GetProperty(columnName).GetValue(x));//u can put your test code heere and you can loop it through object properties
}
you can create a list to store rslt and add results to the list else you can write output in logs files
If I haven't misunderstood your question, you could do something like this using shouldly
[TestMethod]
public void UnitTestExample()
{
var orgs = new Organisation
{
Ids = new List<int>{ 1,3 },
DealerCodes = new List<string> { "foo","bar"}
};
var members = new Organisation
{
Ids = new List<int> { 1,2,3 },
DealerCodes = new List<string> { "foo", "bar", "buzz" }
};
orgs.ShouldSatisfyAllConditions(
() => orgs.Ids.ShouldBe(members.Ids, ignoreOrder: true),
() => orgs.DealerCodes.ShouldBe(members.DealerCodes, ignoreOrder: true)
);
}
The output produced is:
You would still have to specify every property you want to check inside ShouldSatisfyAllConditions but shouldly does all the heavy lifting around comparing the lists and output the differences.
I'm assume that:
orgs is a IQueryable<T1>
members is a IQueryable<T2>
typeof(T1) == typeof(T2)
If so, that function could help:
private static void CompareRecords<TEntity>(IQueryable<TEntity> orgs, IQueryable<TEntity> members)
{
var entityType = typeof (TEntity);
var selectMethod = typeof (Queryable).GetMethods().First(x => x.Name == "Select");
var exceptMethod = typeof(Queryable).GetMethods().First(x => x.Name == "Except");
var toArrayMethod = typeof (Enumerable).GetMethods().First(x => x.Name == "ToArray");
foreach (var property in entityType.GetProperties())
{
var paramExpr = Expression.Parameter(entityType, "x");
var propExpr = Expression.Property(paramExpr, property.Name);
var delegateType = typeof (Func<,>).MakeGenericType(entityType, property.PropertyType);
var lambdaExpr = Expression.Lambda(delegateType, propExpr, paramExpr);
var parameterizedSelectMethod = selectMethod.MakeGenericMethod(entityType, property.PropertyType);
var parameterizedExceptMethod = exceptMethod.MakeGenericMethod(property.PropertyType);
var source = parameterizedSelectMethod.Invoke(null, new object[] {orgs, lambdaExpr});
var dest = parameterizedSelectMethod.Invoke(null, new object[] {members, lambdaExpr});
var errorList = parameterizedExceptMethod.Invoke(null, new[] {source, dest});
var errorListArray = toArrayMethod.MakeGenericMethod(property.PropertyType).Invoke(null, new[] {errorList});
var failedTests = string.Join("\n", ((IEnumerable)errorListArray).Cast<object>().Select(x => x.ToString()));
Assert.IsTrue(0 == failedTests.Length, $"The following {property.Name} are not contained in the source: \n{failedTests}");
}
}

Evaluating complex lambda expression

I have a lambda expression that I want to shorten by combining two function calls inside. If you see in below code I am calling this.adgroupRepository.GetBidRange twice. There has to be a way to combine these calls into one and just pass the FloorValue and CeilingValue from within.
Can someone help?
new JsonResult
{
Data = result.Data.Where(x => x.Bidding != null).Select(
x => new
{
x.ID,
x.Name,
BidRange = new
{
FloorValue = (x.Bidding.FloorPrice != null) ? x.Bidding.FloorPrice : this.adgroupRepository.GetBidRange(this.contextProvider.CurrentAccount.CurrencyCode, x.PricingModel, x.Bidding.Type).FloorValue,
CeilingValue = (x.Bidding.CeilingPrice != null) ? x.Bidding.CeilingPrice : this.adgroupRepository.GetBidRange(this.contextProvider.CurrentAccount.CurrencyCode, x.PricingModel, x.Bidding.Type).CeilingValue
},
DefaultBid = x.Bidding.BroadBid
})
};
You can always use a lambda statement instead of an expression. That allows you to write a block of code, create local variables, and then return the result. Also you can use the null-coalescing operator ?? instead of the conditional operator with a null check.
new JsonResult
{
Data = result.Data.Where(x => x.Bidding != null).Select(
x =>
{
var bidRange =
x.Bidding.FloorPrice == null
|| x.Bidding.CeilingPrice == null ?
this.adgroupRepository.GetBidRange(
this.contextProvider.CurrentAccount.CurrencyCode,
x.PricingModel,
x.Bidding.Type) :
null;
return new
{
x.ID,
x.Name,
BidRange = new
{
FloorValue = x.Bidding.FloorPrice ?? bidRange.FloorValue,
CeilingValue = x.Bidding.CeilingPrice ?? bidRange.CeilingValue
},
DefaultBid = x.Bidding.BroadBid
};
})
};
Something like this?
new JsonResult
{
Data = result.Data.Where(x => x.Bidding != null).Select(x =>
{
var bidRange = adgroupRepository.GetBidRange(
contextProvider.CurrentAccount.CurrencyCode,
x.PricingModel,
x.Bidding.Type);
return new
{
ID = x.ID,
Name = x.Name,
BidRange = new
{
FloorValue = x.Bidding.FloorPrice ?? bidRange.FloorValue,
CeilingValue = x.Bidding.CeilingPrice ?? bidRange .CeilingValue
},
DefaultBid = x.Bidding.BroadBid
}
})
};

Expression failure

I've got such expression:
Linq2Rest.Reactive.InnerRestObservable`1[A]
.Where(item => (Convert(IIF((item != null), item.ID, 0)) == Convert(61)))
.Skip(0)
.Take(20)
When I invoke Subscribe method on it I recieve such error:
variable 'item' of type 'A' referenced from scope '', but it is not defined
Can't figure out what is the problem. Actually can't see any problems with item argument...
UPD.
Where clause built with this code:
public static IQbservable WhereExpression(this IQbservable query, Expression filterExpression, ParameterExpression instance = null)
{
if (instance == null)
instance = Expression.Parameter(query.ElementType, "item"); // NOI18N
var filteredQuery = (IQbservable)GenericsHelper.InvokeGenericExtensionMethod(
typeof(Qbservable),
"Where", // NOI18N
new[] { query.ElementType },
query,
Expression.Lambda(filterExpression, instance)
);
return filteredQuery;
}
public static object InvokeGenericExtensionMethod(
Type extensionClass,
string extensionMethodName,
Type[] genericTypes,
params object[] parameters
)
{
var method = extensionClass.GetMethods().FirstOrDefault(m =>
m.Name == extensionMethodName &&
m.IsGenericMethod &&
m.GetGenericArguments().Length == genericTypes.Length &&
m.GetParameters().Length == parameters.Length
);
if (method == null)
throw new ArgumentException(string.Format("Type {0} doesn't contain method {1}", extensionClass.Name, extensionMethodName)); // NOI18N
var genericMethod = method.MakeGenericMethod(genericTypes);
return genericMethod.Invoke(null, parameters);
}
UPD 2. This is how WhereExpression calls:
foreach (var filter in filters)
{
var paramExpression = Expression.Parameter(query.ElementType, "item"); // NOI18N
query = query.WhereExpression(filter.CreateFilterExpression(paramExpression), paramExpression);
}
filters is collection of IFilterDescriptor interface from telerik.
You need to use the same ParameterExpression instance both as the parameter and in the body of the expression.
The easiest thing would be to simply use the one from the filter expression, by using it completely.

Categories

Resources