LINQ to entities comparing collections - c#

I'm having issues with a Linq query that will actually do what I need it to. I've constructed a search that needs to find articles in a database (entity framework) and the search then uses a list of checkboxes to allow the user to only search within certain areas of practice. I've seen posts about linq joins & multiple collections, but my problem is that what I'm trying to select from is the parent table Article and I need to comppare the .Practices to a Practices list I've compiled from the checkbox list.
Before it was a checkbox list, it was a dropdown and this query worked to search for 1 practice:
ar = (from a in db.Articles
from p in a.Practices
where p.ID == practiceID
select a);
Now, I need the where clause to work like an "IN" from regular SQL. My collection of Practices compiled from a checkbox list need to be compared to db.Articles.Practices for my search.
Any suggestions or clarification needed?

Assuming: List practiseIds
ar = (from a in db.Articles
from p in db.Practices
where practiseIds.Contains(p.ID)
select a);
However some clarification is needed:
IN is not a first class supported SQL method in EF so the above code will in fact turn into a list of or's (this may have been resolved in the .net 4 release of EF - http://blogs.msdn.com/b/adonet/archive/2009/08/05/improvements-to-the-generated-sql-in-net-4-0-beta1.aspx)
Psuedo Code -
Select * From db.Articles
inner join db.Practices on <relation>
Where db.Practices.practiceid = #p0 || db.Practices.practiceid = #p1 || etc..

Just pass in the id collection as array practiceIds and use a Contains() query:
ar = (from a in db.Articles
from p in a.Practices
where practiceIds.Contains(p.ID)
select a);

Related

Linq Join when second table will have multiple records

Trying to create a Linq query that can do multiple searches in one command instead of having multiple search result pages. It is working great when I am trying to find multiple records that have a subject (in my case CHIEF_COMPLAINT) and comments with a specific word. The problem is when I want to search with serial numbers.
There are two issues here. One is that multiple pieces of equipment can be attached to a specific ticket and also a single piece of equipment can be associated with multiple tickets. When I query the table used to associate equipment to tickets (VIEW_WT_EQUIP, using the view because it is where the serial number is seen) I potential get multiple results with the same Ticket_ID.
This is the query that I have right now, but it returns no results when I put in a serial number that I know is in the system.
var query = from a in db.VIEW_WT_HEADERs
join c in db.VIEW_WT_EQUIPs on a.TICKET_ID equals c.TICKET_NUMBER into c_group
from c2 in c_group
join b in db.WT_EVENTs on a.TICKET_ID equals b.TICKET_ID
where b.COMMENTS.Contains(input) || a.CHIEF_COMPLAINT.Contains(input) || c2.SERIAL_NUMBER.Contains(input)
orderby a.TICKET_ID descending
select new { a.TICKET_ID, a.ENTRY_DATE, a.CONTACT, a.CHIEF_COMPLAINT, a.STATUS };
I also tried a method where I used 2 linq queries and put all the ticket numbers from a serial number search into a list, but the second query didn't like that I was trying to compare an int array.
I think I am just going about this wrong. Join is probably not the right way to do this, but I don't know how to tell the main query to pull all the tickets associated with a piece of equipment.
Please let me know where I can clarify, because I know this explination is rough.
I would put this as comment instead of an answer, but I want to show you some code, so I had to choose "answer".
If you are using Linq to Entities, you probably have a relationship between the objects. It means that the join is not necessary. You should only use join when no navigation property is available.
I can't tell exactly what you should do, but here is some code the might be helpful:
var query = from a in db.VIEW_WT_HEADERs
from b in a.WT_EVENTs
from c in a.VIEW_WT_EQUIPs
where b.COMMENTS.Contains(input) || a.CHIEF_COMPLAINT.Contains(input) ...
orderby a.TICKET_ID descending
select new { a.TICKET_ID, a.ENTRY_DATE, a.CONTACT, a.CHIEF_COMPLAINT, a.STATUS };
you can also use let to store a sub-expression:
var query = from a in db.VIEW_WT_HEADERs
from b in a.WT_EVENTs
from c in a.VIEW_WT_EQUIPs
let x = c.FirstOrDefault()
where b.COMMENTS.Contains(input) || a.CHIEF_COMPLAINT.Contains(input) || x.SomeProperty ....
orderby a.TICKET_ID descending
select new { a.TICKET_ID, a.ENTRY_DATE, a.CONTACT, a.CHIEF_COMPLAINT, a.STATUS };
Those are just example, maybe it helps!

