NHibernate Linq : Contains Statement - c#

As an example, let's say you have a class like such
**Person**
int PersonID
string PersonName
BusinessLocation Locations
**BusinessLocation**
string city
string state
List<int> ZipCodes
(saying that the locations may exist in multiple zipcodes)
(also ignoring that zipcodes should be strings instead of ints, this is just an example)
Say that the locations of the Businesses exist in multiple zipcodes.
Now I am trying to pull back all the people in the person table, given a business zipcode.
For example, I want all the people who have a zipcode of 32567.
(Given a list of IDs, this works, I am trying to do the opposite, given one ID, I want a list of people)
public Person GetPersonsByBusinessZipCode(int zipcode)
{
List<Person> personList =
this.GetAllQueryable().Where(
x => x.Locations.ZipCodes.Contains(zipcode)).ToList();
}
This is Mapped Like such in Fluent.
HasMany<int>(x => x.ZipCodes)
.Table("BusinessLocationsZipCodes")
.KeyColumns.Add("BusinessLocationID")
.Inverse()
.Element("ZipCode")
.AsBag()
.Cascade.None()
.Cache.ReadOnly();
BusinessLocationZipCodes is just a reference table alluding that a BusinessLocation can have multiple ZipCodes, hence the HasMany.
Knowing that the reverse works, if I am given a list of ZipCodes and I am trying to find BusinessLocations contained in the list of zipcodes works (as long as the mapping is to a zipcode and not a List of zipcodes). Now I'm just trying to find the BusinessLocations given a zipcode.
If anyone has an answer, I would appreciate it.

Paco, apparently I screwed up because I had two accounts floating out here that I did not know about, so this is my OpenID one. Anyhow I am not sure of the NullReferenceException, it dives down into NHibernate namespaces with no link back to any of the classes I am working with. But I only get the NullReferenceException when I try to dive into it using Contains. As I state above, I do not get the NullReferenceException if I am just bring back the list of ZipCodes. So my mapping works, but it can not seem to return me a List of BusinessLocations based on the Contains statement.

Linq provider shouldn't matter much as long as you're using System.Linq.Queryable and the IQueryable interface.
I believe you are looking for the Queryable.Any method.
Persons.Where(p => p.Locations.ZipCodes.Any(z => z == zipCode))
Or, if your Persons have many Locations:
IQueryable<Person> query =
from p in Persons
where
(
from l in p.Locations
from z in l.ZipCodes
select z
).Any(z => z == zipCode)
select p;

What's the exception? That should work correctly using the latest NHibernate build (2.1.2.400).
// The following query: (never mind the Spanish)
var transporte = // some entity;
var solicitud = IQueryable<Solicitud>
.Where(x => x.SolicitudesDeTransporte.Contains(transporte)).ToList();
Generates:
SELECT this_.Id as Id6_0_,
/* etc... */
FROM Solicitud this_
WHERE this_.Id in (SELECT this_0_.Id as y0_
FROM Solicitud this_0_
left outer join Solicitud_Transporte solicitude1_
on this_0_.Id = solicitude1_.Id_Solicitud
WHERE solicitude1_.Id = 1 /* :p0 */)

Related

Multiple joins with multiple on statements using Linq Lambda expressions [duplicate]

