Linq Outer Joins - don't want DefaultIfEmpty, rather want NullIfEmpty - linq

all,
This problem relates to the Dynamics CRM 2011 Linq provider - which does have a LOT of quirks. However, I have not tagged it such as I think this is a general Linq question.
I have a class- Product. It has a property (say ProductPrice) of type Price.
I am doing an Outer Join on this in Linq. The CRM documentation says outer joins are not possible, but it seems to work (with the obvious problem I am asking here).
So say I am doing something like: (apologies for the pseudo linq)
IList<Product> products = (from p in xrmContext.Products
join pr in xrmContext.Prices
on p.ProductId equals pr.ProductId into prx from prices in prx.DefaultIfEmpty
select new Product { ProductName = p.productName, ProductPrice = new Price { Amount = prices.PriceValue }).ToList();
This works great to a point. It creates all the products irrespective of whether they have a price object or not. Tippety top.
The problem is the DefaultIfEmpty. As you are no doubt aware if a product has no price this DefaultIfEmpty will create a 'default' price object ... i.e. an object with null values. What I actually want is NO price object - i.e. null, not a 'blank' object.
How is that possible?
I have worked round it by testing for a blank price name - ProductPrice = price.priceName == "" ? null : new Price ...
It would be nice to be able to do something like NullIfEmpty. Any ideas?

You can skip the join:
from p in xrmContext.Products
let price = xrmContext.Prices.FirstOrDefault(pr => pr.ProductID == p.ProductID)
select new Product()
{
ProductName = p.productName,
ProductPrice = price != null ? new Price() { Amount = price.PriceValue } : null
}

crm 2011 linq doesn't support outer join.
If you try Amiram's code - when evaluating the select, you'll get an error "products doesn't contain attribute "pricevalue"".

Related

How to retieve CRM Guid using LINQ and joins?

We are using CRM 2011. We have Contracts with Entity Reference to Product and each Product has an Entity Reference to a Subject. Given a Contract Guid, I need to retrieve the Subject Guid.
I am a beginner at LINQ but I coded:
var subject = from s in context.SubjectSet
join product in context.ProductSet
on s.Id equals product.SubjectId.Id
join contract in context.ContractSet
on product.Id equals contract.ce_ProductId.Id
where contract.Id == gContractId
select s;
foreach (var s in subject)
{
newReportableAction.ce_SupergroupRegarding =
new EntityReference(Xrm.Subject.EntityLogicalName, new Guid(s.Id.ToString()));
}
This throws an error:
AttributeFrom and AttributeTo must be either both specified or both ommited. You can not pass only one or the other. AttributeFrom: , AttributeTo: ce_ProductId
What does this error mean?
How can I get the Guid?
Update:
I tried breaking the query into parts to see where the error was being generated from so I had:
var query = from product in context.ProductSet
join contract in context.ContractSet
on product.Id equals contract.ce_ProductId.Id
This gives:
"The type of one of expressions in the join clause is incorrect. Type inference failed in the call to 'Join'"
Thank you to all who help...
I don't think you can use the .Id property right off the entity in Linq statements. Try this instead:
var query = from product in context.ProductSet
join contract in context.ContractSet
on product.ProductId equals contract.ce_ProductId.Id
Notice product.ProductId instead of product.Id.

Subquery nightmares in EF

I'm really, really struggling with what should otherwise be a straightforward query in anything other than LINQ (for example SQL!)
I have two entities:
Product
ProductApprover
The Product entity has a one to many relationship on the ProductApprover entity, e.g:
Product.ProductApprovers gives me all ProductApprover entities relating to the Product.
Getting a Product and associated ProductApprover data is simple enough when querying by my ProductID column on my Product entity as the ProductApprover data associated is bundled into the result automatically, but my problem comes when I want to alter my query by querying data WITHIN my associated ProductApprover entities. I have tried all sorts with use of the 'Where', 'Contains' and 'Any', functions, etc, to perform a subquery, but cannot seem to get the result I want.
The query I want to perform is:
SELECT * FROM Product p
INNER JOIN ProductApprover pa ON p.ProductId = pa.ProductId
WHERE p.ProductId = #id AND pa.Version = #version
Can anybody help me out please? Thank you kindly in advance.
Try this (I guess this is a LINQ interpretation of your SQL query):
int id = 123;
int version = 555;
var results = from p in context.Products
join pa in context.ProductApprovers
on p.ProductId = pa.ProductId
where p.ProductId equals id && pa.Version equals version
select new { Product = p, Approver = pa };
I suspect you want something like this:
var query = from product in db.Products
where product.ProductId == productId
select new {
Product = product,
Approvers = product.Approvers.Where(pa => pa.Version == version)
};
If that doesn't do what you want, could you explain where it falls down?

How do I programmatically translate a LINQ query to readable English text that correctly describes the linq expression?

I am working on a project that uses Albahari's PredicateBuilder library http://www.albahari.com/nutshell/ to create a linq expression dynamically at run time. I would like to find a way to translate this dynamically created linq predicate of type Expression<Func<T, bool>> into a readable english statement at runtime.
I'll give a statically created linq statement as an example:
from p in Purchases
select p
where p.Price > 100 && p.Description != "Bike".
For this linq statement I would want to dynamically generate at runtime an english description along the lines of:
"You are searching for purchases where the price is greater than 100 and the description is not bike".
Are there any libraries that already exist which accomplish this goal, keep in mind I am using PredicateBuilder to dynamically generate the where predicate. If no solution exists how would you go about building a solution?
Thanks!
This caught my attention so I downloaded ExpressionSerializationTypeResolver.cs and ExpressionSerializer.cs and then I:
class Purchase
{
public decimal Price {get;set;}
public string Description {get;set;}
}
...
var purchases = new List<Purchase>() { new Purchase() { Price = 150, Description = "Flute" }, new Purchase() { Price = 4711, Description = "Bike" } };
Expression<Func<IEnumerable<Purchase>>> queryExp = () => from p in purchases
where p.Price > 100 && p.Description != "Bike"
select p;
ExpressionSerializer serializer = new ExpressionSerializer();
XElement queryXml = serializer.Serialize(queryExp);
and then I got into problems, but maybe you could do something with the pretty big expression tree of your query? You can find it here.

Help required to optimize LINQ query

I am looking to optimize my LINQ query because although it works right, the SQL it generates is convoluted and inefficient...
Basically, I am looking to select customers (as CustomerDisplay objects) who ordered the required product (reqdProdId), and are registered with a credit card number (stored as a row in RegisteredCustomer table with a foreign key CustId)
var q = from cust in db.Customers
join regCust in db.RegisteredCustomers on cust.ID equals regCust.CustId
where cust.CustomerProducts.Any(co => co.ProductID == reqdProdId)
where regCust.CreditCardNumber != null && regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.DisplayName,
RegNumber = cust.RegNumber
};
As an overview, a Customer has a corresponding Person which has the Name; PersonID is a foreign key in Customer table.
If I look at the SQL generated, I see all columns being selected from the Person table. Fyi, DisplayName is an extension method which uses Customer.FirstName and LastName. Any ideas how I can limit the columns from Person?
Secondly, I want to get rid of the Any clause (and use a sub-query) to select all other CustomerIds who have the required ProductID, because it (understandably) generates an Exists clause.
As you may know, LINQ has a known issue with junction tables, so I cannot just do a cust.CustomerProducts.Products.
How can I select all Customers in the junction table with the required ProductID?
Any help/advice is appreciated.
The first step is to start your query from CustomerProducts (as Alex Said):
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
join regCust in db.RegisteredCustomers
on custProd.Customer.ID equals regCust.CustId
where
custProd.ProductID == reqProdId
&& regCust.CreditCardNumber != null
&& regCust.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
This will simplify your syntax and hopefully result in a better execution plan.
Next, you should consider creating a foreign key relationship between Customers and RegisteredCustomers. This would result in a query that looked like this:
IQueryable<CustomerDisplay> myCustDisplay =
from custProd in db.CustomerProducts
where
custProd.ProductID == reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
Finally, for optimum speed, have LINQ compile your query at compile time, rather than run time by using a compiled query:
Func<MyDataContext, SearchParameters, IQueryable<CustomerDisplay>>
GetCustWithProd =
System.Data.Linq.CompiledQuery.Compile(
(MyDataContext db, SearchParameters myParams) =>
from custProd in db.CustomerProducts
where
custProd.ProductID == myParams.reqProdId
&& custProd.Customer.RegisteredCustomer.CreditCardNumber != null
&& custProd.Customer.RegisteredCustomer.Authorized == true
select new CustomerDisplay
{
Id = cust.Id,
Name = cust.Person.Name,
RegNumber = cust.RegNumber
};
);
You can call the compiled query like this:
IQueryable<CustomerDisplay> myCustDisplay = GetCustWithProd(db, myParams);
I'd suggest starting your query from the product in question, e.g. something like:
from cp in db.CustomerProducts
join .....
where cp.ProductID == reqdProdID
As you have found, using a property defined as an extension function or in a partial class will require that the entire object is hydrated first and then the select projection is done on the client side because the server has no knowledge of these additional properties. Be glad that your code ran at all. If you were to use the non-mapped value elsewhere in your query (other than in the projection), you would likely see a run-time exception. You can see this if you try to use the Customer.Person.DisplayName property in a Where clause. As you have found, the fix is to do the string concatenation in the projection clause directly.
Lame Duck, I think there is a bug in your code as the cust variable used in your select clause isn't declared elsewhere as a source local variable (in the from clauses).

