Use method inside LINQ GroupBy - group-by

I'm trying to manipulate properties in a GroupBy clause to be used in a dictionary:
var lifeStages = await _dbContext.Customers
.GroupBy(x => GetLifeStage(x.DoB))
.Select(x => new { LifeStage = x.Key, Count = x.Count() })
.ToDictionaryAsync(x => x.LifeStage, x => x.Count);
I'm expecting results like
adolescent: 10,
adult: 15,
senior: 12 etc
But getting error:
Either rewrite the query in a form that can be translated,
or switch to client evaluation explicitly
by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
Offcourse I can't combine ToDictionary() with any of the mentioned calls, and splitting up the query did not resolve the issues or taught my anything)
I've tried with making GetLifeStage() static and async, no difference there as well. The method gets called, performs what it needs to do, and still GroupBy can't be translated
If I leave out the Select() part and work with the Key of the GroupBy, same error:
"...could not be translated."
I saw an error too that said I couldn't combine a GroupBy() with a ToDictionary() during try-outs, but doesn't seem to pop up atm.
As I'm running out of ideas, all suggestions are welcome!
update:
private LifeStage GetLifeStage(DateTimeOffset doB)
{
var ageInMonths = Math.Abs(12 * (doB.Year - DateTimeOffset.UtcNow.Year) + doB.Month - DateTimeOffset.UtcNow.Month);
switch (ageInMonths)
{
case < 216:
return LifeStage.Adolescent;
case < 780:
return LifeStage.Adult;
case >= 780:
return LifeStage.Senior;
}
}

The problem is the usage of the custom GetLifeStage method inside the GroupBy expression. Custom methods cannot be translated to SQL because the query translator code has no way to know what is inside that method. And it cannot be called because there are no objects at all during the translation process.
In order to make it translatable, you have to replace the custom method call with its body, converted to translatable expression - basically something which can be used as expression bodied method. You can't use variables and switch, but you can use conditional operators. Instead of variable, you could use intermediate projection (Select).
Here is the equivalent translatable query:
var lifeStages = await _dbContext.Customers
.Select(c => new { Customer = c, AgeInMonths = Math.Abs(12 * (c.DoB.Year - DateTimeOffset.UtcNow.Year) + c.DoB.Month - DateTimeOffset.UtcNow.Month) })
.GroupBy(x => x.AgeInMonths < 216 ? LifeStage.Adolescent : x.AgeInMonths < 780 ? LifeStage.Adult : LifeStage.Senior)
// the rest is the same as original
.Select(x => new { LifeStage = x.Key, Count = x.Count() })
.ToDictionaryAsync(x => x.LifeStage, x => x.Count);

Related

Howto loop OrderedAssertions in FakeItEasy 2

