Query by field defined in interface - c#

I have a couple of classes implementing an ISortable interface:
public interface ISortable
{
int Id { get; set; }
int? Idx { get; set; }
}
In my DbContext class I have an update method that should do some extra stuff for entities that implements the ISortable:
public void UpdateSingle<T>(T item) where T : class
{
// If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
var sortable = item as ISortable;
if (sortable != null)
{
Detach(item); // need to detach the entity from the context in order to retrieve the old values from DB
var oldItem = Find<T>(sortable.Id) as ISortable;
if (oldItem != null && sortable.Idx != oldItem.Idx)
{
var entities = FindAll<T>().ToList().Cast<ISortable>();
var oldIdx = oldItem.Idx;
var newIdx = sortable.Idx;
if (newIdx > oldIdx)
{
var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
}
}
else
{
var toUpdate = entities.Where(a => a.Idx >= newIdx && a.Idx < oldIdx).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
}
}
}
Detach(oldItem);
Attach(item); // re-attach to enable saving
}
Entry(item).State = EntityState.Modified;
Commit();
}
What I'm wondering about is this line:
var entities = FindAll<T>().ToList().Cast<ISortable>();
I have to convert the LINQ to SQL expression to a list in order to cast the entities to ISortable. And I need to cast it to ISortable in order to perform this Where:
var toUpdate = entities.Where(a => a.Idx <= newIdx && a.Idx > oldIdx).Select(a => a);
The Idx attribute is exposed by the interface.
The problem is that calling ToList() on FindAll() loads the whole table into memory.
Is there a way of performing the Where without first loading the whole table, and without losing the generic implementation?
The idea here is that I want to perform some common action on update for all entities that are "sortable". For this to work the update method needs to be generic in order to handle various classes, but then I need the interface to expose the necessary fields... If there's a better way of doing this (there probably is), please let me know. :-)

The problem is that calling ToList() on FindAll() loads the whole table into memory.
Use AsEnumerable instead of ToList; it just changes the compile-time type to IEnumerable<T> instead of IQueryable<T>, so the subsequent operations are executed in memory rather than in the database, but only one item at a time is processed (items are fetched from the DB one by one as needed by the subsequent operations).

Trying again, this time with expressions. I think this should work:
public void UpdateSingle<T>(T item) where T : class
{
// If entity is Sortable, update the indexes of the records between the new and the old index of the updated entity
var sortable = item as ISortable;
if (sortable != null)
{
Detach(item); // need to detach the entity from the context in order to retrieve the old values from DB
var oldItem = Find<T>(sortable.Id);
if (oldItem != null && sortable.Idx != oldItem.Idx)
{
UpdateSingleSortable(oldItem, sortable);
}
Detach(oldItem);
Attach(item); // re-attach to enable saving
}
Entry(item).State = EntityState.Modified;
Commit();
}
public void UpdateSingleSortable<T>(T oldItem, ISortable sortable)
where T : class
{
var entities = FindAll<T>();
var oldIdx = oldItem.Idx;
var newIdx = sortable.Idx;
if (newIdx > oldIdx)
{
var expression = GenerateExpressionA(oldItem, newIdx, oldIdx);
var typedExpression = expression as Expression<Func<T, bool>>;
var toUpdate = entities.Where(typedExpression).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx - 1;
}
}
else
{
var expression = GenerateExpressionB(oldItem, newIdx, oldIdx);
var typedExpression = expression as Expression<Func<T, bool>>;
var toUpdate = entities.Where(typedExpression).Select(a => a);
foreach (var toUpdateEntity in toUpdate)
{
toUpdateEntity.Idx = toUpdateEntity.Idx + 1;
}
}
}
Expression GenerateExpressionB<T>(T t, int? newIdx, int? oldIdx)
{
// a => a.Idx >= newIdx && a.Idx < oldIdx
var underlyingType = t.GetType();
var idxGetter = underlyingType.GetProperty("Idx");
Type genericFunc = typeof(Func<,>);
Type[] typeArgs = { underlyingType, typeof(bool) };
Type returnType = genericFunc.MakeGenericType(typeArgs);
var param = Expression.Parameter(underlyingType);
var toReturn = Expression.Lambda(
returnType,
Expression.And
(
Expression.GreaterThanOrEqual(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(newIdx, typeof(int?))
),
Expression.LessThan(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(oldIdx, typeof(int?))
)
),
param);
return toReturn;
}
Expression GenerateExpressionA<T>(T t, int? newIdx, int? oldIdx)
{
// a => a.Idx <= newIdx && a.Idx > oldIdx
var underlyingType = t.GetType();
var idxGetter = underlyingType.GetProperty("Idx");
Type genericFunc = typeof(Func<,>);
Type[] typeArgs = { underlyingType, typeof(bool) };
Type returnType = genericFunc.MakeGenericType(typeArgs);
var param = Expression.Parameter(underlyingType);
var toReturn = Expression.Lambda(
returnType,
Expression.And
(
Expression.LessThanOrEqual(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(newIdx, typeof(int?))
),
Expression.GreaterThan(
Expression.MakeMemberAccess(param, idxGetter),
Expression.Constant(oldIdx, typeof(int?))
)
),
param);
toReturn.Dump();
return toReturn;
}