Suppose I have a list of {City, State}. It originally came from the database, and I have LocationID, but by now I loaded it into memory. Suppose I also have a table of fast food restaurants that has City and State as part of the record. I need to get a list of establishments that match city and state.
NOTE: I try to describe a simplified scenario; my business domain is completely different.
I came up with the following LINQ solution:
var establishments = from r in restaurants
from l in locations
where l.LocationId == id &&
l.City == r.City &&
l.State == r.State
select r
and I feel there must be something better. For starters, I already have City/State in memory - so to go back to the database only to have a join seems very inefficient. I am looking for some way to say {r.City, r.State} match Any(MyList) where MyList is my collection of City/State.
UPDATE
I tried to update based on suggestion below:
List<CityState> myCityStates = ...;
var establishments =
from r in restaurants
join l in myCityStates
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
and I got the following compile error:
Error CS1941 The type of one of the expressions in the join clause is incorrect. Type inference failed in the call to 'Join'.
UPDATE 2
Compiler didn't like anonymous class in the join. I made it explicit and it stopped complaining. I'll see if it actually works in the morning...
It seems to me that you need this:
var establishments =
from r in restaurants
join l in locations.Where(x => x.LocationId == id)
on new { r.City, r.State } equals new { l.City, l.State } into gls
select r;
Well, there isn't a lot more that you can do, as long as you rely on a table lookup, the only thing you can do to speed up things is to put an index on City and State.
The linq statement has to translate into a valid SQL Statement, where "Any" would translate to something like :
SELECT * FROM Restaurants where City in ('...all cities')
I dont know if other ORM's give better performance for these types of scenarios that EF, but it might be worth investigating. EF has never had a rumor for being fast on reads.
Edit: You can also do this:
List<string> names = new List { "John", "Max", "Pete" };
bool has = customers.Any(cus => names.Contains(cus.FirstName));
this will produce the necessary IN('value1', 'value2' ...) functionality that you were looking for

Linq and RESTful services: how to best merge data from multiple tables in a resultset

I'm experimenting with pulling data from multiple datasets using RESTful services. I'm hooking up to the Cloud version of Northwind, and attempting to use Linq to get the equivalent of this:
SELECT TOP 20 p.ProductName, p.ProductID, s.SupplierID, s.CompanyName AS Supplier,
s.ContactName, s.ContactTitle, s.Phone
FROM Products p
JOIN Suppliers s on p.SupplierID = s.SupplierID
ORDER BY ProductName
So, I define a class to hold my data:
public class ProductSuppliers
{
public string ProductName;
public int ProductID;
public string SupplierName;
public string ContactName;
public string ContactPosition;
public string ContactPhone;
}
And hook into the Northwind service:
NorthwindEntities dc = new NorthwindEntities (new
Uri("http://services.odata.org/Northwind/Northwind.svc/"));
After trying to set up a join, not being able to get it to work, and wandering around in the back corridors of MSDN for a while, I find that Linq joins aren't supported by the OData spec. Which seems obvious once you think about it, given the limitations of URI syntax.
Of course, the usual thing to do is stored procs and views on the server side anyway, handling any sort of joins there. However, I wanted to work out some sort of solution for a situation like this one, where you don't have the capability of creating stored procs or views.
My naive solution has all the elegance of medieval battlefield surgery, and it has to scale horribly. I pulled the two tables as two separate List objects, then iterated one, used Find to locate the matching ID in the other, and Added a combined record into my Product. Here's the code:
public List<ProductSuppliers> GetProductSuppliers()
{
var result = new List<ProductSuppliers>();
ProductSuppliers ps;
var prods =
(
from p in dc.Products
orderby p.ProductName
select p
).ToList();
var sups =
(
from s in dc.Suppliers
select s
).ToList();
foreach (var p in prods)
{
int cIndex = sups.IndexOf(sups.Find(x => x.SupplierID == p.SupplierID));
ps = new ProductSuppliers()
{
ProductName = p.ProductName,
ProductID = p.ProductID,
SupplierName = sups[cIndex].CompanyName,
ContactName = sups[cIndex].ContactName,
ContactPosition = sups[cIndex].ContactTitle,
ContactPhone = sups[cIndex].Phone
};
result.Add(ps);
}
return result;
}
There has to be something better than this, doesn't there? Is there something obvious I'm missing?
[Edit] I've looked at the link someone gave me on the Expand method, and that works...sort of. Here's the code change:
var sups =
(
from s in dc.Suppliers.Expand("Products")
select s
).ToList();
This gives me a list of Suppliers with Products for each in a sublist (dc.Suppliers[0].Products[0], etc.). While I could get what I want from there, I'd still have to iterate the entire list to invert the values (wouldn't I?), so it doesn't look like a more scaleable solution. Also, I can't apply Expand to the Products table to include Suppliers (Changing the from clause in prods to from p in dc.Products.Expand("Suppliers") results in a helpful "An Error occurred while processing this request."). So, it doesn't look like I can expand products to include lookup values from Suppliers, since it looks like expanding is expanding parents to include children, not looking up parent values in a list of children. Is there a way to use Expand (or is there some other mechanism besides client-side manipulation of the two tables) to include lookup values from a foreign key table?
The best you can do is described in this SO answer to a similar question. Not what you expected either, since you're required to make multiple roundtrips to the service.
If you don't control the server-side of things (or you don't want to use SPs/views/joins there) you are forced to use one of these mechanisms.
Anyway, at the very least you can improve the products-suppliers matching in your code to this:
var results = from p in prods
join s in sups on s.SupplierId equals p.SupplierId
select new ProductSuppliers()
{
ProductName = p.ProductName,
ProductID = p.ProductID,
SupplierName = s.CompanyName,
ContactName = s.ContactName,
ContactPosition = s.ContactTitle,
ContactPhone = s.Phone
};
You still need to retrieve all records and join in-memory, though.

