Autocomplete extender 'extended' across entire data table - asp.net

I have a request to create an auto complete that will search an data table. Is this achieveable quickly and simply or is it a case of writing a reasonable amount of code?
Originally, I have been using a webservice and linq to point at a single column's worth of data (IDDesc) and pull back the list of products:
Product.FinalProductsDataContext dbac = new Product.FinalProductsDataContext();
return dbac.tblProduct
.Where(r => r.Account== HttpContext.Current.Session["AccountKey"].ToString() && r.IDDesc.Contains(prefixText))
.Distinct()
.OrderBy(r => r.IDDesc)
.Select(r => r.IDDesc)
.Take(count)
.ToArray();
However, if I wish the autocomplete to look at all the columns, is it a case of repeating similar LINQ statements for each of the columns contained within the datatable or is there a 'quick fix'?
I personally don't think this is an ideal scenario but it is a request I must work towards.
Any help or advice, greatly appreciated.

Rather than trying to solve this entirely with LINQ (and repeating all those statements for each column in the table, as well as hitting the database repeatedly), I think I'd look to put something in the database to do the heavy lifting here.
You could create a view that takes the fields from the table and amalgamates them into one column e.g.
CREATE VIEW [dbo].[ProductView]
AS
SELECT CAST(ProductName AS NVARCHAR(50)) AS 'ProductColumn'
FROM dbo.Products
UNION
SELECT CAST(SupplierName AS NVARCHAR(50))
FROM dbo.Products
UNION
...
which if you added the view to your context would then allow you to modify your existing LINQ query and point it at that view e.g.:
Product.FinalProductsDataContext dbac = new Product.FinalProductsDataContext();
return dbac.ProductView
.Where(r => r.Account== HttpContext.Current.Session["AccountKey"].ToString() && r.ProductColumn.Contains(prefixText))
.Distinct()
.OrderBy(r => r.ProductColumn)
.Select(r => r.ProductColumn)
.Take(count)
.ToArray();

Related

Deleting records from look up table not working in .NET Core 6

The bounty expires in 1 hour. Answers to this question are eligible for a +100 reputation bounty.
eia92 wants to draw more attention to this question.
I recently upgraded my project to .NET Core 6 and now removing records from my look up tables is not working. I have a Risk object that has a collection of Users. Removing users from the risk object no longer works. Any ideas what I'm doing wrong?
My lookup table is called RiskItemUser, and it has two columns, RiskItemId and UserId.
Code:
var postSavedRisk = _riskService.Queryable().Include(c => c.AssignedTo).Where(w => w.Id == riskitem.Id).FirstOrDefault();
List<User> usersToRemove = postSavedRisk.AssignedTo.Where(c => userNamesToRemove.Contains(c.UserName)).ToList();
using (var db = new ApplicationDbContext())
{
var postSavedAssginedTo = db.RiskItemUser
.Where(w => w.RiskItemId == riskitem.Id)
.ToList();
foreach (var userToRemove in usersToRemove)
{
foreach (var riskAssignedTo in postSavedAssginedTo)
{
if(userToRemove.Id == riskAssignedTo.UserId)
db.RiskItemUser.Remove(riskAssignedTo);
await db.SaveChangesAsync().ConfigureAwait(false);
}
}
}
The code, as you show it, looks like it should work, although some parts are hidden. Therefore, it's hard to tell how to make it work. But there's room for simplification, which should result in working code.
You want to remove users whose names are specified by userNamesToRemove from a risk that's specified by riskitem.Id. Assuming that there's a navigation property RiskItemUser.User, removing these data could be done by essentially one line of code:
db.RiskItemUser.RemoveRange(
db.RiskItemUser.Where(ru => ru.RiskItemId == riskitem.Id
&& userNamesToRemove.Contains(ru.User.Name)));
await db.SaveChangesAsync().ConfigureAwait(false);
You tagged EFC 6, but as of EFC 7.0, there's support for bulk delete (and update) functions, allowing for single-statement deletion of multiple database records:
db.RiskItemUser
.Where(db.RiskItemUser.Where(ru => ru.RiskItemId == riskitem.Id
&& userNamesToRemove.Contains(ru.User.Name)))
.ExecuteDelete();
This will execute one delete statement, whereas the previous method will execute one statement per row.
Note that this bulk method is like executing raw SQL. There's no communication with EF's change tracker and EF can't coordinate the correct order of statements. I think the general advice should be to not mix these bulk methods with regular SaveChanges calls.

Why EF core executes n query for n+1 query