Just change the signature of the method to the following:
public void UpdateSingle<T>(T item)
where T : class, ISortable
Then not only can you perform the query on the DB end (you won't need to pull the collection into memory to get the items meeting the given condition) you also won't be doing the check at runtime; you'll be checking to make sure T implements ISortable at compile time.

Related

How to get dynamic column data linq

I have following code,
public List<MemberDto> GetMembers(out int rowCount,int pageIndex,int pageSize, string seachColumn = "", string searchTerm = "", string sortBy = "", string sortDiection = "")
{
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m).AsEnumerable();
if (!string.IsNullOrEmpty(sortBy))
{
PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(EFModel.ClientData.Member)).Find(sortBy, true);
members = (sortDiection.ToLower() == "descnding") ? members.OrderByDescending(x => prop.GetValue(x)).ToList() : members.OrderBy(x => prop.GetValue(x)).ToList();
}
rowCount = (!string.IsNullOrEmpty(searchTerm)) ? members.Count() : rowCount = context.Members.Count() ;
members = members.Skip(pageIndex).Take(pageSize).ToList();
List<MemberDto> memberDtos = new List<MemberDto>();
mapper.Map(members, memberDtos);
return memberDtos;
}
In the above code, I seachColumn value can be ("a","b",or ""). When seachColumn = "a" I need to search table data by column MemberNumber based on searchTerm value
When seachColumn = "b" I need to search table data by column LastName. based on searchTerm value
to achieve that I wrote following code.
if(seachBy == "a")
{
var sa = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
select m).AsEnumerable();
}
else if (seachBy == "b")
{
var sa = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m).AsEnumerable();
}
I know, I tried code is bit fool. Have any proper way to do this?
Yes there is a better way to do this. First of all you want to do this all as an IQueryable - Why? - Because as soon as you do .AsEnumerable() or .ToList() the query is executed on the DB Server and the data is loaded into Memory.
So in your code here - because you have called .AsEnumerable() it has loaded all context.Members.Where condition is true into Memory:
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m)
.AsEnumerable();
Your second part of code is pagination. What we normally do is write a IQueryable extension methods.
So add the following class with the 2 extension methods to a common location.
public static class IQueryableExtensions
{
public static IQueryable<T> ApplyPagination<T>(this IQueryable<T> source, string sortDirection, string sortBy, int pageNumber, int pageSize)
{
var sortDirectionInternal = sortDirection == "asc" ? "OrderBy" : "OrderByDescending";
var orderBy = sortBy;
if (pageSize != -1) // -1 is for All - I don't apply pagination if pageSize == -1.
{
return source.OrderBy(orderBy, sortDirectionInternal)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize);
}
return source.OrderBy(orderBy, sortDirection);
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string ordering, string sortDirection)
{
var type = typeof(T);
// Get Property to Sort By
var property = type.GetProperty(ordering);
// If Property is NULL (not found) - Just use the first Property (Default) to ORDER BY - as this will prevent Exception
if (property == null)
{
property = type.GetProperties().First();
}
var parameter = Expression.Parameter(type, "p");
var propertyAccess = Expression.MakeMemberAccess(parameter, property);
var orderByExp = Expression.Lambda(propertyAccess, parameter);
var resultExp = Expression.Call(typeof(Queryable), sortDirection, new[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExp));
return source.Provider.CreateQuery<T>(resultExp);
}
}
So now your above code will look something like this - I didn't use VS so there might be some sytnax errors:
// NOTE: you will need to include the namespace for the new IQueryableExtensions class
var members = (from m in context.Members
where (string.IsNullOrEmpty(searchTerm) || m.MemberNumber.Equals(searchTerm))
|| (string.IsNullOrEmpty(searchTerm) || m.LastName.Equals(searchTerm))
select m);
// retrieve count only if you need to of total members that match the above criteria
rowCount = members.Count();
// This is all you need to do! - ApplyPagination(params) -
members = members.ApplyPagination(sortDiection, sortBy, pageIndex, pageSize);
return mapper.Map<List<MemberDto>>(members);

