NHibernate Linq ToFuture and Max - linq

I want the following two queries to be executed in single round trip to database so I'm using ToFutureValue.
var query = AsQueryable()
.Where(x => x.User == user);
var singinCount = query
.Where(x => x.ActionDate >= from && x.ActionDate <= to)
.ToFutureValue(q => q.Count());
var lastSignin = query
.Where(x => x.ActionName.ToLower() == Actions.Signin))
.ToFutureValue(q => q.Max(x => x.ActionDate));
The problem is that Count and Max returns result but not IQueryable. I have found the following extension that enables me to pass Count to ToFutureValue:
public static IFutureValue<TResult> ToFutureValue<TSource, TResult>(
this IQueryable<TSource> source, Expression<Func<IQueryable<TSource>, TResult>> selector)
where TResult : struct
{
var provider = (INhQueryProvider)source.Provider;
var method = ((MethodCallExpression)selector.Body).Method;
var expression = Expression.Call((Expression)null, method, source.Expression);
return (IFutureValue<TResult>)provider.ExecuteFuture(expression);
}
Now I need to adopt that extension for Max because I have to pass arguments to Max (.ToFutureValue(q => q.Max(x => x.ActionDate))). How can I do that?

as a workaround
var lastSignin = query
.Where(x => x.ActionName.ToLower() == Actions.Signin))
.OrderByDescending(x => x.ActionDate)
.Take(1)
.ToFutureValue();

It looks like there is appropriate patch in GitHub pull request
#120
And here is jira bug NH-3184
the following is code from pull #120 that works:
public static IFutureValue<TResult> ToFutureValue<T, TResult>(
this IQueryable<T> query, Expression<Func<IQueryable<T>, TResult>> selector)
{
var nhQueryable = query as QueryableBase<T>;
if (nhQueryable == null)
{
throw new NotSupportedException("Query needs to be of type QueryableBase<T>");
}
var provider = (INhQueryProvider) query.Provider;
var expression = ReplacingExpressionTreeVisitor.Replace(
selector.Parameters.Single(),
query.Expression,
selector.Body);
return (IFutureValue<TResult>) provider.ExecuteFuture(expression);
}

Related

How to Search through all fields in a LINQ table?

