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
Related
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.
I have a sql statement like this:
DECLARE #destinations table(destinationId int)
INSERT INTO #destinations
VALUES (414),(416)
SELECT *
FROM GroupOrder grp (NOLOCK)
JOIN DestinationGroupItem destItem (NOLOCK)
ON destItem.GroupOrderId = grp.GroupOrderId
JOIN #destinations dests
ON destItem.DestinationId = dests.destinationId
WHERE OrderId = 5662
I am using entity framework and I am having a hard time getting this query into Linq. (The only reason I wrote the query above was to help me conceptualize what I was looking for.)
I have an IQueryable of GroupOrder entities and a List of integers that are my destinations.
After looking at this I realize that I can probably just do two joins (like my SQL query) and get to what I want.
But it seems a bit odd to do that because a GroupOrder object already has a list of DestinationGroupItem objects on it.
I am a bit confused how to use the Navigation property on the GroupOrder when I have an IQueryable listing of GroupOrders.
Also, if possible, I would like to do this in one trip to the database. (I think I could do a few foreach loops to get this done, but it would not be as efficient as a single IQueryable run to the database.)
NOTE: I prefer fluent linq syntax over the query linq syntax. But beggars can't be choosers so I will take whatever I can get.
If you already have the DestinationGroupItem as a Navigation-property, then you already have your SQL-JOIN equivalent - example. Load the related entities with Include. Use List's Contains extension method to see if the desired DestinationId(s) is(are) hit:
var destinations = new List<int> { 414, 416 };
var query = from order in GroupOrder.Include(o => o.DestinationGroupItem) // this is the join via the navigation property
where order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId)
select order;
// OR
var query = dataContext.GroupOrder
.Include(o => o.DestinationGroupItem)
.Where(order => order.OrderId == 5662 && destinations.Contain(order.DestinationGroupItem.DestinationId));
I'm currently using EF Code-First, and I'd like to SELECT data from many tables in the database. This query is a customizable query, and hence I can't predict what kind of data I will be retrieving.
At first, when I tried running ctx.Database.SqlQuery<string>(sql, param), I ran into an exception when facing a DateTime value. I'd like to do this without casting it on the server side.
Does anybody have any idea how I can go about doing it? It can be in LINQ, LINQ-SQL, or purely SQL--so long as it gets the job done! Thanks guys...
You will not get it. Linq-to-entities will not make transformation to list of strings. Your best chance is executing normal queries and do conversion and transformation your application.
Argument that you don't know which columns user selects just means you need more dynamic solution - Linq-to-entities is not a good tool for you (except if you try to use Dynamic Linq or build expression trees manually). Use ESQL or SQL directly.
When selecting data from many tables, use anonymous types to encapsulate the properties(fields) you want to select from these tables into a new entity, something like:
var query = _db.Categories.Join(
_db.Products,
c => c.CategoryId,
p => p.CategoryId,
(category, product) =>
new
{
ProductName = product.Name,
CategoryName = category.Name,
ExpiryDate = product.ExpiryDate
});
You can achieve string values by casting your data fields to string in this way:
var query = _db.Categories.Join(
_db.Products,
c => c.CategoryId,
p => p.CategoryId,
(category, product) =>
new
{
ProductName = product.Name.toString(),
CategoryName = category.Name.toString(),
ExpiryDate = product.ExpiryDate.toString()
});
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 */)
I'm using NHibernate 2.1.2.400, and I'm having an issue with a an ISQLQuery query.
The reason why I use an ISQLQuery here, is that this query uses a table for which I have no entity mapped in NHibernate.
The query looks like this:
ISQLQuery query = session.CreateSQLQuery (
"select p.*, price.* " +
"from prestation p left outer join prestationprice price on p.PrestationId = price.PrestationId " +
"where p.Id IN ( select id from prestationregistry where ...");
'Prestationregistry' is the table that is not known by NHibernate (unmapped, so therefore the native SQL Query).
my code continues like this:
query.AddEntity ("p", typeof(Prestation));
query.AddJoin ("price", typeof(PrestationPrice));
query.SetResultTransformer (Transformers.DistinctRootEntity);
var result = query.List();
So far so good.
I expect that I'm given a list of 'Prestation' instances as a result of this query, since I declared 'Prestation' as being the root-object that has to be returned by the AddEntity method.
I also expect that the PrestationPrices for each Prestation are eagerly loaded by this query (hence the AddJoin method).
To my surprise, the List() method returns a collection of PrestationPrice instances instead of Prestation instances.
How come ? Am I doing something wrong ? And if so, could you be so kind to tell me what I'm doing wrong ?
Edit: Additional Info:
When I debug, and put a watch on the 'query' instance, I can see that the queryReturns member of the query contains 2 items:
- one NativeSqlQueryRootReturn instance who'se ReturnEntityName is 'Prestation'
- one NativeSqlQueryJoinReturn
When I do not specify the 'DistinctRootEntity' result transformer, the query returns instances of 'Prestation' instead of PrestationPrice. However, it contains multiple copies of the same instance.
I'm not sure this is the real cause of your problem, but you must enclose SQL aliases in braces, e.g.
select {p.*}, {price.*}
from prestation p
left outer join prestationprice price on p.PrestationId = price.PrestationId
where p.Id IN ( select id from prestationregistry where ...
Try specifying the type to be returned in the List method:
var result = query.List<Prestation>();
I've solved the problem in a slightly different way.
I didn't know that you could use an SQL expression as an ICriteria-criterion (thx to the NH-Users google-group), so I did it like this:
ICriteria crit = session.CreateCriteria (typeof(Prestation), "p");
crit.SetFetchMode ("p.Prices", FetchMode.Eager);
crit.Add (Expression.Sql ("{alias}.PrestationId = ( SELECT id FROM sometable WHERE ... "));
crit.SetResultTransformer (Transformers.DistinctRootEntity);
var result = crit.List<Prestation>();
Anyway, the question remains if the behaviour that I've got using the ISQLQuery is expected, or a bug ...
The NHUsers thread that I've started as well