Searching a DateTime using a search string in Entity Framework code first

I have an MVC project which outputs a nullable DateTime (DateTime?) as a string to the user for each model item as shown below (my user interface has paging so I only do this computation for a small number of records - in other words, this part is okay):
foreach (DocumentEvent item in Model.items)
#(item?.TimeUtc?.ToString() ?? "N/A")
I want to add a search functionality. I've tried searching as follows, but this is not performant because AsEnumerable materializes my list and I am now in the C# world, enumerating through each record:
using (var context = new ClientEventsContext())
var items = context.Events.AsEnumerable().Where(x => {
(x?.TimeUtc?.ToString() ?? "N/A").Contains(model.search)
});
Instead I want to take advantage of my SQL Server database. How can I build an SQL Server-friendly query for the above code without AsEnumerable that will produce the same results as my current logic?
Here is how you can build and use LINQ to Entities compatible conversion from date to string in M/d/yyyy h:mm:ss tt format. Rather that embedding that monster inside the query, I will use a custom "marker" method and will bind the implementation using ExpressionVisitor. This way you can experiment and change the format if needed (even add some controlling arguments) w/o affecting the readability of the query.
First, the implementation:
public static class EFExtensions
{
public static string ToCustomDateFormat(this DateTime value)
{
// Should never happen
throw new InvalidOperationException();
}
public static IQueryable<T> ApplyCustomDateFormat<T>(this IQueryable<T> source)
{
var expression = new CustomDateFormatBinder().Visit(source.Expression);
if (source.Expression == expression) return source;
return source.Provider.CreateQuery<T>(expression);
}
class CustomDateFormatBinder : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.DeclaringType == typeof(EFExtensions) && node.Method.Name == "ToCustomDateFormat")
{
var date = Visit(node.Arguments[0]);
var year = DatePart(date, v => DbFunctions.Right("0000" + v.Year, 4));
var month = DatePart(date, v => v.Month.ToString());
var day = DatePart(date, v => v.Day.ToString());
var hour = DatePart(date, v => (1 + (v.Hour + 11) % 12).ToString());
var minute = DatePart(date, v => DbFunctions.Right("0" + v.Minute, 2));
var second = DatePart(date, v => DbFunctions.Right("0" + v.Second, 2));
var amPM = DatePart(date, v => v.Hour < 12 ? "AM" : "PM");
var dateSeparator = Expression.Constant("/");
var timeSeparator = Expression.Constant(":");
var space = Expression.Constant(" ");
var result = Expression.Call(
typeof(string).GetMethod("Concat", new Type[] { typeof(string[]) }),
Expression.NewArrayInit(typeof(string),
month, dateSeparator, day, dateSeparator, year, space,
hour, timeSeparator, minute, timeSeparator, second, space, amPM));
return result;
}
return base.VisitMethodCall(node);
}
Expression DatePart(Expression date, Expression<Func<DateTime, string>> part)
{
var parameter = part.Parameters[0];
parameterMap.Add(parameter, date);
var body = Visit(part.Body);
parameterMap.Remove(parameter);
return body;
}
Dictionary<ParameterExpression, Expression> parameterMap = new Dictionary<ParameterExpression, Expression>();
protected override Expression VisitParameter(ParameterExpression node)
{
Expression replacement;
return parameterMap.TryGetValue(node, out replacement) ? replacement : node;
}
}
}
then the usage:
var items = context.Events
.Where(x => x.TimeUtc != null &&
x.TimeUtc.Value.ToCustomDateFormat().Contains(model.search))
.ApplyCustomDateFormat();
I found a solution. This syntax seems to work:
using (var context = new ClientEventsContext())
var items = context.Events.Where(x => {
x.TimeUtc.HasValue && x.TimeUtc.Value.ToString().Contains(model.search)
});
Another solution using SqlFunctions library to convert anything to Raw Query and do the job at SQL level, for example:
using (var context = new ClientEventsContext())
var items = context.Events.Where(x =>
SqlFunctions.PatIndex(model.search,
SqlFunctions.DateName('your pattern here', x?.TimeUtc) ?? "N/A").Value > -1
);