As I understand, ordered assertions in FakeItEasy 2 are done like this (from the docs):
// Assert
A.CallTo(() => unitOfWorkFactory.BeginWork()).MustHaveHappened()
.Then(A.CallTo(() => usefulCollaborator.JustDoIt()).MustHaveHappened())
.Then(A.CallTo(() => unitOfWork.Dispose()).MustHaveHappened());
Now, suppose I have a collection and for each item in this collection I want to assert that a call was made to a faked object. What is the best approach to assert the calls were made in the correct order?
I came up with this, but don't really like it:
IOrderableCallAssertion ioca = null;
foreach (var item in items.OrderBy(i => i.Id)
{
var itemUnderTest = item;
if (ioca == null)
{
ioca = A.CallTo(() => fakeObject.Handle(itemUnderTest, otherArgument)).MustHaveHappened(Repeated.Exactly.Once);
}
else
{
ioca = ioca.Then(A.CallTo(() => fakeObject.Handle(itemUnderTest, otherArgument)).MustHaveHappened(Repeated.Exactly.Once));
}
}
That looks about right to me. Of course, you could inline itemUnderTest and pull MustHaveHappened outside of the two if branches.
And you could always hide this in a convenience method.
An alternative: use Invokes to capture the fakes as the calls come in and later compare them against a list.

Dynamic linq query in EF

I am using dynamic LINQ query in EF4.
Below code throws error: 'New' cannot be resolved into a valid type or function.
var x = ent.OM_COMPANY
.Where(qry)
.OrderBy("it.CM_CODE")
.Select("New(it.CM_CODE, it.CM_NAME)");
What am I doing wrong?
The below code executes without any error.
var x = from cmp in ent.OM_COMPANY
where (qry)
orderby cmp.CM_CODE
select new { cmp.CM_CODE, cmp.CM_NAME };
I don't even know how you got that first code block to compile. Both OrderBy and Select take lambdas not strings. It should be written as:
var x = ent.OM_COMPANY
.Where(qry)
.OrderBy(c => c.CM_CODE)
.Select(c => c.CM_CODE, c.CM_NAME);

RX PropertyChanged GroupBy deadlock

I am trying to use Reactive Extensions to throttle PropertyChanged notifications. There are examples of doing this using GroupBy, but with one Subscription created for each PropertyName.
I want to handle the PropertyChanged event for all properties, and I need to Throttle those events for each PropertyName.
This is what I have so far, but it causes a deadlock.
ValuesPropertyChanged = Observable.FromEventPattern<PropertyChangedEventArgs>(value, "PropertyChanged")
.GroupBy(o => o.EventArgs.PropertyName)
.First()
.Throttle(TimeSpan.FromSeconds(2))
.Subscribe(args => HandlePropertyChanged(args.EventArgs.PropertyName));
The deadlock happens in the call to .First().
It still locks if I change that line to:
.Select(o => o.First())
I have also tried
.Select(o => o.FirstAsync())
The examples for GroupBy here look pretty concise, but I am incapable of wrapping my head around converting these examples to my solution.
Why does this cause a deadlock, and what should I do to make this work?
I think this might be what you're after:
// assume MyObj : INotifyPropertyChanged, naturally
var value = new MyObj();
Action<string> HandlePropertyChanged =
name => Console.WriteLine("Got a change for name:" + name);
// The query
var valuesPropertyChanged =
// create from event stream
from propChange in Observable.FromEventPattern<PropertyChangedEventArgs>(
value,
"PropertyChanged")
// group events by property name
group propChange by propChange.EventArgs.PropertyName into batchByName
// Throttle the resulting batch
from throttledByName in batchByName.Throttle(TimeSpan.FromSeconds(1))
// then select each item of the "throttled output"
select throttledByName;
valuesPropertyChanged.Subscribe(args =>
HandlePropertyChanged(args.EventArgs.PropertyName));
for(int i=0;i<10;i++)
{
value.Value1 = i.ToString();
value.Value2 = (i-1).ToString();
}
Output:
Got a change for name:Value2
Got a change for name:Value1
Here is the same but with extension methods:
var valuesPropertyChanged =
Observable.FromEventPattern<PropertyChangedEventArgs>(
_vm,
"PropertyChanged")
.GroupBy(propchange => propchange.EventArgs.PropertyName)
.Select(o => o.Throttle(TimeSpan.FromSeconds(1)))
.Merge();

can i use custom lambda method in entity framework?

i have some methods like:
public static string ToOtherFormat (this string inp)
{
// some code to change inp
return inp;
}
and in my select i want to have code like this:
var DetailMembers = db.TB_Members
.Where(x=> x.FName == obj.ToOtherFormat())
.Select( x=> new { name = (x.FName.ToOtherFormat() + " " + x.LName) , x.ActCode });
i try and just have error. is it possible?
thanks!
i receive this error in simple convert to integer
LINQ to Entities does not recognize the method 'Int32 ToInt32(System.String)' method, and this method cannot be translated into a store expression.
with this code
.Where(x => x.MemberID == Convert.ToInt32(Hmemid.Hash_two_Decrypt())
Looks like you are querying against the database. Your current query will get translated into SQL query and since SQL doesn't recognize your function that is why you get error.
You may get the data from the tables using a query without that function and then later do the formatting on the result set.
i found it on use .AsEnumerable() method like:
var DetailMembers = db.TB_Members.AsEnumerable()
.Where(x=> x.FName == obj.ToOtherFormat())
.Select( x=> new { name = (x.FName.ToOtherFormat() + " " + x.LName) , x.ActCode });

MongoDB - combining multiple Numeric Range queries (C# driver)

*Mongo Newbie here
I have a document containing several hundred numeric fields which I need to query in combination.
var collection = _myDB.GetCollection<MyDocument>("collection");
IMongoQuery mongoQuery; // = Query.GT("field", value1).LT(value2);
foreach (MyObject queryObj in Queries)
{
// I have several hundred fields such as Height, that are in queryObj
// how do I build a "boolean" query in C#
mongoQuery = Query.GTE("Height", Convert.ToInt16(queryObj.Height * lowerbound));
}
I have several hundred fields such as Height (e.g. Width, Area, Perimeter etc.), that are in queryObj how do I build a "boolean" query in C# that combines range queries for each field in conjunction.
I have tried to use the example Query.GT("field", value1).LT(value2);, however the compiler does not accept the LT(Value) construct. In any event I need to be able to build a complex boolean query by looping through each of the numeric field values.
Thanks for helping a newbie out.
EDIT 3:
Ok, it looks like you already have code in place to build the complicated query. In that case, you just needed to fix the compiler issue. Am assuming you want to do the following (x > 20 && x < 40) && (y > 30 && y < 50) ...
var collection = _myDB.GetCollection<MyDocument>("collection");
var queries = new List<IMongoQuery>();
foreach (MyObject queryObj in Queries)
{
//I have several hundred fields such as Height, that are in queryObj
//how do I build a "boolean" query in C#
var lowerBoundQuery = Query.GTE("Height", Convert.ToInt16(queryObj.Height * lowerbound));
var upperBoundQuery = Query.LTE("Height", Convert.ToInt16(queryObj.Height * upperbound));
var query = Query.And(lowerBoundQuery, upperBoundQuery);
queries.Add(query);
}
var finalQuery = Query.And(queries);
/*
if you want to instead do an OR,
var finalQuery = Query.Or(queries);
*/
Original Answer.
var list = _myDb.GetCollection<MyDoc>("CollectionName")
.AsQueryable<MyDoc>()
.Where(x =>
x.Height > 20 &&
x.Height < 40)
.ToList();
I have tried to use the example Query.GT("field", value1).LT(value2);,
however the compiler does not accept the LT(Value) construct.
You can query MongoDB using linq, if you are using the official C# driver. That ought to solve the compiler issue I think.
The more interesting question I have in mind is, how are you going to construct that complicated boolean query?
One option is to dynamically build an Expression and then pass that to the Where
My colleague is using the following code for something similar...
public static IQueryable<T> Where<T>(this IQueryable<T> query,
string column, object value, WhereOperation operation)
{
if (string.IsNullOrEmpty(column))
return query;
ParameterExpression parameter = Expression.Parameter(query.ElementType, "p");
MemberExpression memberAccess = null;
foreach (var property in column.Split('.'))
memberAccess = MemberExpression.Property
(memberAccess ?? (parameter as Expression), property);
//change param value type
//necessary to getting bool from string
ConstantExpression filter = Expression.Constant
(
Convert.ChangeType(value, memberAccess.Type)
);
//switch operation
Expression condition = null;
LambdaExpression lambda = null;
switch (operation)
{
//equal ==
case WhereOperation.Equal:
condition = Expression.Equal(memberAccess, filter);
lambda = Expression.Lambda(condition, parameter);
break;
//not equal !=
case WhereOperation.NotEqual:
condition = Expression.NotEqual(memberAccess, filter);
lambda = Expression.Lambda(condition, parameter);
break;
//string.Contains()
case WhereOperation.Contains:
condition = Expression.Call(memberAccess,
typeof(string).GetMethod("Contains"),
Expression.Constant(value));
lambda = Expression.Lambda(condition, parameter);
break;
}
MethodCallExpression result = Expression.Call(
typeof(Queryable), "Where",
new[] { query.ElementType },
query.Expression,
lambda);
return query.Provider.CreateQuery<T>(result);
}
public enum WhereOperation
{
Equal,
NotEqual,
Contains
}
Currently it only supports == && !=, but it shouldn't be that difficult to implement >= or <= ...
You could get some hints from the Expression class: http://msdn.microsoft.com/en-us/library/system.linq.expressions.expression.aspx
EDIT:
var props = ["Height", "Weight", "Age"];
var query = _myDb.GetCollection<MyDoc>("CName").AsQueryable<MyDoc>();
foreach (var prop in props)
{
query = query.Where(prop, GetLowerLimit(queryObj, prop), WhereOperation.Between, GetUpperLimit(queryObj, prop));
}
// the above query when iterated over, will result in a where clause that joins each individual `prop\condition` with an `AND`.
// The code above will not compile. The `Where` function I wrote doesnt accept 4 parameters. You will need to implement the logic for that yourself. Though it ought to be straight forward I think...
EDIT 2:
If you don't want to use linq, you can still use Mongo Query. You will just need to craft your queries using the Query.And() and Query.Or().
// I think this might be deprecated. Please refer the release notes for the C# driver version 1.5.0
Query.And(Query.GTE("Salary", new BsonDouble(20)), Query.LTE("Salary", new BsonDouble(40)), Query.GTE("Height", new BsonDouble(20)), Query.LTE("Height", new BsonDouble(40)))
// strongly typed version
new QueryBuilder<Employee>().And(Query<Employee>.GTE(x => x.Salary, 40), Query<Employee>.LTE(x => x.Salary, 60), Query<Employee>.GTE(x => x.HourlyRateToClients, 40), Query<Employee>.LTE(x => x.HourlyRateToClients, 60))