Dapper multi-mapping - collections are empty

I'm attempting to use Dapper to return a set of Shares and an associated one-to-many collection of ShareItems and ShareHistories. My Dapper call looks like this:
string sql =
#"select s.Id, s.UserId, s.Name, si.ShareId as Id, si.Name as ItemName
, sh.ShareId As Id, sh.DateShared, sh.SentTo
from Shares s
inner join ShareItems si on s.Id = si.ShareId
inner join ShareHistory sh on s.Id = sh.ShareId
where s.Id = #shareId";
return conn.Query<Share, List<ShareItem>, List<ShareHistory>, Share>(
sql,
(share, shareItems, history) =>
{
share.Items = shareItems;
share.History = history; return share;
},
new { shareId = shareId }).Single();
When I run the query in SQL I get the flattened data I expect. However, when I run the code through Dapper the Items and History collections are coming back empty. I was screwing around with the splitOn parameter but after reading this question I now understand what splitOn is doing (this would be good to have somewhere on the Dapper site btw) and I think I'm handling that part okay. So what am I doing wrong?
I don't think you can populate a deep object graph from 1 row. (Unless all items are in that one row) There's a similar question:
Populating a list in a object with dapper
Edit: There's also QueryMultiple - you might want to check that out. It allows the return of multiple resultsets. You could then map your entities.
Query Multiple Example

LINQ Many to Many With In or Contains Clause (and a twist)

I have a many to many table structure called PropertyPets. It contains a dual primary key consisting of a PropertyID (from a Property table) and one or more PetIDs (from a Pet table).
Next I have a search screen where people can multiple select pets from a jquery multiple select dropdown. Let's say somebody selects Dogs and Cats.
Now, I want to be able to return all properties that contain BOTH dogs and cats in the many to many table, PropertyPets. I'm trying to do this with Linq to Sql.
I've looked at the Contains clause, but it doesn't seem to work for my requirement:
var result = properties.Where(p => search.PetType.Contains(p.PropertyPets));
Here, search.PetType is an int[] array of the Id's for Dog and Cat (which were selected in the multiple select drop down). The problem is first, Contains requires a string not an IEnumerable of type PropertyPet. And second, I need to find the properties that have BOTH dogs and cats and not just simply containing one or the other.
Thank you for any pointers.
You can do this using a nested where clause.
You need to filter p.PropertyPets using contains - return all rows where PetID is in search.PetType.
Then only return rows from properties where all search id's have been found - eg number of rows >= number of serach id's
All together:
var result = from p in properties
where p.PropertyPets.Where(c => search.PetType.Contains(c.PetID)).Count() >= search.PetType.Count()
select p;
For the part where Contains requires a string would not be true, Contains should require an int if your search.PetType is int[]. That means that you need to "convert" p.PropertyPets into an int. To convert p.PropertyPets to IEnumerable<int> you need to select the PropertyID field: p.PropertyPets.Select(propertyPet => propertyPet.PropertyID), but that won't get you a single int as required but a whole bunch. (.First() would give you one int but not solve your problem.
What you really want to do is
var result = properties.Where(p =>
search.PetType.Except(p.PropertyPets.Select(propertyPet =>
propertyPet.PropertyID)).Count() == 0);
But Except is not available in LINQ2SQL.
The best option I can find is to apply Contains for each item in search.PetType.
Something like this:
var result = properties;
foreach(var petType in search.PetType)
{
result = from p in result
where p.PropertyPets.Select(propertyPet =>
propertyPet.PropertyID).Contains(petType)
select p;
}