in LINQ how do i search all fields in a table, what do i put for ANYFIELD in the below?
Thanks
var tblequipments = from d in db.tblEquipments.Include(t => t.User).Include(t => t.ChangeLog).Include(t => t.AssetType)
where d."ANYFIELD" == "VALUE" select d;
You can't. You must compare each field individually. It doesn't make sense to compare all fields, given a field may not even be of the same type as the object you're comparing to.
You can, using reflection. Try this:
static bool CheckAllFields<TInput, TValue>(TInput input, TValue value, bool alsoCheckProperties)
{
Type t = typeof(TInput);
foreach (FieldInfo info in t.GetFields().Where(x => x.FieldType == typeof(TValue)))
{
if (!info.GetValue(input).Equals(value))
{
return false;
}
}
if (alsoCheckProperties)
{
foreach (PropertyInfo info in t.GetProperties().Where(x => x.PropertyType == typeof(TValue)))
{
if (!info.GetValue(input, null).Equals(value))
{
return false;
}
}
}
return true;
}
And your LINQ query:
var tblequipments = from d in db.tblEquipments.Include(t => t.User).Include(t => t.ChangeLog).Include(t => t.AssetType)
where CheckAllFields(d, "VALUE", true) select d;
The third parameter should be true if you want to check all fields and all properties, and false if you want to check only all fields.
EDIT: Someone already built this...see here.
Not a full answer, but I don't agree with assertion that you simply can't...
You could come up with an extension method that dynamically filtered the IQueryable/IEnumerable (I'm guessing IQueryable by the db variable) based on properties of a similar type for you. Here's something whipped up in Linqpad. It references PredicateBuilder and is by no means complete/fully accurate, but I tested it out in Linq-to-SQL on some of my tables and it worked as described.
void Main()
{
YourDbSet.WhereAllPropertiesOfSimilarTypeAreEqual("A String")
.Count()
.Dump();
}
public static class EntityHelperMethods
{
public static IQueryable<TEntity> WhereAllPropertiesOfSimilarTypeAreEqual<TEntity, TProperty>(this IQueryable<TEntity> query, TProperty value)
{
var param = Expression.Parameter(typeof(TEntity));
var predicate = PredicateBuilder.True<TEntity>();
foreach (var fieldName in GetEntityFieldsToCompareTo<TEntity, TProperty>())
{
var predicateToAdd = Expression.Lambda<Func<TEntity, bool>>(
Expression.Equal(
Expression.PropertyOrField(param, fieldName),
Expression.Constant(value)), param);
predicate = predicate.And(predicateToAdd);
}
return query.Where(predicate);
}
// TODO: You'll need to find out what fields are actually ones you would want to compare on.
// This might involve stripping out properties marked with [NotMapped] attributes, for
// for example.
private static IEnumerable<string> GetEntityFieldsToCompareTo<TEntity, TProperty>()
{
Type entityType = typeof(TEntity);
Type propertyType = typeof(TProperty);
var fields = entityType.GetFields()
.Where (f => f.FieldType == propertyType)
.Select (f => f.Name);
var properties = entityType.GetProperties()
.Where (p => p.PropertyType == propertyType)
.Select (p => p.Name);
return fields.Concat(properties);
}
}
Useful resources for the unresolved part:
Finding the relevant properties
if this help some one.
first find all properties within Customer class with same type as query:
var stringProperties = typeof(Customer).GetProperties().Where(prop =>
prop.PropertyType == query.GetType());
then find all customers from context that has at least one property with value equal to query:
context.Customer.Where(customer =>
stringProperties.Any(prop =>
prop.GetValue(customer, null) == query));

Select all with EF and lambda

I wrote this awesome code that I am having trouble using now.
Currently working example:
complete item = ReliableExecution.RetryWithExpression<complete, complete>(u => u.FirstOrDefault(x => x.str_en == segment));
and this is a part of the RetryWithExpression code:
public static TValue RetryWithExpression<T, TValue>(Func<ObjectSet<T>, TValue> func, Int32 retryInfiniteLoopGuard = 0)
where T : class
{
RetryPolicy policy = RetryPolicyProvider.GetSqlAzureRetryPolicy();
using (DDEntities dataModel = new DDEntities())
{
var entitySet = dataModel.CreateObjectSet<T>();
...
var query = policy.ExecuteAction(() => (func(entitySet)));
...
Now my question is how to change the above select query to do SELECT * ?
I did try this but it tells me things about errors I don't understand:
complete item = ReliableExecution.RetryWithExpression<complete, complete>(u => u.Select(x => x.str_en != ""));
IQueryable<complete> items = ReliableExecution.RetryWithExpression<complete, IQueryable<complete>>(u => u.Where(x => x.str_en != "")); ?

How to "let" in lambda expression?

How can I rewrite this linq query to Entity on with lambda expression?
I want to use let keyword or an equivalent in my lambda expression.
var results = from store in Stores
let AveragePrice = store.Sales.Average(s => s.Price)
where AveragePrice < 500 && AveragePrice > 250
For some similar questions like what is commented under my question, it's suggested to
.Select(store=> new { AveragePrice = store.Sales.Average(s => s.Price), store})
which will calculate AveragePrice for each item, while in Query style I mentioned, let expression prevents to calculate average many times.
So, you can use the extension method syntax, which would involve one lambda expression more than you are currently using. There is no let, you just use a multi-line lambda and declare a variable:
var results = Stores.Where(store =>
{
var averagePrice = store.Sales.Average(s => s.Price);
return averagePrice > 250 && averagePrice < 500;
});
Note that I changed the average price comparison, because yours would never return any results (more than 500 AND less that 250).
The alternative is
var results = Stores.Select(store => new { Store = store, AveragePrice = store.Sales.Average(s => s.Price})
.Where(x => x.AveragePrice > 250 && x.AveragePrice < 500)
.Select(x => x.Store);
Basically, you need to use Select and an anonymous type to add the average to your object, followed by the rest of your statement.
Not tested but it should look like this:
Stores.Select(
x => new { averagePrice = x.Sales.Average(s => s.Price), store = x})
.Where(y => y.averagePrice > 500 && y.averagePrice < 250)
.Select(x => x.store);
Warning: This works well for Linq-to-Entities, but be careful with these constructs in Linq-to-Objects. Using let creates a new anonymous type per object in your collection, it consumes a lot of memory with large collections.
Look here for details:
Let in chained extension methods
Another option is to define this extension method:
public static class Functional
{
public static TResult Pipe<T, TResult>(this T value, Func<T, TResult> func)
{
return func(value);
}
}
Then write your query like this:
var results = Stores
.Where(store => store.Sales.Average(s => s.Price)
.Pipe(averagePrice => averagePrice < 500 && averagePrice > 250));
We can avoid the overhead of the lambda used in all the other answers with an inline out declaration:
public static class FunctionalExtensions
{
public static T Assign<T>(this T o, out T result) =>
result = o;
}
And call it like this
var results = Stores
.Where(store => store.Sales
.Average(s => s.Price)
.Assign(out var averagePrice) < 500 && averagePrice > 250);

LINQ: Group By + Where in clause

I'm trying to implement a T-SQL equivalent of a where in (select ...) code in LINQ.
This is what I have now:
int contactID = GetContactID();
IEnumerable<string> threadList = (from s in pdc.Messages
where s.ContactID == contactID
group 1 by new { s.ThreadID } into d
select new { ThreadID = d.Key.ThreadID}).ToList<string>();
var result = from s in pdc.Messages
where threadList.Contains(s.ThreadID)
group new { s } by new { s.ThreadID } into d
let maxMsgID = d.Where(x => x.s.ContactID != contactID).Max(x => x.s.MessageID)
select new {
LastMessage = d.Where(x => x.s.MessageID == maxMsgID).SingleOrDefault().s
};
However, my code won't compile due to this error for the ToList():
cannot convert from
'System.Linq.IQueryable<AnonymousType#1>'
to
'System.Collections.Generic.IEnumerable<string>'
Anyone have any suggestions on how to implement this? Or any suggestions on how to simplify this code?
Your query returns a set of anonymous types; you cannot implicitly convert it to a List<string>.
Instead, you should select the string itself. You don't need any anonymous types.
Change it to
var threadList = pdc.Messages.Where(s => s.ContactID == contactID)
.Select(s => s.ThreadID)
.Distinct()
.ToList();
var result = from s in pdc.Messages
where threadList.Contains(s.ThreadID)
group s by s.ThreadID into d
let maxMsgID = d.Where(x => x.ContactID != contactID).Max(x => x.MessageID)
select new {
LastMessage = d.Where(x => x.MessageID == maxMsgID).SingleOrDefault()
};

how to query the settingspropertyvaluecollection

I have a settingspropertyvaluecollection.I dont want to loop through all the properties using a for each loop.Instead i want to query the collection.How do i do that?is there a way to use LINQ and do it?
Thanks
SettingsPropertyValueCollection doesn't implement IEnumerable<T> but it does implement IEnumerable. If you want to query it using LINQ you have a couple of options.
You could create a Where() extension method that takes IEnumerable and a query and performs the query for you:
public static class IEnumerableExtensions
{
public static IEnumerable<T> Where<T>(this IEnumerable input, Func<T,bool> query)
{
return input.Cast<T>().Where(item => query(item));
}
}
assuming:
var settings = new SettingsPropertyValueCollection
{
new SettingsPropertyValue(new SettingsProperty("Email")
{
DefaultValue = "a#a.com",
PropertyType = typeof(string)
}),
new SettingsPropertyValue(new SettingsProperty("City")
{
DefaultValue = "Austin",
PropertyType = typeof(string)
}),
new SettingsPropertyValue(new SettingsProperty("State")
{
DefaultValue = "TX",
PropertyType = typeof(string)
})
};
usage would be:
var matches = settings.Where<SettingsPropertyValue>(x => x.Name == "City")
alternatively you could use the LINQ Cast<T> operator to query the settings:
var matches = settings.Cast<SettingsPropertyValue>()
.Where(x => x.Name == "City");
if you expect only one possible match then use FirstOrDefault() instead of Where()
var match = settings.Cast<SettingsPropertyValue>()
.FirstOrDefault(x => x.Name == "City");
It's been a while since the question was answered and many things have changed since then.
You could cast the SettingsPropertyValueCollection to a list (or other container) and query it right away.
So, this would be my solution nowadays:
SettingsPropertyValueCollection settings = Properties.Settings.Default.PropertyValues
settings.Cast<SettingsPropertyValue>().ToList().Where(p => p.Name == "myProperty");

Resources