Entity, Contains or intersect, is this query possible?

I have a list of string retreived this way :
List<string> keyWords = db.MotCleRecherche.Select(t => t.MotClé).ToList();
I also have a query that takes many parameters to be executed :
object = db.DAapp.Where(t => t.CODE_ART.StartsWith(s) && t.DATE_CREAT >= debut && t.DATE_CREAT < fin).ToList()
now... I want to add this kind of condition :
db.DAapp.Where(t => t.DESC_ART.ToLower().Contains(keywords.ToLower()))
or
db.DAapp.Where(t => t.DESC_ART.ToLower().Intersect(keywords.ToLower()))
I guess you could see it comming... I can't figure how to really make this work... all i know is considering a list X filed and Y list filled:
X.Intersect(Y).Any()
will return true if there is something equal... but DESC_ART is just ONE long string and i want to know if some of my keywords are in there
I agree with Stephen that you should cast the keyWords to lower first before comparing. But if you really need to do this with linq you can do something like this.
var result = db.DAapp.Where(t => keywords.Any(keyword=> string.Equals(keyword,t.DESC_ART, StringComparison.InvariantCultureIgnoreCase )));
This will cause a to lower to get called on each string every iteration of your linq loop so its expensive.
First add this to your project (for example to your controller):
static Expression<Func<T, bool>> AnyOf<T>(
params Expression<Func<T, bool>>[] expressions)
{
if (expressions == null || expressions.Length == 0) return x => false;
if (expressions.Length == 1) return expressions[0];
var body = expressions[0].Body;
var param = expressions[0].Parameters.Single();
for (int i = 1; i < expressions.Length; i++)
{
var expr = expressions[i];
var swappedParam = new SwapVisitor(expr.Parameters.Single(), param)
.Visit(expr.Body);
body = Expression.OrElse(body, swappedParam);
}
return Expression.Lambda<Func<T, bool>>(body, param);
}
class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
I find this from stackoverflow. now you can create desired query as below :
var filters = new List<Expression<Func<Models.DAapp, bool>>>();
foreach (var st in keyWords)
filters.Add(d => d.DESC_ART.ToLower().Contains(st.ToLower()));
var lambda = AnyOf(filters.ToArray());
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
);
q = q.Where(lambda);
var res = q.ToList();
Please be noticed that, this solution creates only one select query with multiple where expressions. which is more efficient that other solutions like below that contains multiple select queries inside where clause :
var q = db.DAapp.Where(t =>
t.CODE_ART.StartsWith(s)
&& t.DATE_CREAT >= debut
&& t.DATE_CREAT < fin
&& keyWords.Any(k => t.DESC_ART.ToLower().Contains(k.ToLower()))
);

Create Expression for List.Any clause

