Entity Framework. Where clause with parameter from query as variable - entity-framework

Task: use different where clause in one query
Here is example (it is not real query, just to illustrate the problem)
var events = ctx.Events; // ctx - EntityFramework context
var res = events
.GroupBy(ee => ee.State)
.Select(gg => new
{
State = gg.Key,
FirstTwo = events
// how to get this clause from variable
.Where(ee => ee.State == gg.Key)
.Take(2)
})
.ToList();
Next code did not work, the problem is that where expression use parameter from query gg.Key
var events = ctx.Events;
var res = events
.GroupBy(ee => ee.State)
.Select(gg => new
{
State = gg.Key,
FirstTwo = events
// 1
// how to get this clause from variable
//.Where(ee => ee.State == gg.Key)
// 2
// try to take out where expression from query
.Where(_buildExpression(gg.Key))
.Take(2)
})
.ToList();
// method
static Expression<Func<Event, bool>> _buildExpression(string state)
{
return ee => ee.State == state;
}
// exeption
An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.SqlServer.dll
Additional information: variable 'gg' of type 'System.Linq.IGrouping`2[System.String,Entities.Event]' referenced from scope '', but it is not defined
Example of getting where expression from variable, but does not depend on gg.Key (wrong)
Expression<Func<Event, bool>> whereClause = (ee) => (ee.State == "test");
var events = ctx.Events;
var res = events
.GroupBy(ee => ee.State)
.Select(gg => new
{
State = gg.Key,
FirstTwo = events
// 1
// how to get this clause from variable
//.Where(ee => ee.State == gg.Key)
// 2
// try to take out where expression from query
//.Where(_buildExpression(gg.Key))
// 3
// whereClause from variable, but does not depend on gg.Key
.Where(whereClause)
.Take(2)
})
.ToList();
How to take where сlause from variable with depend on gg.Key?
p.s. the query is just example of the problem. The code below does not solve the problem of real query:
var events = ctx.Events;
var res = events
.GroupBy(ee => ee.State)
.Select(gg => new
{
State = gg.Key,
FirstTwo = gg.Take(2)
})
.ToList();

Solution by OP.
Thanks to Ivan Stoev comment.
Expression<Func<Event, string, bool>> whereClause = (ee, state) => (ee.State == state);
var events = ctx.Events;
var res = events
.AsExpandable() // <= add this
.GroupBy(ee => ee.State)
.Select(gg => new
{
State = gg.Key,
FirstTwo = events
.Where(ee => whereClause.Invoke(ee, gg.Key)) // <= Invoke expression
.Take(2)
})
.ToList();
This was made possible by LinqKit

Related

How to EF.Property<T> method works with included queries