Linking Multiple Tables in LINQ to SQL

I would like to get the list of albums (Distinct) which was sung by the artistId=1
I am very new to LINQ to SQL and do not know how to join multiple tables. Please see the database diagram below:
alt text http://a.imageshack.us/img155/8572/13690801.jpg
SingBy is the middle table between Track and Artist.
How could I achieve this?
var albums = from singer in artist
from sb in singby
from t in track
from a in album
where singer.artistId == 1 &&
sb.artistId == 1 &&
sb.trackId == t.trackId &&
a.albumId == track.albumId
select a;
I'm sure there must be a better way. You should look into creating Navigation Properties on your entities. Navigation Properties are like foreign keys.
Edit - corrected to get albums, not artists.
Now, I wrote the codes like the following and it works.
var albums = (from a in db.artists
where a.artistId == 1
join sb in db.singbies on a equals sb.artist
join t in db.tracks on sb.track equals t
join al in db.albums on t.album equals al
select al).Distinct();
return albums.ToList() as List<album>;
I tested the Chad's version and it works too. I would like to know which way is better and good for query optimization? Thanks all.
If you have all the foreign key relationship defined, you should be able to issue call like below:
dc.GetTable<Album>().Where(a => a.Track.Singby.ArtistId == 1).ToList();
This is relying on Linq to perform lazy load for Track and Singby automatically when required. Obviously this is not optimal to use when you have a large set of data in the db and performance is critical. You can chain the query with GroupBy or Distinct operation to return only the distinct set such as
dc.GetTable<Album>().Where(a => a.Track.Singby.ArtistId == 1).Distinct().ToList();
I would like to get the list of albums
(Distinct) which was sung by the
artistId=1
DBDataContext = new DBDataContext();
album[] = db.artists.Where(a => a.artistId == 1) /* Your artist */
.SelectMany(a => a.singbies) /* Check if `singby` converted to `singbies` */
.Select(sb => sb.track) /* The tracks */
.Select(t => t.album) /* The albums */
.GroupBy(al => al.albumId) /* Group by id */ /* "Distinct" for objects */
.Select(alG => alG.First()) /* Select first of each group */
.ToArray();
IEnumerable<Album> query =
from album in myDC.Albums
let artists =
from track in album.Tracks
from singBy in track.SingBys
select singBy.Artist
where artists.Any(artist => artist.ArtistId == 1)
select album;
List<int> Ids = dc.Albums.Where(a => a.Track.Singby.ArtistId == 1).Select(a=> a.albumId).Distinct().ToList();
List<Album> distinctAlbums = dc.Albums.Where(a => distinctAlbumIds.Contains(a.albumId)).ToList();
Hey TTCG, above is the simplest way to do it. This is because doing a Distinct on a List of objects won't do it based on the albumId.
Either you do it in two steps as above, or, you write your own Album Comparer which specifies uniqueness based on AlbumId and pass it to the Distinct call on a List.
NOTE:
The above will only work if you've defined the constraints in your DBML, but better still in your DB.
For best practices, always define your relationships IN THE DATABASE when using Linq to SQL, as Linq to SQL is not like EF, or NHibernate, in that is does not "abstract" your db, it simply reflects it. It's a tool for Data Driven Design, not Domain Driven, so define the relationships in the db.

Categories

Resources