See the following code:
var source = Peoples.AsQueryable();
IQueryable<People> p;
p = source.Where( x => false);
p = p.Concat(source.Where( x => x.FirstName == "DAVID"));
p = p.Concat(source.Where(x => x.City_id == 3));
p.Dump(); //I use linqpad but it does not matter now
this code work, and linq2sql product one sql-statement (with three qouery and UNION ALL).
now the same code, ostensibly exactly the same procedure, in another variation, throw NotSupportedException:
var q = new IQueryableUnderstand<People>(Peoples.AsQueryable());
q.AddToList(x => x.City_id == 3);
q.AddToList(x => x.FirstName == "Dave");
q.Results().Dump();
IQueryableUnderstand Class:
class IQueryableUnderstand<T>
{
IQueryable<T> _source;
IQueryable<T> combin;
public IQueryableUnderstand(IQueryable<T> source)
{
_source = source;
combin = _source.Where(s => false);
}
public void AddToList(Func<T, bool> predicate) => combin = combin.Concat(_source.Where(predicate));
public IQueryable<T> Results() => combin;
}
LINQ To SQL exception: Local sequence cannot be used in LINQ to SQL implementation of query operators except the Contains operator
I feel stupid, what's the difference?
Your AddToList() method accepts a Func<T, bool> predicate. The parameter type is incorrect. If applied to your query, it will convert it to a Linq-To-Objects query. IQueryable<TSource> expects expressions, not delegates.
Change your method signature:
public void AddToList(Expression<Func<T, bool>> predicate) =>
combin = combin.Concat(_source.Where(predicate));
Related
I try to call a scalar function stored in my DB.
Here is my code:
public class PronosticDbContext : DbContext
{
public PronosticDbContext(DbContextOptions<PronosticDbContext> options) : base(options)
{
}
[DbFunction(FunctionName = "GetPlayerPointsForDay", Schema = "dbo")]
public static int GetPlayerPointsForDay(int dayId, int userId) => throw new NotSupportedException();
}
The call :
private IQueryable<PlayerRankingData> GetPlayerRanking(int? dayId)
{
return Context.Days.Where(x => x.Id == dayId.Value).Select(x => new PlayerRankingData
{
// Some properties
Users = x.Season.Users.Select(us => new PlayerRankingData.User
{
// Other properties
Last = PronosticDbContext.GetPlayerPointsForDay(x.Id, us.User.Id),
// Others again
}).OrderByDescending(u => u.Points).ThenByDescending(u => u.Last).ThenBy(u => u.Login)
});
}
I don't understand why but I always go to the NotSupportedException, my scalar function is never called. Why did I miss ?
I also tried like this, but same result...
modelBuilder
.HasDbFunction(typeof(PronosticDbContext).GetMethod(nameof(GetPlayerPointsForDay)))
.HasSchema("dbo")
.HasName(nameof(GetPlayerPointsForDay));
Ok, I got it.
The thing is you can't call the scalar function directly (I don't know why), it must be encapsulate in a linq query, like this :
Last = Context.Users.Select(u => PronosticDbContext.GetPlayerPointsForDay(x.Id, us.User.Id)).FirstOrDefault()
And that's all. No need to declaration in the DbContext, this is enough :
[DbFunction]
public static int? GetPlayerPointsForDay(int dayId, int userId) => throw new NotSupportedException();
I have a data model with a dozen properties, say value1, value2, value3 (actually they have meaningful names but that's not important here).
In my display, I need to do the following for each value:
#if (Model.Value1 >= 2)
{
<div class="col-sm-6 test-box-item">
<h5>#Html.DisplayNameFor(_ => Model.Value1)</h5>
<div>#Model.Value1</div>
</div>
}
This for a dozen of them.
I wanted to create a #function or #helper and simplify the code to something like:
DisplayValue(_ => Model.Value1)
DisplayValue(_ => Model.Value2)
...
but couldn't figure out how to do that.
Any idea? I guess I need a function that accepts an Expression but I really don't know how to code that.
check this helper
public static class HmtlExtensions
{
public static HtmlString DisplayValue<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression)
{
if (expression == null)
throw (new ArgumentNullException("expression"));
var result = new StringBuilder();
ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData);
// get value
var _value = metadata.Model.ToString();
int value = 0;
if (int.TryParse(_value, out value) && value >= 2)
{
result.Append("<div class=\"col-sm-6 test-box-item\"><h5>");
result.Append(helper.DisplayNameFor(expression));
result.Append("</h5><div>");
result.Append(_value);
result.Append("</div></div>");
}
return MvcHtmlString.Create(result.ToString());
}
}
and in your view use it like
#Html.DisplayValue(x => x.Value1)
#Html.DisplayValue(x => x.Value2)
#Html.DisplayValue(x => x.Value3)
Is there a way in EntityFramework (and the resulting LINQ) to query against a property of an entity that is not hard-coded?
Let's say, something that can be used for a search function.
public IList<Entity> Search (string propertyName, object value) {
// something that'll do the following
return context.Set<Entity>()
.Where(x => x.propertyName == value)
.ToList()
;
}
How about Property Descriptor?
The following code seems to do what you require:
string propertyName = "Length";
List<string> testList = new List<string>();
testList.Add("String1");
testList.Add("String10");
testList.Add("String100");
testList.Add("String1000");
System.ComponentModel.PropertyDescriptorCollection props = System.ComponentModel.TypeDescriptor.GetProperties(typeof(string));
System.ComponentModel.PropertyDescriptor desc = props.Find(propertyName, false);
IEnumerable<object> obj = from env in testList
select desc.GetValue(env);
foreach (object it in obj)
{
Console.WriteLine(it.ToString());
}
You can build equals expression manually like this
private static Expression<Func<TEntity, bool>> BuildEqualExpression<TEntity>(string propertyName, object value)
{
var param = Expression.Parameter(typeof(TEntity), "x");
var body = Expression.MakeBinary(ExpressionType.Equal,
Expression.Property(param, propertyName), Expression.Constant(value));
return Expression.Lambda<Func<TEntity, bool>>(body, new ParameterExpression[] { param });
}
and then use it in your LINQ query
var expression = BuildEqualExpression<TEntity>(propertyName, value);
return context.Set<TEntity>().Where(expression).ToList();
I'm attempting to perform dynamic sorting of data that I'm putting into grids into our MVC UI. Since MVC is abstracted from everything else via WCF, I've created a couple utility classes and extensions to help with this. The two most important things (slightly simplified) are as follows:
public static IQueryable<TModel> ApplySortOptions<TModel, TProperty>(this IQueryable<TModel> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
{
var sortedSortOptions = (from o in sortOptions
orderby o.Priority ascending
select o).ToList();
var results = collection;
foreach (var option in sortedSortOptions)
{
var currentOption = option;
var propertyName = currentOption.Property.MemberWithoutInstance();
var isAscending = currentOption.IsAscending;
if (isAscending)
{
results = from r in results
orderby propertyName ascending
select r;
}
else
{
results = from r in results
orderby propertyName descending
select r;
}
}
return results;
}
public interface ISortOption<TModel, TProperty> where TModel : class
{
Expression<Func<TModel, TProperty>> Property { get; set; }
bool IsAscending { get; set; }
int Priority { get; set; }
}
I've not given you the implementation for MemberWithoutInstance() but just trust me in that it returns the name of the property as a string. :-)
Following is an example of how I would consume this (using a non-interesting, basic implementation of ISortOption<TModel, TProperty>):
var query = from b in CurrentContext.Businesses
select b;
var sortOptions = new List<ISortOption<Business, object>>
{
new SortOption<Business, object>
{
Property = (x => x.Name),
IsAscending = true,
Priority = 0
}
};
var results = query.ApplySortOptions(sortOptions);
As I discovered with this question, the problem is specific to my orderby propertyName ascending and orderby propertyName descending lines (everything else works great as far as I can tell). How can I do this in a dynamic/generic way that works properly?
You should really look at using Dynamic LINQ for this. In fact, you may opt to simply list the properties by name instead of using an expression, making it somewhat easier to construct.
public static IQueryable<T> ApplySortOptions<T, TModel, TProperty>(this IQueryable<T> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
{
var results = collection;
foreach (var option in sortOptions.OrderBy( o => o.Priority ))
{
var currentOption = option;
var propertyName = currentOption.Property.MemberWithoutInstance();
var isAscending = currentOption.IsAscending;
results = results.OrderBy( string.Format( "{0}{1}", propertyName, !isAscending ? " desc" : null ) );
}
return results;
}
While I think #tvanfosson's solution will function perfectly, I'm also looking into this possibility:
/// <summary>
/// This extension method is used to help us apply ISortOptions to an IQueryable.
/// </summary>
/// <param name="collection">This is the IQueryable you wish to apply the ISortOptions to.</param>
/// <param name="sortOptions">These are the ISortOptions you wish to have applied. You must specify at least one ISortOption (otherwise, don't call this method).</param>
/// <returns>This returns an IQueryable object.</returns>
/// <remarks>This extension method should honor deferred execution on the IQueryable that is passed in.</remarks>
public static IOrderedQueryable<TModel> ApplySortOptions<TModel, TProperty>(this IQueryable<TModel> collection, IEnumerable<ISortOption<TModel, TProperty>> sortOptions) where TModel : class
{
Debug.Assert(sortOptions != null, "ApplySortOptions cannot accept a null sortOptions input.");
Debug.Assert(sortOptions.Count() > 0, "At least one sort order must be specified to ApplySortOptions' sortOptions input.");
var firstSortOption = sortOptions.OrderBy(o => o.Priority).First();
var propertyName = firstSortOption.Property.MemberWithoutInstance();
var isAscending = firstSortOption.IsAscending;
// Perform the first sort action
var results = isAscending ? collection.OrderBy(propertyName) : collection.OrderByDescending(propertyName);
// Loop through all of the rest ISortOptions
foreach (var sortOption in sortOptions.OrderBy(o => o.Priority).Skip(1))
{
// Make a copy of this or our deferred execution will bite us later.
var currentOption = sortOption;
propertyName = currentOption.Property.MemberWithoutInstance();
isAscending = currentOption.IsAscending;
// Perform the additional orderings.
results = isAscending ? results.ThenBy(propertyName) : results.ThenByDescending(propertyName);
}
return results;
}
using the code from this question's answer:
public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
{
return ApplyOrder(source, property, "OrderBy");
}
public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
{
return ApplyOrder(source, property, "OrderByDescending");
}
public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder(source, property, "ThenBy");
}
public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
{
return ApplyOrder(source, property, "ThenByDescending");
}
private static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
{
var props = property.Split('.');
var type = typeof (T);
var arg = Expression.Parameter(type, "x");
Expression expr = arg;
foreach (var prop in props)
{
// use reflection (not ComponentModel) to mirror LINQ
var pi = type.GetProperty(prop);
expr = Expression.Property(expr, pi);
type = pi.PropertyType;
}
var delegateType = typeof (Func<,>).MakeGenericType(typeof (T), type);
var lambda = Expression.Lambda(delegateType, expr, arg);
var result = typeof (Queryable).GetMethods().Single(
method => method.Name == methodName
&& method.IsGenericMethodDefinition
&& method.GetGenericArguments().Length == 2
&& method.GetParameters().Length == 2)
.MakeGenericMethod(typeof (T), type)
.Invoke(null, new object[] {source, lambda});
return (IOrderedQueryable<T>) result;
}
so, i have a method: I'm counting a number in a sequence with holes, this number should be the first hole or max()
public static int GetRegisterNumber<T>(this IQueryable<T> enumerable, Func<T, bool> whereFunc, Func<T, int?> selectFunc)
{
var regNums = enumerable.OrderBy(selectFunc).Where(whereFunc).ToArray();
if (regNums.Count() == 0)
{
return 1;
}
for (int i = 0; i < regNums.Count(); i++)
{
if (i + 1 != regNums[i])
{
return regNums[i].Value + 1;
}
}
return regNums.Last().Value + 1;
}
i use it like:
var db = new SomeDataContext();
db.Goals.GetRegisterNumber(g => g.idBudget == 123, g => g.RegisterNumber);
So, i expect from linq some query like:
SELECT [t0].[id], [t0].[tstamp], [t0].[idBudget], [t0].[RegisterNumber], FROM [ref].[GoalHierarchy] AS [t0] WHERE [t0].[idBudget] = 123 ORDER BY [t0].[RegisterNumber]
instead i'm getting:
SELECT [t0].[id], [t0].[tstamp], [t0].[idBudget], [t0].[RegisterNumber], FROM [ref].[GoalHierarchy] AS [t0]
So linq gets ALL the table data and then filters it, but that behavior is not acceptable, because the table contains much data. Is there any solution?
Change your method signature to look like this:
public static int GetRegisterNumber<T>(this IQueryable<T> enumerable, Expression<Func<T, bool>> whereFunc, Expression<Func<T, int?>> selectFunc)
This way it will call the appropriate extension methods for IQueryable<T>. If you only have a Func<TSource, bool>, it will call the extension method for IEnumerable<T>.