In System.Linq.Dynamic, there are a few methods to form Select, Where and other Linq statements dynamically. But there is no for SelectMany.
The method for Select is as the following:
public static IQueryable Select(this IQueryable source, string selector, params object[] values)
{
if (source == null) throw new ArgumentNullException("source");
if (selector == null) throw new ArgumentNullException("selector");
LambdaExpression lambda = DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
IQueryable result = source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "Select",
new Type[] { source.ElementType, lambda.Body.Type },
source.Expression, Expression.Quote(lambda)));
return result;
}
I tried to modify the above code, after hours working, I couldn't find a way out.
Any suggestions are welcome.
Ying
Already implemented this one for our project, let me know if it works for you!
public static IQueryable SelectMany(this IQueryable source, string selector, params object[] values)
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
// Parse the lambda
LambdaExpression lambda =
DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
// Fix lambda by recreating to be of correct Func<> type in case
// the expression parsed to something other than IEnumerable<T>.
// For instance, a expression evaluating to List<T> would result
// in a lambda of type Func<T, List<T>> when we need one of type
// an Func<T, IEnumerable<T> in order to call SelectMany().
Type inputType = source.Expression.Type.GetGenericArguments()[0];
Type resultType = lambda.Body.Type.GetGenericArguments()[0];
Type enumerableType = typeof(IEnumerable<>).MakeGenericType(resultType);
Type delegateType = typeof(Func<,>).MakeGenericType(inputType, enumerableType);
lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters);
// Create the new query
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "SelectMany",
new Type[] { source.ElementType, resultType },
source.Expression, Expression.Quote(lambda)));
}
I have added another SelectMany that retuns an AnonymousType.
public static IQueryable SelectMany(this IQueryable source, string selector, string resultsSelector, params object[] values)
{
if (source == null)
throw new ArgumentNullException("source");
if (selector == null)
throw new ArgumentNullException("selector");
// Parse the lambda
LambdaExpression lambda =
DynamicExpression.ParseLambda(source.ElementType, null, selector, values);
// Fix lambda by recreating to be of correct Func<> type in case
// the expression parsed to something other than IEnumerable<T>.
// For instance, a expression evaluating to List<T> would result
// in a lambda of type Func<T, List<T>> when we need one of type
// an Func<T, IEnumerable<T> in order to call SelectMany().
Type inputType = source.Expression.Type.GetGenericArguments()[0];
Type resultType = lambda.Body.Type.GetGenericArguments()[0];
Type enumerableType = typeof(IEnumerable<>).MakeGenericType(resultType);
Type delegateType = typeof(Func<,>).MakeGenericType(inputType, enumerableType);
lambda = Expression.Lambda(delegateType, lambda.Body, lambda.Parameters);
ParameterExpression[] parameters = new ParameterExpression[] {
Expression.Parameter(source.ElementType, "outer"), Expression.Parameter(resultType, "inner") };
LambdaExpression resultsSelectorLambda = DynamicExpression.ParseLambda(parameters, null, resultsSelector, values);
// Create the new query
return source.Provider.CreateQuery(
Expression.Call(
typeof(Queryable), "SelectMany",
new Type[] { source.ElementType /*TSource*/, /*,TCollection*/resultType /*TResult*/, resultsSelectorLambda.Body.Type},
source.Expression, Expression.Quote(lambda), Expression.Quote(resultsSelectorLambda)));
}
I still need to figure out how to do the following using Dynamic, the goal is to return a new result object.
var customerandorderflat = db.Customers
.SelectMany(c => c.Orders.SelectMany(o => o.Order_Details,
(ord, orddetail) => new
{
OrderID = ord.OrderID,
UnitPrice = orddetail.UnitPrice
}).DefaultIfEmpty(),
(cus, ord) => new
{
CustomerId = cus.CustomerID,
CompanyName = cus.CompanyName,
OrderId = ord.OrderID == null ? -1 : ord.OrderID,
UnitPrice = ord.UnitPrice
});
I am using the NWDB when I try:
var customerandorderquery = db.Customers .SelectMany(c => c.Orders.DefaultIfEmpty()).Select("new(CustomerId, CompanyName, OrderId)");
I get an error because CompanyName is in Customers not Orders. So it is not seeing the combination of the two objects.
When I do:
.SelectMany(c => c.Orders.DefaultIfEmpty(), (cus, ord) => new { CustomerId = cus.CustomerID, OrderId = ord.OrderID == null ? -1 : ord.OrderID });
It returns the desired result.
Related
I have an interface :
public IEnumerable<statisticsDaily> statsMonthly(string id, string dtFrom, string dtTo)
{
var rslt = ( from d in db.statMonth
join s in db.masterData on d.m_turbine_id equals s.m_turbine_id
where d.m_turbine_id == IPAddress.Parse(id) && d.m_date >= frm
group d by d.m_date.Month into g
select new statisticsDaily
{
Date = g.Key.ToString("MMM"),
Production = g.Sum(s => s.m_energy_prod),
m_wind_speed = g.Average(s => s.m_wind_speed),
Availability = g.Average(s => s.m_availability)
}
).AsEnumerable();
return rslt;
}
I have a controller which should perform async call:
public async Task StaticsMonthly(string id, string from, string to)
{
_statdaily.statsMonthly(id, startDate.ToString(), now.ToString()));
return Ok( result);
}
this gives me an error Severity Code
Error CIEnumerable' does not contain a definition for 'GetAwaiter' and no accessible extension method 'GetAwaiter' accepting a first argument of type 'IEnumerable' could be found (are you missing a using directive or an assembly reference?)
You need to make your action method as synchronous as it's doesn't doing any asynchronous work and not returning Task object which is causing the exception
This should work
public IActionResult StaticsMonthly(string id, string from, string to)
{
_statdaily.statsMonthly(id, startDate.ToString(), now.ToString()));
return Ok( result);
}
Make that your statsMonthly function should return a task:
public async<Task<IEnumerable<statisticsDaily>>> statsMonthly(string id, string dtFrom, string dtTo)
{
var rslt = await ( from d in db.statMonth
join s in db.masterData on d.m_turbine_id equals s.m_turbine_id
where d.m_turbine_id == IPAddress.Parse(id) && d.m_date >= frm
group d by d.m_date.Month into g
select new statisticsDaily
{
Date = g.Key.ToString("MMM"),
Production = g.Sum(s => s.m_energy_prod),
m_wind_speed = g.Average(s => s.m_wind_speed),
Availability = g.Average(s => s.m_availability)
}
).ToListAsync();
return rslt;
}
Then is your controller await the function:
public async Task StaticsMonthly(string id, string from, string to)
{
var results = await _statdaily.statsMonthly(id, startDate.ToString(), now.ToString()));
return Ok( result);
}
I have a property in a class with Column(TypeName) set to VARCHAR but when the linq uses ToList(), the SQL generated by linq converts to NVARCHAR instead. Is there a way to avoid nvarchar conversion which happens when ToList() method is called ?
var codes = xyz.Where(x => x.IsValid).Select(x => x.Code.ToLower()).ToList();
requests = requests.Where(p => codes.Contains(p.Type.ToLower()));
Property(c => c.Type).HasColumnType("varchar").HasMaxLength(3).IsFixedLength();
As shown above, though the Type property has column type set to VARCHAR but linq uses NVARCHAR when ToList() is used.
You can build your own predicate:
public static class PredicateBuilder
{
private static readonly MethodInfo asNonUnicodeMethodInfo =
typeof(EntityFunctions).GetMethod("AsNonUnicode");
private static readonly MethodInfo stringEqualityMethodInfo =
typeof(string).GetMethod("op_Equality");
public static Expression<Func<TEntity, bool>> ContainsNonUnicodeString<TEntity>(
IEnumerable<string> source,
Expression<Func<TEntity, string>> expression)
{
if (source == null) throw new ArgumentNullException("source");
if (expression == null) throw new ArgumentNullException("expression");
Expression predicate = null;
foreach (string value in source)
{
var fragment = Expression.Equal(
expression.Body,
Expression.Call(null,
asNonUnicodeMethodInfo,
Expression.Constant(value, typeof(string))),
false,
stringEqualityMethodInfo);
if (predicate == null)
{
predicate = fragment;
}
else
{
predicate = Expression.OrElse(predicate, fragment);
}
}
return Expression.Lambda<Func<TEntity, bool>>(predicate,
((LambdaExpression)expression).Parameters);
}
}
See more here: https://learn.microsoft.com/da-dk/archive/blogs/diego/workaround-for-performance-with-enumerable-contains-and-non-unicode-columns-against-ef-in-net-4-0
I have a class named VerseRangeReference that has the properties Chapter, FirstVerse and LastVerse.
I have decorated it with a TypeConverterAttribute [TypeConverter(typeof(VerseRangeReferenceConverter))]
I have an action on a controller like this
public Task<ViewResult> Verses(VerseRangeReference[] verses)
But the value of verses is always a single element with the value null. Here is my type converter
public class VerseRangeReferenceConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
throw new ArgumentNullException(nameof(value));
if (value.GetType() == typeof(string))
{
string source = (string)value;
return VerseRangeReference.ParseMultiple(source);
}
return null;
}
}
The result of VerseRangeReference.ParseMultiple(source) is a valid array of instances of VerseRange.
I had to implement a custom model binder. If someone can think of a way to do this with a TypeConverter then I will accept that answer instead because model binders are more complicated.
public class VerseRangeReferenceArrayModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
string modelName = bindingContext.ModelName;
ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(modelName);
if (valueProviderResult != ValueProviderResult.None)
{
VerseRangeReference[] verseRangeReferences = VerseRangeReference.ParseMultiple(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(verseRangeReferences);
}
return Task.CompletedTask;
}
}
public class VerseRangerReferenceArrayModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType == typeof(VerseRangeReference[]))
return new BinderTypeModelBinder(typeof(VerseRangeReferenceArrayModelBinder));
return null;
}
}
This must be registered.
services.AddMvc(options =>
{
options.ModelBinderProviders.Insert(0, new VerseRangerReferenceArrayModelBinderProvider());
})
.SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
You can use a type converter to bind a comma separated string to a sequence of values. However, the type converter should convert from the string to the sequence directly. This means that the type converter should be configured for something like IEnumerable<T> or T[]. To simplify matters I will continue my explanation for IEnumerable<int> but if you want to use arrays instead you should just make sure that the type converter converts to an array instead of something that implements IEnumerable<T>.
You can configure a type converter for IEnumerable<int> using TypeDescriptor.AddAttributes:
TypeDescriptor.AddAttributes(
typeof(IEnumerable<int>),
new TypeConverterAttribute(typeof(EnumerableIntTypeConverter)));
This configures EnumerableIntTypeConverter as a type converter that can convert IEnumerable<int>.
This call has to be made when the process starts and in the case of ASP.NET Core this can conveniently be done in the Startup.Configure method.
Here is the EnumerableIntTypeConverter that converts the comma separated string of numbers to a list of ints:
internal class EnumerableIntTypeConverter : TypeConverter
{
private const char Separator = ',';
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
=> sourceType == typeof(string);
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (!(value is string #string))
throw new NotSupportedException($"{GetType().Name} cannot convert from {(value != null ? value.GetType().FullName : "(null)")}.");
if (#string.Length == 0)
return Enumerable.Empty<int>();
var numbers = new List<int>();
var start = 0;
var end = GetEnd(#string, start);
while (true)
{
if (!int.TryParse(
#string.AsSpan(start, end - start),
NumberStyles.AllowLeadingSign,
culture,
out var number))
throw new FormatException($"{GetType().Name} cannot parse string with invalid format.");
numbers.Add(number);
if (end == #string.Length)
break;
start = end + 1;
end = GetEnd(#string, start);
}
return numbers;
}
private static int GetEnd(string #string, int start)
{
var end = #string.IndexOf(Separator, start);
return end >= 0 ? end : #string.Length;
}
}
The parsing uses System.Memory to avoid allocating a new string for each number in the list. If your framework doesn't have the int.TryParse overload that accepts a Span<char> you can use string.Substring instead.
I'm building a data service in WCF and I'm using a combination of reflection and open types as some of the data elements need to be created on-the-fly. Most everything is working well, but I can't get filters to work with the open type values.
The error I get is:
<message xml:lang="en-US">An error occurred while processing this request.</message>
<innererror>
<message>The method or operation is not implemented.</message>
<type>System.NotImplementedException</type>
<stacktrace> at lambda_method(Closure , GeographyProvider )
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
at System.Data.Services.DataService`1.SerializeResponseBody(RequestDescription description, IDataService dataService)
at System.Data.Services.DataService`1.HandleRequest()</stacktrace>
</innererror>
I'm using an expression visitor to rewrite the LINQ expressions and it is successfully pulling the value for the open type. At this point, I'm not sure what method or operation I need to implement is. The expression tree looks like this after the expression visitor has done it's work:
Alteryx.Web.API.DatasetProvider+<GetDatasets>d__0.Where(element =>
(element.Variant == "AGSSTD_701000")).SelectMany(element =>
ConvertChecked(element.Geographies)).Where(element =>
(element.Key == "County")).SelectMany(element =>
ConvertChecked(element.Geographies)).Where(element =>
(element.Key == "36")).SelectMany(element =>
ConvertChecked(element.Geographies)).Where(it =>
Convert(((Invoke((o, name) => GetOpenValue(o, name), it, "POPCY") >= Convert(100000)) == True)))}
I've put a break point in the GetOpenValue method and it is getting called and returning the correct value. Any thoughts on where I need to go from here?
Based on Vitek's suggestions, I added checks for Convert and the comparison methods to my expression visitor, but they aren't found. Here is what my visitor code looks like:
static readonly MethodInfo GetValueOpenPropertyMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"GetValue",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(string) },
null
);
static readonly MethodInfo OpenConvertMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"Convert",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(ResourceType) },
null
);
static readonly MethodInfo GreaterThanOrEqualMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"GreaterThanOrEqual",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(object) },
null
);
static readonly MethodInfo EqualMethodInfo =
typeof(OpenTypeMethods)
.GetMethod(
"Equal",
BindingFlags.Static | BindingFlags.Public,
null,
new Type[] { typeof(object), typeof(object) },
null
);
static readonly Expression<Func<object, string, object>> GetValueOpenReplacement =
(o, name) => GetOpenValue(o, name);
static object GetOpenValue(object o, string name)
{
return (o as OpenDataProvider).GetValue(name);
}
static readonly Expression<Func<object, object, object>> GetGreaterThanOrEqualReplacement =
(left, right) => GetOpenGreaterThanOrEqual(left, right);
static object GetOpenGreaterThanOrEqual(object left, object right)
{
string s = left.ToString();
return true;
}
static readonly Expression<Func<object, object, object>> GetEqualReplacement =
(left, right) => GetOpenEqual(left, right);
static object GetOpenEqual(object left, object right)
{
string s = left.ToString();
return true;
}
protected override Expression VisitMethodCall(
MethodCallExpression node
)
{
if (node.Method == GetValueOpenPropertyMethodInfo)
{
// Arguments[0] - the resource to get property from
// Arguments[1] - the ResourceProperty to get
// Invoke the replacement expression, passing the
// appropriate parameters.
if (node.Arguments[0].Type.BaseType == typeof(OpenDataProvider))
{
OpenDataProvider.RequestValue(((ConstantExpression)node.Arguments[1]).Value.ToString());
}
return Expression.Invoke(
Expression.Quote(GetValueOpenReplacement),
node.Arguments[0],
node.Arguments[1]
);
}
else if (node.Method == OpenConvertMethodInfo)
{
// Arguments[0] – the resource
// Arguments[1] – the ResourceType
// no need to do anything, so just
// return the argument
return this.Visit(node.Arguments[0]);
}
else if (node.Method == GreaterThanOrEqualMethodInfo)
{
// Invoke the replacement expression, passing the
// appropriate parameters.
return Expression.Invoke(
Expression.Quote(GetGreaterThanOrEqualReplacement),
node.Arguments[0],
node.Arguments[1]
);
}
else if (node.Method == EqualMethodInfo)
{
// Invoke the replacement expression, passing the
// appropriate parameters.
return Expression.Invoke(
Expression.Quote(GetEqualReplacement),
node.Arguments[0],
node.Arguments[1]
);
}
return base.VisitMethodCall(node);
}
I've put breakpoints in all of the if blocks in the VisitMethodCall method, but only the GetValueOpenProperty block is ever called.
Thanks!
Vitek kindly provided the answer to this here: http://social.msdn.microsoft.com/Forums/en-US/adodotnetdataservices/thread/bfb62cf5-48cc-4435-ae9a-76e4a13d762a
To summarize, the ExpressionVisitor needs to overide the OpenTypeMethods in the VisitBinary method.
Thanks for the help, Vitek!
I want to use SQL's Like keyword in dynamic LINQ.
The query that I want to make is like this
select * from table_a where column_a like '%search%'
Where the column_a can be dynamically changed to other column etc
In this dynamic LINQ
var result = db.table_a.Where( a=> (a.column_a.Contains("search")) );
But the column can't be dynamically changed , only the search key can
How do we create a dynamic LINQ like
var result = db.table_a.Where("column_a == \"search\"");
That we can change the column and the search key dynamically
This should work for you:
.Where("AColumnName.Contains(#0)", "Criteria")
Create an ExtensionMethods class with this function
public static IQueryable<T> Like<T>(this IQueryable<T> source, string propertyName, string keyword)
{
var type = typeof(T);
var property = type.GetProperty(propertyName);
string number = "Int";
if (property.PropertyType.Name.StartsWith(number))
return source;
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var constant = Expression.Constant("%" + keyword + "%");
MethodCallExpression methodExp = Expression.Call(null, typeof(SqlMethods).GetMethod("Like", new Type[] { typeof(string), typeof(string) }), propertyAccess, constant);
Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(methodExp, parameter);
return source.Where(lambda);
}
And then call it like this:
var result = db.table_a.Like("column_a", "%search%");
http://weblogs.asp.net/rajbk/archive/2007/09/18/dynamic-string-based-queries-in-linq.aspx
Addition:
Use an expression tree
How do I create an expression tree to represent 'String.Contains("term")' in C#?
That is what the dynamic linq library does internally.
I do not believe there is a direct translation to SQL for the LIKE keyword in LINQ. You could build one if you used expression trees, but I haven't gotten that good yet.
What I do is something like this:
using System.Data.Linq.SqlClient;
if (!string.IsNullOrEmpty(data.MailerName))
search = search.Where(a => SqlMethods.Like(a.Mailer.Name, string.Format("%{0}%", data.MailerName)));
where search is the query I'm building and data is the object containing the properties that hold the search criteria. I build the query dynamically by listing all of the possible search criteria in this way, which adds the appropriate Where methods to search.
Maybe a bit late but Another approach is add Extention method that use Contains to simulate Like keyword as :
public static class DbHelpers
{
public static IQueryable<T> Like<T>(this IQueryable<T> source, string propertyName, string propertyValue)
{
var prop = typeof(T).GetProperty(propertyName);
if (prop == null || prop.PropertyType.Name.StartsWith("Int"))
return source;
ParameterExpression parameter = Expression.Parameter(typeof(T), "row");
Expression property = Expression.Property(parameter, propertyName);
Expression value = Expression.Constant(propertyValue);
var containsmethod = value.Type.GetMethod("Contains", new[] { typeof(string) });
var call = Expression.Call(property, containsmethod, value);
var lambda = Expression.Lambda<Func<T, bool>>(call, parameter);
return source.Where(lambda);
}
}
And use of it:
var foo = entity.AsQueryable().Like("Name", "bla bla");
If send PropertyName with type of int, the method return original entity that you passed before to it.