I have the following structure that I wan't to query using Linq, specifically Linq to Entities (Enitity Framework).
Table1: RouteMeta
Table2: SitePage
Multiple SitePages can link to the same RouteMeta.
I'm querying the Route Meta to select a number of rows. I'm using a generic repository, currently like this:
return r.Find().ToList();
There's nothing special about it - the Find method accepts an optional linq expression, so I could do something like this:
return r.Find(x => x.Status=1).ToList();
However, what I actually want to do is to select rows from RouteMeta where at least one linked row exists in SitePages with a property IsPublished = true.
return r.Find(x => x.SitePages("where y => y.IsPublished = true");
Obviously, the above isn't correct, I'm just trying to explain the scenario better.
Any advice appreciated.
try something like
return r.Find(x=>x.Sitepages.Any(y=>y.Published))?
I'd also suggesting using a profiler if possible to check that this translates properly into SQL. It probably should do but it depends on how your repository works.
Related
I have a mvc project which I use linq in it.
In my database there is some records, for example "Someth ing","SOmeTH ing","someTh ing","SOMETH ING","someTH ING"
I want to do this:
SELECT * FROM dbo.doc_dt_records WHERE name LIKE '%' + #records.Name + '%'
However if I run this code, list.Count returns 0. What should I do?
records.Name = "someth ing"; //for example
var rec = db.Records.ToList();
var lists = rec.Where(p => p.Name.Contains(records.Name)).ToList();
if (lists.Count > 0)
{
// do sthng
}
Thanks for your helps...
the easy way is to use ToLower() method
var lists = rec.Where(p => p.Name.ToLower().Contains(records.Name.ToLower())).ToList();
a better solution (based on this post: Case insensitive 'Contains(string)')
var lists = rec.Where(p =>
CultureInfo.CurrentCulture.CompareInfo.IndexOf
(p.Name, records.Name, CompareOptions.IgnoreCase) >= 0).ToList();
That is totally not a LINQ issue.
Case sensitiivty on the generated SQL depends on the collation relevant for the table. Which in your case likely is case insensitive.
You would get the same result from any SQL you emit.
use IndexOf and StringComparison.OrdinalIgnoreCase:
p.Name.IndexOf(records.Name, StringComparison.OrdinalIgnoreCase) >= 0;
You can create an extension function like this:
public static bool Contains(this string src, string toCheck, StringComparison comp)
{
return src.IndexOf(toCheck, comp) >= 0;
}
To my understanding, this question does not have an unambiguous answer. The matter is that the best way of doing this depends on details which aren't provided in the question. For instance, what exact ORM do you use and what precise DB server you are connected to. For example, if you use Entity Framework against MS SQL Server, you better do not touch your LINQ expression at all. All you need to do is to set the case-insensitive collation on the database/table/column you compare your string with. That will do the trick much better than any change of your LINQ expression. The matter is that when LINQ is translated to SQL, it better be the straight comparison of the column having case-insensitive collation to your string than anything else. Just because it usually works quicker and it is the natural way to do the trick.
You do not want the final query to be something like:
SELECT *
FROM AspNetUsers U
WHERE UPPER(U.Name) LIKE '%SOMETHING%';
It is much better to come up with something like:
SELECT *
FROM AspNetUsers U
WHERE U.Name LIKE '%SOMETHING%';
But with a case-insensitive collation of [Name] column. The difference is that if you have let's say index containing [Name] column, the second query might use it, the first one would do the full scan of the table anyway.
So if let's say records references to DBSet<T> and the record is just one object of type T. You code would be like this:
var lists = records.Where(p => p.Name.Contains(record.Name)).ToList();
And you do the rest on SQL-server. Or if all you need to know is there any value in the list and do not need these values, it would be even better to do like this:
if (records.Any(p => p.Name.Contains(record.Name)))
{
// do something
}
Generally speaking, if you use any sort of ORM connected to any sort of SQL server, you better do case-insensitivity by setting up appropriate parameters of your server/database/table/column. And only if it is impossible or by far too expensive, you consider other possibilities. Otherwise, you might bang into some unexpected and very unpleasant behaviour. For instance, Entity Framework Core 2.x if it cannot translate your LINQ expression straightway into SQL query, is doing different tricks replacing server-side operations with client-side ones. So you can end up with a solution which fetches all data from the table to the client and filter it there. It might be quite a problem if your table is big enough.
As for the situation when LINQ query is processed locally, there are a lot of ways to do the trick. My favourite one is the next:
var lists = records.Where(p => p.Name
.Contains(record.Name, StringComparison.InvariantCultureIgnoreCase))
.ToList();
try this
var lists = rec.Where(p => String.Equals(p.Name,records.Name,StringComparison.OrdinalIgnoreCase)).ToList();
refer here for documentation
I have executed a linq query by using Entityframework like below
GroupMaster getGroup = null;
getGroup = DataContext.Groups.FirstOrDefault(item => keyword.IndexOf(item.Keywords,StringComparison.OrdinalIgnoreCase)>=0 && item.IsEnabled)
when executing this method I got exception like below
LINQ to Entities does not recognize the method 'Int32 IndexOf(System.String, System.StringComparison)' method, and this
method cannot be translated into a store expression.
Contains() method by default case sensitive so again I need to convert to lower.Is there any method for checking a string match other than the contains method and is there any method to solve the indexOf method issue?
The IndexOf method Of string class will not recognized by Entity Framework, Please replace this function with SQLfunction or Canonical functions
You can also take help from here or maybe here
You can use below code sample:
DataContext.Groups.FirstOrDefault(item =>
System.Data.Objects.SqlClient.SqlFunctions.CharIndex(item.Keywords, keyword).Value >=0 && item.IsEnabled)
You really only have four options here.
Change the collation of the database globally. This can be done in several ways, a simple google search should reveal them.
Change the collation of individual tables or columns.
Use a stored procedure and specify the COLATE statement on your query
perform a query and return a large set of results, then filter in memory using Linq to Objects.
number 4 is not a good option unless your result set is pretty small. #3 is good if you can't change the database (but you can't use Linq with it).
numbers 1 and 2 are choices you need to make about your data model as a whole, or if you only want to do it on specific fields.
Changing the Servers collation:
http://technet.microsoft.com/en-us/library/ms179254.aspx
Changing the Database Collation:
http://technet.microsoft.com/en-us/library/ms179254.aspx
Changing the Columns Collation:
http://technet.microsoft.com/en-us/library/ms190920(v=sql.105).aspx
Using the Collate statement in a stored proc:
http://technet.microsoft.com/en-us/library/ms184391.aspx
Instead you can use this method below for lowering the cases:
var lowerCaseItem = item.ToLower();
If your item is of type string. Then this might get you through that exception.
Erik Funkenbush' answer is perfectly valid when looking at it like a database problem. But I get the feeling that you need a better structure for keeping data regarding keywords if you want to traverse them efficiently.
Note that this answer isn't intended to be better, it is intended to fix the problem in your data model rather than making the environment adapt to the current (apparently flawed, since there is an issue) data model you have.
My main suggestion, regardless of time constraint (I realize this isn't the easiest fix) would be to add a separate table for the keywords (with a many-to-many relationship with its related classes).
[GROUPS] * ------- * [KEYWORD]
This should allow for you to search for the keyword, and only then retrieve the items that have that keyword related to it (based on ID rather than a compound string).
int? keywordID = DataContext.Keywords.Where(x => x.Name == keywordFilter).Select(x => x.Id).FirstOrDefault();
if(keywordID != null)
{
getGroup = DataContext.Groups.FirstOrDefault(group => group.Keywords.Any(kw => kw.Id == keywordID));
}
But I can understand completely if this type of fix is not possible anymore in the current project. I wanted to mention it though, in case anyone in the future stumbles on this question and still has the option for improving the data structure.
I have a problem with linq..
lets see the code
I have an article class:
public calss Article
{
public string Tag{get; set; }
}
I save each tag for article splitted by , comma.
for example : first,second,third
and when I want to get an article I want to get articles that has any common tag.
I use this query but:
var relatedArticles =
_db.Articles.Where(a => a.Tag
.Split('،')
.Any(t => article.Tag
.Split('،')
.Any(ac => ac == t)));
and I am getting this exception:
LINQ to Entities does not recognize the method 'System.String[]
Split(Char[])' method
Any other way?
Update:
i cant keep tags in different table because i must let user to create tags as many as he wants when he/she is inserting article.
like 50 and it will be overhead to check if that tag exists or not when saving article to db.
Set your class as follows:
public class Article
{
public List<string> Tag{get; set; }
}
I'm not 100% sure what your 2nd statement does, but you can use a.Tag.Contains() to check your values.
I think my comment could be worth as answer to your problem, so i write it down as one :)
You should think about your table / class design.
You need to normalize it because that looks like n:m reference. Keep the article in one table with a reference to a mapping table, where you reference the arcticleId and the tagId and than one table with that tags with a primary key tagId.
If one Tag will change in future, you don't need to update every article, you just update that particular tag and it changes for every article.
The "a.Tag.Split('،')" is a node in the expression tree that your IQueryable is, not an actual operation. When you materialize results by calling something like ToList, the whole expression tree is translated into SQL expression before execution - this is the point of error because translator doesnt have an idea how to convert Split() method into sql statement.
As an alternative, you can retrieve all results into app, materialize them by ToList() and then do what you want with them as IEnumerables. Also, you can write a stored procedure and pass search tags array into there.
Also, maybe it will work - try to pass a simple values array (not as methods) into query so resulting sql looks like
"WHERE ... IN ...".
it means that Linq to entities failed to find translation of split method that can be written as SQL query. if you want to perform split functions you have to bring the record in memory by calling ToList(), AsEnumerable() etc.
But better approach would be to create separate table for tags in your db.
Linq query would look something like this(supposing many-to-many relationship between articles and tags):
var relatedArticles =
_db.Articles.Where(a => a.Tags.Any(t => t.Articles.Count() > 1));
// if Article has Tag which is assigned more then to one Article then it suits us
You can't call regular methods like string.Split() directly in Linq when working with EF, since it can't be translated to SQL.
You could append AsEnumerable() to your Where() clause to cause Linq to fetch the data, allowing you to perform operations like that on it later. Unfortunately, that will not permit you to do what you want with the list without fetching the whole list, which I'm sure you would rather avoid.
Perhaps you could do something like this instead?:
List<string> tagsForCurrentArticle = ... // fetch this first somehow
_db.Articles.Where(a =>
tagsForCurrentArticle.Any(tag =>
a.Tag.Contains(tag)))
Note, just to be clear: This should work, but the better option, if possible, would be to move your tags out into a separate table, as others have suggested.
I have a larger/more complex problem, but for simplicity sake, let us consider the following:
Let us say that I have table in the SQL DataBase called Product, having two columns, ID (int, primary key) and Name (varchar/string). I also have a simple LINQ DataContext.
I have a query constructed and handed to “my” function. Let us say it is something like: (though it may be a bit more complex)
IQueryable<Product> query = from p in db.Products select p;
Once my method gets this query, passed in as a parameter, it has to change the sort order e.g.
IQueryable<Product> sortedQuery = query.OrderBy(x => x.Name);
I would like to make this more generic i.e. to specify the field to sort on. Normally, I can do a switch statement that takes a string. However I would like to know if there is a way to pass the parameter directly. I intend to extend this to other Database tables, so these switch statements would get tedious.
I was trying something like:
IQueryable<Product> sortedQuery = query.OrderBy(x => (typeof(Product)).GetProperty(“Name”));
But this does not work. I also want to ensure that the LINQ to SQL is maintained i.e. the sort to be done on the SQL Server. Hence if I debug, I should get a SQL query from this LINQ query.
Thank you in advance for your help.
You could use Dynamic Linq for this purpose.
See here Dynamic LINQ (Part 1: Using the LINQ Dynamic Query Library)
Then you can make calls like this:
var query = DBContext.Users.Where( "Age > 3" ).OrderBy( "Name asc" );
Try this out instead:
query.OrderBy(x => x.GetType().GetProperty(“Name”).GetValue(x, null));
You can't just grab the property. You need to grab the value off of that property, hence the call to GetValue.
It is not as easy as it seems. The LINQ to SQL engine parses the expression you pass to the OrderBy method in order to obtain the name of the property you are referencing, then uses this information to compose a plain SQL order by clause.
I guess that maybe it can be done by using reflection, anyway. Maybe you can get something useful from the accepted answer of this SO question.
I'm fairly new to Linq and struggling using dynamic where over a many to many relationship.
Database tables are like so:
Products <-> Products_SubCategories <-> SubCategories
with Products_SubCategories being a link table.
My full linq statement is
db.Products.Where("it.SubCategories.SubCategoryID = 2")
.Include("SubCategories")
.OrderBy(searchOrderBy)
.Skip(currentPage * pageSize)
.Take(pageSize)
.ToList()
.ForEach(p => AddResultItem(items, p));
So ignoring everything bar the Where() I'm just trying to pull out all products which are linked to sub category ID 2, this fails with
To extract properties out of collections, you must use a sub-query to iterate over the collection., near multipart identifier, line 8, column 1.
I think using the SQL-esque syntax I can do a subquery as per this link. However I'm not sure how to do that in the lambda / chaining syntax.
This is the start of a search function and I would like to build up the where string dynamically, as I have with the searchOrderBy string to avoid a large SELECT CASE. Products is linked to another table via a link table that I will need to include once I understand how to do this example.
Any help would be much appreciated!
Thanks
This is wrong:
db.Products.Where("it.SubCategories.SubCategoryID = 2")
SubCategories is a list. It does not have a property called SubCategoryID. Rather, it contains a group of entities which each have a property called SubCategoryID. That's a critical distinction.
When you run into a situation where you don't know how to proceed in there are multiple problems, it is good to break the problem down into several, smaller problems.
Let's start by removing the dynamic query. It will be easier to solve the problem with a non-dynamic query. Once you've done that, you can go back and make it dynamic again.
So start by using the non-dynamic syntax. Type something like this in Visual Studio, and see what IntelliSense does for you:
db.Products.Where(p => p.SubCategories.
You will quickly see that there is no SubCategoryID property. Instead, you will see a bunch of LINQ API methods for working with lists. If you know LINQ well, you will recognize that the Any() method is what you want here:
db.Products.Where(p => p.SubCategories.Any(sc => sc.SubCategoryID == 2))
Go ahead and run that query. Does it work? If so, you can move ahead to making it dynamic. I'm no ESQL expert, but I'd start with something along the lines of:
db.Products.Where("EXISTS(SELECT SC FROM it.SubCategories AS SC WHERE SC.SubCategoryID = 2");
As an aside, I use MS Dynamic Query ("Dynamic LINQ") for this sort of thing rather than Query Builder, as it's more testable.
It worked for me.
db.Products.Where("SubCategories.Any(SubCategoryID = 2)")