LINQ left-join Entity Frame work - Unable to create a constant value of type

There are simular questions with answers that do not work in my situation.
I'm getting a
Unable to create a constant value of type '.Model.featureoptions'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
Using Entity First, EntityFramework 4.1, MVC3, C# 4.
vehicles is a table of vehicle details, owners is a table of vehicle owners. vehicles and owners are inner joined and that works.
features table is a list of optional features e.g. sunroof, paint, etc. featureOptions is a list of the available options for a feature. e.g. paint could be 'pearlescent', 'matalic' and sunroof could be 'glass pop-up', 'title + slide'.
vehicleFeatures is a list of chosen options for a vehicle, for a particulare feature a vehicle can have zero or one record.
In this query feature1 should be null or the chosen value for a feature (i.e. the chosen sunroof option) and feature2 should be null or the chosen value for a different feature (i.e. the chosen paint option)
var query = (from v in _entities.vehicles
join o
in _entities.owners
on v.OwnerID equals o.OwnerID
// Some more inner joins
select new
{
// <code snipped >
// o. fields and v. fields
// </ code snipped>
feature1 = (from feature1
in _entities.vehiclefeatures
.Where ( f_1 => f_1.VehicleID == v.VehicleID)
join feature1_fo
in _entities.featureoptions
on feature1.FeatureOptionID equals feature1_fo.FeatureOptionID
join feature1_f
in _entities.features
.Where (bt_f => bt_f.CodeEnum==1)
on feature1_fo.FeatureID equals feature1_f.FeatureID
select new featureoptionsDTO () { Option = feature1_fo.Option }
),
feature2 = (from feature2
in _entities.vehiclefeatures
.Where(f_2 => f_2.VehicleID == v.VehicleID)
join feature2_fo
in _entities.featureoptions
on feature2.FeatureOptionID equals feature2_fo.FeatureOptionID
join feature2_f
in _entities.features
.Where(feature2_f => feature2_f.CodeEnum == 2)
on feature2_fo.FeatureID equals feature2_f.FeatureID
select new featureoptionsDTO() { Option = feature2_fo.Option }
)
}
);
foreach (var vehicle in query) // Exception here
{
}
the
feature1 = (from ..
and
feature2 = (from ..
are causing the
Unable to create a constant value of type '.Model.featureoptions'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.
I understand that LINQ is trying to create an entity, how can I get it to create an anonymous (or own class) instead?
Unfortunately Entity Framework can't handle select clauses which construct arbitrary types in LINQ to Entities queries. I've tripped over this one a few times myself, and it's quite annoying. It is, however, quite necessary as LINQ to Entities queries are translated into SQL to run on the database, and the database can't handle the construction of .NET objects. It might be nice to be able to do it at the end of the query, but it can certainly never be allowed in the middle.
What I tend to do is write a query which produces exactly the input required to the constructors all in LINQ to Entities, so that it runs on the database. Then call ToEnumerable() on the IQueryable you get from that, which turns it into an IEnumerable, and after that you're in LINQ to Objects so you can do whatever you like in your Select().
I resolved this by using a view in the database to do the outer joins and the linq queries the entity assciated with the view.
By doing the left outer in the database means the outer joins are done earlier, possibly making it a bit quicker. The linq is tidier and just has to do what it needs to, which in this case is filtering.

Sort Linq list with one column

I guess it should be really simple, but i cannot find how to do it.
I have a linq query, that selects one column, of type int, and i need it sorted.
var values = (from p in context.Products
where p.LockedSince == null
select Convert.ToInt32(p.SearchColumn3)).Distinct();
values = values.OrderBy(x => x);
SearchColumn3 is op type string, but i only contains integers. So i thought, converting to Int32 and ordering would definitely give me a nice 1,2,3 sorted list of values. But instead, the list stays ordered like it were strings.
199 20 201
Update:
I've done some tests with C# code and LinqPad.
LinqPad generates the following SQL:
SELECT [t2].[value]
FROM (
SELECT DISTINCT [t1].[value]
FROM (
SELECT CONVERT(Int,[t0].[SearchColumn3]) AS [value], [t0].[LockedSince], [t0].[SearchColumn3]
FROM [Product] AS [t0]
) AS [t1]
WHERE ([t1].[LockedSince] IS NULL)
) AS [t2]
ORDER BY [t2].[value]
And my SQL profiler says that my C# code generates this piece of SQL:
SELECT DISTINCT a.[SearchColumn3] AS COL1
FROM [Product] a
WHERE a.[LockedSince] IS NULL
ORDER BY a.[SearchColumn3]
So it look like C# Linq code just omits the Convert.ToInt32.
Can anyone say something useful about this?
[Disclaimer - I work at Telerik]
You can solve this problem with Telerik OpenAccess ORM too. Here is what i would suggest in this case.
var values = (from p in context.Products
where p.LockedSince == null
orderby "cast({0} as integer)".SQL<int>(p.SearchColumn3)
select "cast({0} as integer)".SQL<int>(p.SearchColumn3)).ToList().Distinct();
OpenAccess provides the SQL extension method, which gives you the ability to add some specific sql code to the generated sql statement.
We have started working on improving this behavior.
Thank you for pointing this out.
Regards
Ralph
Same answer as one my other questions, it turns out that the Linq provider i'm using, the one that comes with Telerik OpenAccess ORM does things different than the standard Linq to SQL provider! See the SQL i've posted in my opening post! I totally wasn't expecting something like this, but i seem that the Telerik OpenAccess thing still needs a lot of improvement. So be careful before you start using it. It looks nice, but it has some serious shortcomings.
I can't replicate this problem. But just make sure you're enumerating the collection when you inspect it. How are you checking the result?
values = values.OrderBy(x => x);
foreach (var v in values)
{
Console.WriteLine(v.ToString());
}
Remember, this won't change the order of the records in the database or anywhere else - only the order that you can retrieve them from the values enumeration.
Because your values variable is a result of a Linq expression, so that it doest not really have values until you calling a method such as ToList, ToArray, etc.
Get back to your example, the variable x in OrderBy method, will be treated as p.SearchColumn3 and therefore, it's a string.
To avoid that, you need to let p.SearchColumn3 become integer before OrderBy method.
You should add a let statement in to your code as below:
var values = (from p in context.Products
where p.LockedSince == null
let val = Convert.ToInt32(p.SearchColumn3)
select val).Distinct();
values = values.OrderBy(x => x);
In addition, you can combine order by statement with the first, it will be fine.

Linq to SQL problem

I have a local collection of record Id's (integers).
I need to retrieve records that have every one of their child records' ids in that local collection.
Here is my query:
public List<int> OwnerIds { get; private set; }
...
filteredPatches = from p in filteredPatches
where OwnerIds.All(o => p.PatchesOwners.Select(x => x.OwnerId).Contains(o))
select p;
I am getting this error:
Local sequence cannot be used in Linq to SQL implementation of query operators except the Contains() operator.
I understand that .All() isn't supported by Linq to SQL, but is there a way to do what I am trying to do?
Customers where
OrderIds in the child collection are a subset of the IDs in the in-memory collection.
from c in myDC.Customer
where c.Orders.All(o => myList.Contains(o.ID))
select c;
Customers where
OrderIds in the in-memory collection are a subset of the IDs in the child collection.
from c in myDC.Customers
where (from o in c.Orders
where myList.Contains(o.ID)
group o.ID by o.ID).Distinct().Count() == myList.Count()
select c;
Customers where
OrderIds in the in-memory collection are set-equal to the IDs in the child collection.
from c in myDC.Customers
let Ids = c.Orders.Select(o => o.ID).Distinct()
where Ids.Count() == myList.Count()
&& Ids.All(id => myList.Contains(id))
select c;
All of these generated sql for me.
PS - these presume the IDs are already distinct in myList. If they aren't yet, use:
myList = myList.Distinct().ToList();
PSS - good for lists up to ~2000 items. Higher than that will get translated to sql, and then sql server will barf at the number of parameters.
I don't know of a way to do it with Linq to SQL. The problem is that you need to get your list over to the server so that it can query against it. (your list is in memory on your machine, SQL Server needs to do the filtering on the server)
With straight SQL, you could use a regular SELECT statement with the "in()" operator to do that. (don't go over 1,000 items in the "in")
You could insert all of the ID's into a temp table in SQL, and then join to the table (you could use LINQ with this solution, but it requires 2 steps - the insert (assuming you have a "sets" table), and then the joined query (and then a cleanup query to remove your set).
You could LINQ query without the filter condition and then filter on your in-memory set (not recommended if the unfiltered result set could be large).
what the compiler says is...
OwnerIds.Contains(someVariable)
is supported and it will be translated as:
WHERE someVariable IN (OwnerId1, OwnerId2, OwnerIdN)
now, we don't have all the informations of you query but if you can reformulate what you're trying to do to use Contains, you'll be ok.
Could you do a join OwnerIds?
Error said "Local sequence (means OwnerIds) cannot be used in Linq to SQL implementation of query operators except the Contains() operator."
So you can do:
1) load ALL filteredPatches rows from SQL
var loadedData = filteredPatches.Select(i => i).ToList();
2) filter data as simple local sequence
var result = loadedData.Where(i => i.PatchesOwners.All(o => OwnerIds.Contains(o.ID)));

Linq, emulating joins, and the Include method

I'm looking into an issue that is related to...
Join and Include in Entity Framework
Basically the following query returns the list of "Property" objects the current user has permissions ( ACLs ) to view.
IQueryable<Property> currPropList
= from p in ve.Property
.Include("phyAddress")
.Include("Contact")
from a in ve.ACLs
from u in ve.Account
from gj in ve.ObjectGroupJoin
where u.username == currUsername // The username
&& (a.Account.id == u.id // The ACLs
&& a.objType == (int)ObjectType.Group)
&& (gj.ObjectGroup.id == a.objId // The groups
&& gj.objId == p.id) // The properties
select p;
The query returns the correct list of properties and in large works fine.
But the "Include" calls in the linq query above does not load the objects. If I call "Load()" explicitly after the LINQ query then the objects load.
The related SO question suggested that there may be a conflict between the "Include" call and the where clause. How can that be the case?
But at any rate, how can I restructure this query to load the "phyAddress" and "Contract" members? Specifically, I'd only like to load the members on returned objects, not all the "phyAddress" and "Contact" objects in the database.
Thanks.
Edit
I've tracked down the issue to the use of multiple from clauses
This works...
IQueryable<Property> currPropList
= from p in ve.Property
.Include("phyAddress")
select p;
And the "phyAddress" member is loaded.
But this doesn't work...
IQueryable<Property> currPropList
= from p in ve.Property
.Include("phyAddress")
from a in ve.ACLs
select p;
Basically the Include call is ignored when there are multiple from clauses. Does anyone know of a work around for this?
Edit 2
One workaround is to cast the IQueryable result as a ObjectQuery and get the include off of that. But I would like to prevent the second roundtrip to the database I am assuming this causes.
Eg. This works....
IQueryable<Property> currPropList
= ((from p in ve.Property
from a in ve.ACLs
select p) as ObjectQuery<Property>).Include("phyAddress");
Is there a way to do this with only a single query?
Edit 3
No second query because of deferred execution [ http://blogs.msdn.com/charlie/archive/2007/12/09/deferred-execution.aspx. So edit 2 would be the solution.
This is a known issue with Include... if you do something that changes the shape of the query (i.e. from from) then the Include is lost there are simple enough workarounds though:
you can wrap the include around the query, see Tip 22 - How to make include really include.
or you can get everything you need in the select clause and let relationship fixup do the job for you. i.e.
var x = from p in ve.Property
from a in ve.ACLs
select new {p,p.phyAddress};
var results = x.AsEnumerable().Select(p => p.p);
Now results is an enumeration of property entities, but each one has it's phyAddress loaded, as a side-effect of the initial request for the phyAddress, and EF's relationship fixup.

Categories