Consider this hierarchy of classes.
class Event
{
public Attendees[] AttendeesList;
}
class Attendees
{
public ComplexProperty Property;
public object Value;
}
class ComplexProperty
{
}
class Program
{
static void Main(string[] args)
{
// There are constants.
ComplexProperty constproperty = new ComplexProperty();
object constValue = 5;
// consider this linq query:
Event evnt = new Event();
var result = evnt.AttendeesList.Any((attnds) => attnds.Property == constproperty && attnds.Value == constValue);
// I want to create an Expression tree for above linq query. I need something like this:
ParameterExpression parameter = Expression.Parameter(typeof(Attendees));
Expression left = Expression.Property(parameter, typeof(ComplexProperty).GetProperty("Property"));
// complete this piece.......
}
}
I want to create a Linq.Expressions.Expression for evnt.AttendeesList.Any((attnds) => attnds.Property == constproperty && attnds.Value == constValue);
this linq query. How to query collection with Linq.Expressions looks similar, but I have Any in my linq expression.
Please help.
This will give you a start:
ParameterExpression parameter = Expression.Parameter(typeof(Attendees));
Expression left = Expression.Equal(Expression.Property(parameter, "Property"), Expression.Constant(constproperty));
var objType = constValue == null ? typeof(object) : constValue.GetType();
var convertLeft = Expression.Convert(Expression.Property(parameter, "Value"), objType);
var convertRight = Expression.Convert(Expression.Constant(constValue), objType);
Expression right = Expression.Equal(convertLeft, convertRight);
Expression joined = Expression.And(left, right);
var anyMethod = typeof(Queryable).GetMethods().Where(m => m.Name=="Any" && m.GetParameters().Count() == 2).First().MakeGenericMethod(typeof(Attendees));
var call = Expression.Call(anyMethod, Expression.Constant(evnt.AttendeesList, typeof(IQueryable<Attendees>)), Expression.Lambda(joined, parameter));
I changed Value to type int for ease of use. If you want to keep it as an object, you will need to add a Expression.Convert() call in the right Expression.

LINQ Expression to return Property value?

