I'm using LINQ on a Telerik OpenAccess generated data model, to setup a search query and get the results. This all works very well and the code looks very nice.
But now i need to add one more thing, and i'm not sure how to.
The products i'm selecting should have a different price for each customer. The price depends on some other values in 2 other SQL tables. Right now i have the price calculation in a SQL scalar function, but i'm not sure on how to use that in combination with my LINQ.
My goal is to retrieve all data in one database roundtrip, and to be able to sort on this calculated price column as well.
Right now i have something like:
var products = (from p in Products select p);
if(searchOption)
products = products.Where(product => product.Name.Contains(searchOption));
products = products.OrderByDescending(product => product.Name);
products = products.Skip(pageNr * pageSize).Take(pageSize);
I can, of course, use all properties of my Product class, but i want to be able to use a new virtual/calculated property, let say: Product.CalculatedPrice as well.
I have a feeling it should look a bit like this.
(from p in Products
select new {
productId = p.ProductId,
name = p.Name,
calculatedPrice = CalculatedValueFromStoredProcedure/OrScalarFunction(p.ProductId, loggedInCustomerId)
});
Best Regards, Tys
.Select() allows you to create a dynamic type, in which you can extend the product with the calculated price
products.Select(i => new { Product = i, CalculatedPrice = 100})
or in your initial line:
var products = (from p in Products select new { Product = p, CalculatedPrice = 100 });
After doing some more research i've found out that what i want to do is NOT possible in combination with the Telerik OpenAccess ORM! So i've created a workaround that pre-calculates the values i need, put them in a table and join my selection with the contents of that table.
For now, that's the best possible solution i've found.
Related
I have a database that contains 3 tables:
Phones
PhoneListings
PhoneConditions
PhoneListings has a FK from the Phones table(PhoneID), and a FK from the Phone Conditions table(conditionID)
I am working on a function that adds a Phone Listing to the user's cart, and returns all of the necessary information for the user. The phone make and model are contained in the PHONES table, and the details about the Condition are contained in the PhoneConditions table.
Currently I am using 3 queries to obtain all the neccesary information. Is there a way to combine all of this into one query?
public ActionResult phoneAdd(int listingID, int qty)
{
ShoppingBasket myBasket = new ShoppingBasket();
string BasketID = myBasket.GetBasketID(this.HttpContext);
var PhoneListingQuery = (from x in myDB.phoneListings
where x.phonelistingID == listingID
select x).Single();
var PhoneCondition = myDB.phoneConditions
.Where(x => x.conditionID == PhoneListingQuery.phonelistingID).Single();
var PhoneDataQuery = (from ph in myDB.Phones
where ph.PhoneID == PhoneListingQuery.phonePageID
select ph).SingleOrDefault();
}
You could project the result into an anonymous class, or a Tuple, or even a custom shaped entity in a single line, however the overall database performance might not be any better:
var phoneObjects = myDB.phoneListings
.Where(pl => pl.phonelistingID == listingID)
.Select(pl => new
{
PhoneListingQuery = pl,
PhoneCondition = myDB.phoneConditions
.Single(pc => pc.conditionID == pl.phonelistingID),
PhoneDataQuery = myDB.Phones
.SingleOrDefault(ph => ph.PhoneID == pl.phonePageID)
})
.Single();
// Access phoneObjects.PhoneListingQuery / PhoneCondition / PhoneDataQuery as needed
There are also slightly more compact overloads of the LINQ Single and SingleOrDefault extensions which take a predicate as a parameter, which will help reduce the code slightly.
Edit
As an alternative to multiple retrievals from the ORM DbContext, or doing explicit manual Joins, if you set up navigation relationships between entities in your model via the navigable join keys (usually the Foreign Keys in the underlying tables), you can specify the depth of fetch with an eager load, using Include:
var phoneListingWithAssociations = myDB.phoneListings
.Include(pl => pl.PhoneConditions)
.Include(pl => pl.Phones)
.Single(pl => pl.phonelistingID == listingID);
Which will return the entity graph in phoneListingWithAssociations
(Assuming foreign keys PhoneListing.phonePageID => Phones.phoneId and
PhoneCondition.conditionID => PhoneListing.phonelistingID)
You should be able to pull it all in one query with join, I think.
But as pointed out you might not achieve alot of speed from this, as you are just picking the first match and then moving on, not really doing any inner comparisons.
If you know there exist atleast one data point in each table then you might aswell pull all at the same time. if not then waiting with the "sub queries" is nice as done by StuartLC.
var Phone = (from a in myDB.phoneListings
join b in myDB.phoneConditions on a.phonelistingID equals b.conditionID
join c in ph in myDB.Phones on a.phonePageID equals c.PhoneID
where
a.phonelistingID == listingID
select new {
Listing = a,
Condition = b,
Data = c
}).FirstOrDefault();
FirstOrDefault because single throws error if there exists more than one element.
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.
My problem solving like this such a code;
string permalink = "computers/hp/computers"; //example for similarity
List<string> aliases = permalink.Split('/').ToList();
Category cat = db.Categories.SingleOrDefault(c => c.Alias == aliases.First());
aliases.Remove(aliases.First());
foreach (string alias in aliases)
{
cat = cat.Categories.SingleOrDefault(c => c.Alias == alias);
}
return cat;
But this is sent many query..
How do I make one time?
If I understand what you want, you can use the Enumerable.Aggregate method. You will have to start with a 'root' category, that encompasses all of db.Categories. That's pretty easy to mock up though. Try this:
var aliases = permalink.Split('/');
var category = new Category // start with a 'root' category
{
Id = 0,
Categories = db.Categories
};
var cat = aliases.Aggregate(category, (c, a) => c.Categories.SingleOrDefault(x => x.Alias == a));
Firstly, if the category table is small it is sometimes better to just grab the whole table and do the selection in memory (perhaps using p.w.s.g's answer).
If the table is large, then a Stored procedure would probably be better than Linq.
But, if you really want to do it in Linq, then I think the only way is to repeatedly add a join to same table.
The following is assuming that your relationship is between fields called ParentID and Id. I have also changed your string permalink to better illustrate the order.
You first need a little helper class
public class Info
{
public Category category;
public int? recordID;
}
then your main code
string permalink ="computers1/hp/computers2";
var aliases = permalink.Split('/');
var query = dc.Categories.Where(r=>r.Alias == aliases[aliases.Length-1])
.Select(r=> new Info { category = r, recordID = r.ParentID});
for(int i = aliases.Length -2 ; i >= 0; i--)
{
string alias = aliases[i];
query = query.Join(dc.Categories ,
a => a.recordID , b => b.Id , (a,b) => new { a , b} )
.Where(r=>r.b.Alias == alias)
.Select(r=> new Info { category = r.a.category, recordID = r.b.ParentID});
}
return query.SingleOrDefault().category;
As you can see the lambda syntax of join is (IMHO) horrendous and I usually try to avoid it, but I can't think of anyway of avoiding it here.
Since I can't test it, it could be totally wrong (maybe I've mixed up the ID, ParentID or my a's and b's ), so it is important to test this and to test how it performs.
I think the sql produced should be something like
SELECT * from Categories AS t0
INNER JOIN Categories AS t1 ON t0.ParentID = t1.id
INNER JOIN Categories AS t2 ON t1.ParentID = t2.id
WHERE t2.Alias = 'computers1'
AND t1.Alias = 'hp'
AND t0.Alias = 'computers2'
The more sections or aliases, then the more joins there are.
Now that you've see all that, you probably want to avoid using this method -)
I'll probably just add to your confusion :), but let me just throw an idea...
Let me just say it that this doesn't work (exactly per your specs) - and it's not the solution but might help you simplify things a bit.
var query =
(from cat in db.Categories
where cat.Alias == "mainAalias"
from subcat in cat.Categories
where aliases.Contains(subcat.Alias)
orderby subcat.Alias descending
select subcat);
query.FirstOrDefault(); // or something
This should produce one relatively simple query
(e.g. SELECT...FROM...JOIN...WHERE... AND...IN...ORDERBY...).
e.g. if you give it 'cat1', 'cat2'...'cat6' - out of cat1 - to cat100 - it gives 'cat6'...'cat1' (I mean the aliases)
However it has a major 'flaw' with the 'sorting' - your specs require a sort that is the order of 'aliases' as they come - which is a bit unfortunate for queries. If you could somehow enforce, or define an order, that could be translated to SQL this (or similar) might work.
I'm assuming - that your 'aliases' are pre-sorted in an ascending
order - for this query to work. Which they are not, and I'm aware of
that.
But I think that your idea is not clearly defined here (and why all of us are having problems) - think through, and optimize - simplify your requirements - and let your C# tier help e.g. by pre-sorting.
You could also try some form of 'grouping' per cat.Alias etc. - but I think the same 'sorting problem' persists.
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()
});
Is there a way to use LoadWith but specify the fields that are returned?
For example, if I have two tables 1) Products 2) Categories
and do something like
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Products>(d => d.Categories);
db.LoadOptions = dlo;
MyDataContext db = new MyDataContext();
var result = from d in db.Products
select d;
when i check the query in profiler i see that ALL the rows from the Categories table are being returned. All I really need is the "Name" field.
I know I can rewrite the query using joins but I need to return the result set as a "Product" data type which is why I am using LoadWith.
No that's not possible with LoadWith.
You could try with a nested query in the projection, though that will be slow: 1 query per parent (so 1 query for the related category per product loaded).
You can use a projection, but you need to deal with anynomus types after that
select new {Order = order, ProductName = order.Product.Name,
CustomerName = order.Customer.Name,
OrderType = order.OrderType.Name } // etc