How do I query only a single item from a database using LINQ?

I would like to get a LINQ-to-SQL query that returns only one item, not a collection of them?
For example, I have a list of products with a particular name. There are no products with duplicate names in the database, so I want to be able to query and return just that instance of that product.
Products product = from p in _productContext.Products
where p.name.Equals("BrownShoes")
select p;
How do I do something like that?
Use Single:
Product product = _productContext.Products
.Single(p => p.Name == "BrownShoes");
or
Product product = _productContext.Products
.Where(p => p.Name == "BrownShoes")
.Single();
There's no query expression syntax for Single, so you have to call it as a normal extension method. At that point your query is simpler written entirely with dot notation, hence the form above. You could write it as:
Product product = (from p in _productContext.Products
where p => p.Name == "BrownShoes"
select p).Single();
But that's got a lot more fluff. If there isn't exactly a single element in the result, an exception will be thrown.
I recommend using IQueryable.SingleOrDefault(). IQueryable.SingleOrDefault() and IQueryable.Single() will throw an exception if there is more than one record, but IQueryable.Single() will also throw one if there is less than one record. This means that if you are searching for a single record and for whatever reason it doesn't exist, you have to handle an exception.
Much better is IQueryable.SingleOrDefault(), because you just check for null:
var employee = (from e in context.Employees
where e.ID == employeeID
select e).SingleOrDefault();
if (employee == null)
{
// Cope with employee not found
}
// Do stuff with employee
You need to use .Single() or .SingleOrDefault():
Products product = (from p in _productContext.Products where p.name.Equals("BrownShoes") select p).Single();
Single() will throw an exception if there's not exactly 1 product there, SingleOrDefault() will return null if it doesn't exist, or throw an exception if there are multiple.
Products product = (from p in _productContext.Products
where p.name.Equals("BrownShoes")
select p).FirstOrDefault();
If you are sure you will find a product. An exception will be thrown if no product is found:
Products product = _productContext.Products
.Single(p => p.name.Equals("BrownShoes"));
Or, if no product is found, product will be null:
Products product = _productContext.Products
.SingleOrDefault(p => p.name.Equals("BrownShoes"));

Resources