I'm trying to create a generic function to help me select thousands of records using LINQ to SQL from a local list. SQL Server (2005 at least) limits queries to 2100 parameters and I'd like to select more records than that.
Here would be a good example usage:
var some_product_numbers = new int[] { 1,2,3 ... 9999 };
Products.SelectByParameterList(some_product_numbers, p => p.ProductNumber);
Here is my (non-working) implementation:
public static IEnumerable<T> SelectByParameterList<T, PropertyType>(Table<T> items,
IEnumerable<PropertyType> parameterList, Expression<Func<T, PropertyType>> property) where T : class
{
var groups = parameterList
.Select((Parameter, index) =>
new
{
GroupID = index / 2000, //2000 parameters per request
Parameter
}
)
.GroupBy(x => x.GroupID)
.AsEnumerable();
var results = groups
.Select(g => new { Group = g, Parameters = g.Select(x => x.Parameter) } )
.SelectMany(g =>
/* THIS PART FAILS MISERABLY */
items.Where(item => g.Parameters.Contains(property.Compile()(item)))
);
return results;
}
I have seen plenty of examples of building predicates using expressions. In this case I only want to execute the delegate to return the value of the current ProductNumber. Or rather, I want to translate this into the SQL query (it works fine in non-generic form).
I know that compiling the Expression just takes me back to square one (passing in the delegate as Func) but I'm unsure of how to pass a parameter to an "uncompiled" expression.
Thanks for your help!
**** EDIT:** Let me clarify further:
Here is a working example of what I want to generalize:
var local_refill_ids = Refills.Select(r => r.Id).Take(20).ToArray();
var groups = local_refill_ids
.Select((Parameter, index) =>
new
{
GroupID = index / 5, //5 parameters per request
Parameter
}
)
.GroupBy(x => x.GroupID)
.AsEnumerable();
var results = groups
.Select(g => new { Group = g, Parameters = g.Select(x => x.Parameter) } )
.SelectMany(g =>
Refills.Where(r => g.Parameters.Contains(r.Id))
)
.ToArray()
;
Results in this SQL code:
SELECT [t0].[Id], ... [t0].[Version]
FROM [Refill] AS [t0]
WHERE [t0].[Id] IN (#p0, #p1, #p2, #p3, #p4)
... That query 4 more times (20 / 5 = 4)
I've come up with a way to chunk the query into pieces - i.e. you give it 4000 values, so it might do 4 requests of 1000 each; with full Northwind example. Note that this might not work on Entity Framework, due to Expression.Invoke - but is fine on LINQ to SQL:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace ConsoleApplication5 {
/// SAMPLE USAGE
class Program {
static void Main(string[] args) {
// get some ids to play with...
string[] ids;
using(var ctx = new DataClasses1DataContext()) {
ids = ctx.Customers.Select(x => x.CustomerID)
.Take(100).ToArray();
}
// now do our fun select - using a deliberately small
// batch size to prove it...
using (var ctx = new DataClasses1DataContext()) {
ctx.Log = Console.Out;
foreach(var cust in ctx.Customers
.InRange(x => x.CustomerID, 5, ids)) {
Console.WriteLine(cust.CompanyName);
}
}
}
}
/// THIS IS THE INTERESTING BIT
public static class QueryableChunked {
public static IEnumerable<T> InRange<T, TValue>(
this IQueryable<T> source,
Expression<Func<T, TValue>> selector,
int blockSize,
IEnumerable<TValue> values) {
MethodInfo method = null;
foreach(MethodInfo tmp in typeof(Enumerable).GetMethods(
BindingFlags.Public | BindingFlags.Static)) {
if(tmp.Name == "Contains" && tmp.IsGenericMethodDefinition
&& tmp.GetParameters().Length == 2) {
method = tmp.MakeGenericMethod(typeof (TValue));
break;
}
}
if(method==null) throw new InvalidOperationException(
"Unable to locate Contains");
foreach(TValue[] block in values.GetBlocks(blockSize)) {
var row = Expression.Parameter(typeof (T), "row");
var member = Expression.Invoke(selector, row);
var keys = Expression.Constant(block, typeof (TValue[]));
var predicate = Expression.Call(method, keys, member);
var lambda = Expression.Lambda<Func<T,bool>>(
predicate, row);
foreach(T record in source.Where(lambda)) {
yield return record;
}
}
}
public static IEnumerable<T[]> GetBlocks<T>(
this IEnumerable<T> source, int blockSize) {
List<T> list = new List<T>(blockSize);
foreach(T item in source) {
list.Add(item);
if(list.Count == blockSize) {
yield return list.ToArray();
list.Clear();
}
}
if(list.Count > 0) {
yield return list.ToArray();
}
}
}
}
Easiest way to do this: Use LINQKit (Free, non-restrictive license)
Working version of code:
public static IEnumerable<T> SelectByParameterList<T, PropertyType>(this Table<T> items, IEnumerable<PropertyType> parameterList, Expression<Func<T, PropertyType>> propertySelector, int blockSize) where T : class
{
var groups = parameterList
.Select((Parameter, index) =>
new
{
GroupID = index / blockSize, //# of parameters per request
Parameter
}
)
.GroupBy(x => x.GroupID)
.AsEnumerable();
var selector = LinqKit.Linq.Expr(propertySelector);
var results = groups
.Select(g => new { Group = g, Parameters = g.Select(x => x.Parameter) } )
.SelectMany(g =>
/* AsExpandable() extension method requires LinqKit DLL */
items.AsExpandable().Where(item => g.Parameters.Contains(selector.Invoke(item)))
);
return results;
}
Example usage:
Guid[] local_refill_ids = Refills.Select(r => r.Id).Take(20).ToArray();
IEnumerable<Refill> results = Refills.SelectByParameterList(local_refill_ids, r => r.Id, 10); //runs 2 SQL queries with 10 parameters each
Thanks again for all your help!
LINQ-to-SQL still works via standard SQL parameters, so writing a fancy expression isn't going to help. There are 3 common options here:
pack the ids into (for example) csv/tsv; pass down as a varchar(max) and use a udf to split it (at the server) into a table variable; join to the table variable
use a table-valued-parameter in SQL Server 2008
have a table on the server that you could push the ids into (perhaps via SqlBulkCopy) (perhaps with a "session guid" or similar); join to this table
The first is the simplest; getting a "split csv udf" is trivial (just search for it). Drag the udf onto the data-context and consume from there.
Pass IQuerable to the Contains function instead of list or array. please see the below example
var df_handsets = db.DataFeed_Handsets.Where(m => m.LaunchDate != null).
Select(m => m.Name);
var Make = (from m in db.MobilePhones
where (m.IsDeleted != true || m.IsDeleted == null)
&& df_handsets.Contains(m.Name)
orderby m.Make
select new { Value = m.Make, Text = m.Make }).Distinct();
when you pass list or array it is passed in form of parameters and its exceed the counts when the list items count is greater than 2100.
You can create your own QueryProvider
public class QueryProvider : IQueryProvider
{
// Translates LINQ query to SQL.
private readonly Func<IQueryable, DbCommand> _translator;
// Executes the translated SQL and retrieves results.
private readonly Func<Type, string, object[], IEnumerable> _executor;
public QueryProvider(
Func<IQueryable, DbCommand> translator,
Func<Type, string, object[], IEnumerable> executor)
{
this._translator = translator;
this._executor = executor;
}
#region IQueryProvider Members
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new Queryable<TElement>(this, expression);
}
public IQueryable CreateQuery(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
bool isCollection = typeof(TResult).IsGenericType &&
typeof(TResult).GetGenericTypeDefinition() == typeof(IEnumerable<>);
var itemType = isCollection
// TResult is an IEnumerable`1 collection.
? typeof(TResult).GetGenericArguments().Single()
// TResult is not an IEnumerable`1 collection, but a single item.
: typeof(TResult);
var queryable = Activator.CreateInstance(
typeof(Queryable<>).MakeGenericType(itemType), this, expression) as IQueryable;
IEnumerable queryResult;
// Translates LINQ query to SQL.
using (var command = this._translator(queryable))
{
var parameters = command.Parameters.OfType<DbParameter>()
.Select(parameter => parameter)
.ToList();
var query = command.CommandText;
var newParameters = GetNewParameterList(ref query, parameters);
queryResult = _executor(itemType,query,newParameters);
}
return isCollection
? (TResult)queryResult // Returns an IEnumerable`1 collection.
: queryResult.OfType<TResult>()
.SingleOrDefault(); // Returns a single item.
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
#endregion
private static object[] GetNewParameterList(ref string query, List<DbParameter> parameters)
{
var newParameters = new List<DbParameter>(parameters);
foreach (var dbParameter in parameters.Where(p => p.DbType == System.Data.DbType.Int32))
{
var name = dbParameter.ParameterName;
var value = dbParameter.Value != null ? dbParameter.Value.ToString() : "NULL";
var pattern = String.Format("{0}[^0-9]", dbParameter.ParameterName);
query = Regex.Replace(query, pattern, match => value + match.Value.Replace(name, ""));
newParameters.Remove(dbParameter);
}
for (var i = 0; i < newParameters.Count; i++)
{
var parameter = newParameters[i];
var oldName = parameter.ParameterName;
var pattern = String.Format("{0}[^0-9]", oldName);
var newName = "#p" + i;
query = Regex.Replace(query, pattern, match => newName + match.Value.Replace(oldName, ""));
}
return newParameters.Select(x => x.Value).ToArray();
}
}
static void Main(string[] args)
{
using (var dc=new DataContext())
{
var provider = new QueryProvider(dc.GetCommand, dc.ExecuteQuery);
var serviceIds = Enumerable.Range(1, 2200).ToArray();
var tasks = new Queryable<Task>(provider, dc.Tasks).Where(x => serviceIds.Contains(x.ServiceId) && x.CreatorId==37 && x.Creator.Name=="12312").ToArray();
}
}

Categories

Resources