I trying write a generic method for create response to datatables ajax request;
public static Response<T> CreateResponse<T>(IQueryable<T> query, Request request) where T : class
{
query = query.AsNoTracking();
var filtered = query;
if (!string.IsNullOrEmpty(request.Search.Value))
{
var keywords = Regex.Split(request.Search.Value, #"\s+").ToList();
request
.Columns
.Where(p => p.Searchable)
.ToList()
.ForEach(c =>
keywords.ForEach(k =>
{
filtered = filtered.Intersect(query.Where(p => EF.Functions.Like(EF.Property<string>(p, c.Name), $"%{k}%")));
})
);
}
var ordered = filtered;
request.Order.ForEach(p => ordered = p.Dir == "asc" ? ordered.OrderBy(q => EF.Property<T>(q, request.Columns[p.Column].Name)) : ordered.OrderByDescending(q => EF.Property<T>(q, request.Columns[p.Column].Name)));
var paged = ordered.Skip(request.Start).Take(request.Length);
return new Response<T> { draw = request.Draw, recordsTotal = query.Count(), recordsFiltered = filtered.Count(), data = paged.ToList() };
}
My problem is, when query parameter is IIncludableQueryable EF.Property method can't locate sub properties. For example;
DataTables.CreateResponse<Rayon>(context.Rayons.Include(p=>p.User), parameters);
EF.Property<T>.Property<string>(p, "Name") is working but, EF.Property<T>.Property<string>(p, "User.Name") is not working, exception message is "EF.Property called with wrong property name."
Sorry for bad English.

Use FirstOrDefaultAsync in query when creating instance

I have the following Entity Framework query:
IQueryable<Unit> units = context.Units;
Product product = new Product {
Conversions = model.Conversions.Select(y => new Conversion {
UnitId = units
.Where(z => z.Name == y.Unit)
.Select(z => z.Unit.Id)
.FirstOrDefault(),
Value = y.Value
}).ToList(),
Name = model.Name
}
I tried to use await before units.Where( ... as:
Product product = new Product {
Conversions = model.Conversions.Select(y => new Conversion {
UnitId = await units
.Where(z => z.Name == y.Unit)
.Select(z => z.Unit.Id)
.FirstOrDefaultAsync(),
Value = y.Value
}).ToList(),
Name = model.Name
}
This is not allowed as it is inside new Conversion ...
Shouldn't I use await? How can I use it in this query?
You can use this approach :
unitId = await Task.Factory.Start<int>(()=>{
return units.Where(z => z.Name == y.Unit)
.Select(z => z.Unit.Id)
.FirstOrDefault();
})
change int to your return type.

Prevent sort result of union in entity framework after select and distinct

Before I asked Prevent sort result of union in entity framework
I got my answer but now I have new problem with this issue. I have this code:
var productExactlyTitle = products.Where(x => x.Title == keyword);
var productStartWithPhrase = products.Where(x => x.Title.StartsWith(keyword));
var productStartWithWord = products.Where(x => x.Title.StartsWith(keyword + " "));
var productContainsWord = products.Where(x => x.Title.Contains(" " + keyword + " "));
var productContainsPhrase = products.Where(x => x.Title.Contains(keyword)
|| x.Title.Contains(keyword)
|| x.SubTitle.Contains(keyword)
|| x.OtherName.Contains(keyword));
var splitWords = keyword.Split(' ');
var productSplitWordSearch = splitWords.Aggregate(products, (current, word) => current.Where(x => x.Title.Contains(word.Trim())));
var p1 = productExactlyTitle.Select(x => new { Item = x, Order = 1 });
var p2 = productStartWithWord.Select(x => new { Item = x, Order = 2 });
var p3 = productStartWithPhrase.Select(x => new { Item = x, Order = 3 });
var p4 = productContainsWord.Select(x => new { Item = x, Order = 4 });
var p5 = productContainsPhrase.Select(x => new { Item = x, Order = 5 });
var p6 = productSplitWordSearch.Select(x => new { Item = x, Order = 6 });
var productList = p1
.Union(p2)
.Union(p3)
.Union(p4)
.Union(p5)
.Union(p6)
.OrderBy(x => x.Order)
.Take(21)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Item.Id,
Title = x.Item.Title,
Price = DiscountController.ApplyDiscountToPrice(x.Item).ToPrice(),
Image = x.Item.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
Result of above code have duplicate records and I have to use select and distinct to remove duplicate records. so I change my code like this:
var productList = p1
.Union(p2)
.Union(p3)
.Union(p4)
.Union(p5)
.Union(p6)
.OrderBy(x => x.Order)
.Select(x => x.Item)
.Distinct()
.Take(21)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Id,
Title = x.Title,
Price = DiscountController.ApplyDiscountToPrice(x).ToPrice(),
Image = x.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
But after that my result is sorted with Id column again.
How can I solved this?
First, since adding Order field to each query makes the record unique, using Union (which is supposed to remove duplicates) doesn't make sense, so simply use Concat instead.
Second, to remove duplicates and not lose the Order field needed for later ordering, you need to group by Item and take the minimum Order for each group. The rest is the same as in the original solution.
var productList = p1
.Concat(p2)
.Concat(p3)
.Concat(p4)
.Concat(p5)
.Concat(p6)
.GroupBy(e => e.Item)
.Select(g => new { Item = g.Key, Order = g.Min(e => e.Order) })
.OrderBy(e => e.Order)
.Select(e => e.Item)
.Take(21)
.AsEnumerable()
...

Prevent sort result of union in entity framework

In SQL server union, result is sorted based on primary key column. I want to prevent this behavior in entity framework.
In this post, #praveen has explained how to do this in pure sql. But I want to do this in entity framework.
My code:
public virtual ActionResult Search(string keyword)
{
var products = _db.Products
.Where(x => x.IsActive)
.AsQueryable();
var productExactlyTitle = products.Where(x => x.Title == keyword);
var productStartTitle = products.Where(x => x.Title.StartsWith(keyword));
var productContainsTitle = products.Where(x => x.Title.Contains(keyword)
|| x.Title.Contains(keyword)
|| x.SubTitle.Contains(keyword)
|| x.OtherName.Contains(keyword));
var productList = productExactlyTitle.Union(productStartTitle)
.Union(productContainsTitle)
.Take(10)
.AsEnumerable()
.Select(x => new ProductItemViewModel()
{
Id = x.Id,
Title = x.Title,
Price = x.Price.ToPrice(),
Image = x.Images.FirstOrDefault(y => y.IsCoverPhoto)?.ImageUrl
});
// some code ...
}
I want to show records with below order:
First: records of productExactlyTitle
Second: records of productStartTitle
Third: records of productContainsTitle
But result is sorted with Id column! and I don't want this.
Is there a way for do this?
In SQL all queries without an order by explicitly set is considered unordered. (and EF queries a translated into SQL). So if you want a specific order after your union just specify it.
var result = q1.Union(q2).OrderBy(x => x.?);
For your specific case:
var p1 = productExactlyTitle.Select(x => new { Item = x, Order = 1 });
var p2 = productStartTitle.Select(x => new { Item = x, Order = 2 });
var p3 = productContainsTitle.Select(x => new { Item = x, Order = 3 });
var productList = p1.Union(p2)
.Union(p3)
.OrderBy(x => x.Order)
.Select(x => x.Item)
.Take(10);

GroupBy SQL to EF Lambda syntax

I'm currently defeated in my attempts to map the following sql to an EF lambda style query:
sql:
SELECT ResellerId, Name, Month, SUM(MonthTotal) AS MonthTotal
FROM dbo.View_ResellerYearMonthBase
WHERE (Year = 2015) AND (EventId > 0) AND (ValidationResponse IS NOT NULL)
GROUP BY ResellerId, Month, Name
I've tried
public JsonResult GetResellerAnnualReportData(int year, bool includeUnValidated, bool includeUnbooked)
{
var qry = _reportsDal.View_ResellerYearMonthBase.AsQueryable();
qry = qry.Where(x => x.Year == year);
if (!includeUnValidated) { qry = qry.Where(x => x.ValidationResponse.Length > 0); }
if (!includeUnbooked) { qry = qry.Where(x => x.EventId > 0); }
qry = qry.GroupBy(x => new { x.ResellerId, x.Month, x.Name }).Select(y => new ResellerAnnualReportDto
{
ResellerId = y.Key.ResellerId,
Month = y.Key.Month.Value,
Name = y.Key.Name,
SumMonthTotal = y.Sum(z => z.MonthTotal.Value)
});
throw new NotImplementedException();//keep the compiler happy for now
}
How should I go about achieving the SQL Query with the function parameters (year, includeUnValidated etc)
.GroupBy(key => new { key.ResellerId, key.Month, key.Name},
el => el.MonthTotal,
(key, el) => new ResellerAnnualReportDto
{
ResellerId = key.ResellerId,
Month = key.Month,
Name = key.Name,
MonthTotal = el.Sum(s => s.MonthTotal)
}).ToList();
This uses the overload with keyselector, elementselector and resultselector. This way you avoid making the IGrouping<key,value> and get the results you want immediately. Couldn't test though.
Here is how to do this:
var result = qry.GroupBy(x => new { x.ResellerId, x.Month, x.Name }).
Select(y => new {
y.Key.ResellerId,
y.Key.Month,
y.Key.Name,
SumMonthTotal = y.Sum(z => z.MonthTotal)
}).ToList();