I am trying to get Departments with last DepartmentLog, but ef core executes n+1 query. And works so slow. I have 10 000 rows on Department table and 12 000 rows on DepartmentLogs. I am not sure it must work 1.5s - 2s for executing like this query.
var query2 = _unitOfWork.Departments.GetDbSet()
.Include(p => p.CreatedUser)
.ThenInclude(p => p.Employee)
.OrderByDescending(p => p.CreatedAt)
.Select(p => new
{
Department = p,
DepartmentLog = _unitOfWork.DepartmentLogs.GetDbSet()
.Include(m => m.CreatedUser)
.ThenInclude(m => m.Employee)
.OrderByDescending(m => m.CreatedAt)
.FirstOrDefault(m => m.DepartmentId == p.Id)
})
.Take(10).ToList();
The lambda you're passing to Select is executed for each item in the result set. Since you're making a query in that lambda, you're going to have N queries issued, one for each item in the result set.
The reason it only generates one extra query when you remove FirstOrDefault, is that you're returning the all the department logs, which then allows EF to add them to the object cache, where it can pull them out from later, instead of issuing a new query. With FirstOrDefault, you're only returning one, so EF caches that, but on the next run of the lambda, you're selecting a different one that it doesn't have cached.
To use FirstOrDefault and still have just one extra query, you should run this query first:
var departmentLogs = await _unitOfWork.DepartmentLogs.GetDbSet()
.Include(m => m.CreatedUser)
.ThenInclude(m => m.Employee)
.OrderByDescending(m => m.CreatedAt)
.ToListAsync();
Then, in your Select:
.Select(p => new
{
Department = p,
DepartmentLog = departmentLogs.FirstOrDefault(m => m.DepartmentId == p.Id)
})
Then, it will run the FirstOrDefault on the result set already in memory. However, this requires returning all the logs, which could be more problematic performance-wise, if there's a ton of them. You'll have to profile each way and see which ends up being better in your particular circumstance.
Alternatively, a probably preferably, you can simply create this query as a stored procedure, and then just call the stored procedure to get your results, instead of using LINQ to build a query. There's only so much EF can do to optimize the query, and this is a highly inefficient query, no matter what you do.

Linq Query Distinct Function do not work with DropdownList

I want to assign Linq Query result to dropdownlist which contain a
Distinct function
My Code:-
var area = de.City_Area_View
.Select(m => new { m.Area_Id, m.Area_Name})
.Distinct()
.ToList();
drpfilter.DataTextField = "Area_Name";
drpfilter.DataValueField = "Area_Id";
drpfilter.DataSource = area;
drpfilter.DataBind();
Problem :- When I write this code then I get Below Error
Error:- The method 'Distinct' is not supported.
I get System.NotSupportedException.
I want to assign a Distinct name of area to the DropDownList
So please help me for this problem.
If your set is small enough (so you don't mind fetching all the values from the database), the simplest thing would be to force the distinct part to be performed locally:
var area = de.City_Area_View
.Select(m => new { m.Area_Id, m.Area_Name})
.AsEnumerable()
.Distinct()
.ToList();
AsEnumerable simply "changes" the expression type to IEnumerable<T> instead of IQueryable<T>, so that the compiler calls Enumerable.Distinct instead of Queryable.Distinct - and Enumerable.Distict will definitely work.

Filter Products in a Dynamic Way using LINQ

After hours of trying and searching, I think its time to share my problem with you right now.
Problem Definition :
I have a Dictionary of KeyValuePairs(named filterPool) which includes an integer (PropertyID) and a string(McValue). What I am trying to do is filtering products depending on those KeyValuePairs and return them as a DataTable/List.
You may consider this as building dynamic "Where ... And .." clauses as SQL.
Here is the code that I am using :
foreach (KeyValuePair<int, string> filter in filterPool)
{
products = products.Where(i => i.PROPERTYID == filter.Key && i.MCVALUE.Equals(filter.Value));
}
return products.ToDataTable();
The problem is the foreach loop above seems to work only once, for the latest KeyValuePair available in the Dictionary.
As far as I could find on Stackoverflow, the closest solution to my problem was : this one, also using a Dictionary of values for filtering
There must be a way to achieve the goal of filtering using Dictionary and LINQ; or there's a huge thing that I am missing/ignoring to see somehow.
Hope the problem given is clear enough for all,
Thanks
^^
This is a closure issue. You can solve it by making a temporary:
foreach (KeyValuePair<int, string> filterTmp in filterPool)
{
var filter = filterTmp; // Make a temporary
products = products.Where(i => i.PROPERTYID == filter.Key && i.MCVALUE.Equals(filter.Value));
}
return products.ToDataTable();
For details on what's happening, see Eric Lippert's post Closing over the loop variable considered harmful.
Also note that this behavior has changed for C# 5. In C# 5/VS2012, this code would work as expected.
You're overwriting your products collection on every iteration of your foreach. I'm not sure what the data type on your collection is, but you'll want to do something like this in your foreach instead:
products.AddRange(products.Where(i => i.PROPERTYID == filter.Key && i.MCVALUE.Equals(filter.Value)));
I'm not sure if that makes sense, but it seems like you're trying to create a collection full of products that match your filterPool.
I think that it's better solved with aggregate:
return filter
.Aggregate(products, (acc, filter) => acc.Where(i => i.PROPERTYID == filter.Key && i.MCVALUE.Equals(filter.Value)));
.ToDataTable();

Linq query returning same row 12 times

Hi i have written one linq query to fetch records from entity model. I am getting perfect number of records but all are same.
here is my query
Entities.TEST.Where(a => a.ID.ToUpper().Equals(ID.ToUpper())).OrderBy(s => s.NAME).ToList();
Am I missing something?
You need to make sure your Entity Key in your Entity Data Model is unique.
So in your example, ID should be the entity key for your Test entity
Your query should work, i have a similar sample that works for northwind DB:
var ctx = new NorthwindEntities();
var emp = ctx.Employees.Where(e => e.TitleOfCourtesy.Equals("ms.", StringComparison.OrdinalIgnoreCase)).OrderBy(n => n.FirstName).ToList();
Please check your query in LinqPad. You will see the results and the generated SQL.
Replace Equals with == and you can go

Resources