NHibernate Linq .Contains code generation has bug? - c#

I'm trying to achieve:
select StoreId, StoreName from Store where StoreId in (
select StoreId from Employee where EmployeeName = 'Steve Jobs')
I have this code:
public class Store
{
public virtual int StoreId { get; private set; }
public virtual string StoreName { get; set; }
public virtual IList<Employee> Staff { get; set; }
}
public class Employee
{
public virtual Store Store { get; set; }
public virtual int EmployeeId { get; private set; }
public virtual string EmployeeName { get; set; }
}
var q = from s in session.Query<Store>()
where
(from e in session.Query<Employee>()
where s.EmployeeName == "Steve Jobs"
select e.Store.StoreId).Contains(s.StoreId)
select s;
NHibernate generates this (aliases stripped):
select s.StoreId, s.StoreName
from Store s
where exists
(
select t.StoreId
from Employee e
left join Store t on t.StoreId = e.StoreId
where e.EmployeeName = 'Steve Jobs'
-- wrongly generated code
and t.EmployeeId = s.StoreId
)
Linq-toSql generates the code correctly:
select s.StoreId, s.StoreName
from Store s
where exists
(
select null
from Employee e
where e.EmployeeName = 'Steve Jobs'
and e.StoreId = s.StoreId
)
Is there a problem with subquery code generation on Linq to NHibernate?
However, HQL works:
var q = session.CreateQuery("from Store as s where s.StoreId in (select e.WorkingInStore.StoreId from Employee as e where e.EmployeeName = 'lennon')").List<Store>();

Certainly looks like a bug, but I think you're overcomplicating the whole query. As I understand, you want all stores where an employee named Steve Jobs is on the payroll. Try:
var q = from s in session.Query<Store>()
where s.Staff.Any(e=>e.EmployeeName == "Steve Jobs")
This should generate the query you want, and it's much cleaner and more readable than the subquery.

I answered a similar question in this post. Instead of using Contains() operator, we can tweak the subquery a little bit and use Any() operator instead.
LINQ to NHibernate WHERE EXISTS IN

Related

Entity Framework - Get 'fake' navigation property within one query

I have a Product table that has no relation defined to the translation table. I added a Translation property to the Product POCO as [NotMapped].
**My Product POCO: **
public partial class Product
{
public int ProductID { get; set; }
public double Price { get; set; }
[NotMapped]
public virtual Translation Translation{ get; set; }
/** Other properties **/
}
I also have a Translation table, and like the name says, it contains all the translations.
Now, the right translation can be retrieved from the database by providing three parameters: LanguageID, TranslationOriginID and ValueID.
LanguageID: ID from the language that the user has defined.
TranslationOriginID: Simply said, 'What table contains the entity that I want the translation for?' In other words, this ID points to another table that contains all possible origins. An origin is a table/entity that can have a translation. E.g: The origin in this example is Product.
ValueID: This is the ID of the entity that I want a translation for.
My Translation POCO:
public partial class Translation
{
public int TranslationID { get; set; }
public byte LanguageID { get; set; }
public short TranslationOriginID { get; set; }
public int ValueID { get; set; }
public string TranslationValue { get; set; }
/** Other properties **/
public virtual TranslationOrigin TranslationOrigin { get; set; }
public virtual Language Language { get; set; }
}
When I want to retrieve all products with their Translation, I execute this code:
List<Product> products = context.Products.ToList();
foreach (Product product in products)
{
product.Translation = context.Translations.FirstOrDefault(y => y.LanguageID == 1 && y.TranslationOriginID == 2 && y.ValueID == product.ProductID);
}
Like you can see, I execute for every product in the list another query to get the translation.
My question:
Is it possible to get all the products and their translation in one query? Or even that I automatically retrieve the right translation when I select a product?
I already tried an .Include() and a .Select(). It didn't work, maybe I did something wrong?
I also tried this method, didn't work either.
Btw, I use Entity framework 5 with .NET 4 (so, Entity Framework 4.4).
Thanks in advance.
Greetings
Loetn
Answer
With the example given by Ed Chapel, I came up with a solution.
return (from p in context.Products
join t in context.Translations
on new
{
Id = p.ProductID,
langId = languageID,
tOriginId = translationOriginID
}
equals new
{
Id = d.ValueID,
langId = d.LanguageID,
tOriginId = d.TranslationOriginID
}
into other
from x in other.DefaultIfEmpty()
select new
{
Product = p,
Translation = x
})
.ToList().ConvertAll(x => new Product()
{
Code = x.Product.Code,
Translation = x.Translation,
/** Other properties **/
});
I don't like proper LINQ in most cases. However, join is one scenario where the LINQ is easy than the extensions methods:
from p in context.Products
join t in context.Translations
on t.ValueID equals p.ValueID
&& t.LanguageID == 1
&& t.TranslationOriginID == 2
into joinT
from x in joinT
select new {
Product = p,
Translation = t,
};
You then loop over the result setting x.Product.Translation = x.Translation.
First of all you should realize that your translations table is not structured like a dba would like it You have a non enforced relationship because depending on the OriginId your valueId references a different table.
Because of this you cannot use lazy loading or includes from EF.
My best idea at this point would to manually join the table on an anonymous type(to include your originId). Afterwards you can iterate over the results to set the translation property
The result would look like this :
var data = from p in context.Products
join pt in context.Translations on new{p.Id,2} equals new {pt.ValueId, pt.OriginId} into trans
select new {p, trans};
var result = data.ToList().Select( a =>
{
a.p.Translations = a.trans;
return a.p;
}).ToList();
With the example that Ed Chapel proposed as a solution, I came up with this.
return (from p in context.Products
join t in context.Translations
on new
{
Id = p.ProductID,
langId = languageID,
tOriginId = translationOriginID
}
equals new
{
Id = d.ValueID,
langId = d.LanguageID,
tOriginId = d.TranslationOriginID
}
into other
from x in other.DefaultIfEmpty()
select new
{
Product = p,
Translation = x
})
.ToList().ConvertAll(x => new Product()
{
Code = x.Product.Code,
Translation = x.Translation,
/** Other properties **/
});

Linq Query with join on subquery

Basically I'm trying to write a query where it joins on select top 1 from a second table so something like:
SELECT Sum(pinfo.quantity + p.itemcount),
i.owner
FROM invoice i
JOIN purchase_info pinfo
ON pinfo.invoice = i.invid
JOIN (SELECT DISTINCT sku,
productlineid,
itemcount
FROM products WHERE productlineid in (13, 14)) p
ON p.sku = pinfo.item
WHERE i.owner = 22623
GROUP BY i.owner
Here's my pathetic attempt in linq that has somewhat invalid syntax, any ideas would be much appreciated.
(from i in _invoiceRepository.Table
join pi in _purchaseInfoRepository.Table on i.InvoiceId equals pi.InvoiceId
join p in (from p2 in _productRepository.Table where p2.Sku == pi.Item select new { p2.Sku, p2.ItemCount }).Take(1)
on pi.Item equals p.Sku
where i.MemberId == memberId &&
(p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
select pi.Quantity * p.ItemCount)
.DefaultIfEmpty(0)
.Sum();
Here is my first stab at this.
From the sql, it looks like you want to find how many Ink and Toner Cartridges a particular customer has ordered from you ever.
This should give you the same results as the sql (this is depending on the order of the Products table since we are taking the top 1 without some sort of ordering being done:
var count = from i in _invoiceRepository.Table
where i.OwnerId == memberId
select new
{
OwnerId = i.OwnerId,
TotalProductCount = i.Purchases.Sum(pro => pro.Products
.Where(p => p.ProductLineId == (int)ProductLines.InkCartridges ||
p.ProductLineId == (int)ProductLines.TonerCartridges)
.Take(1)
.Sum(p => p.ItemCount * pro.Quantity))
};
Since I did not know the the classes of the three objects (Invoice, PurchaseInfo, and Product) I made a guess at what they are:
Invoice Class: I assume it has a list/collection of PurchaseInfos
public class Invoice
{
public int Id { get; set; }
public int OwnerId { get; set; }
public List<PurchaseInfo> Purchases { get; set; }
}
PurchaseInfos: An invoice has multiple PurchaseInfos, each one links to (ideally) one product but since the SKU is not unique I assome that this has a list/collection of Products in it.
public class PurchaseInfo
{
public int Id { get; set; }
public int Quantity { get; set; }
public int InvoiceId { get; set; }
public Invoice Invoice { get; set; }
public int Item {get;set;}
public List<Product> Products { get; set; }
}
Product Class: I assome that there is an Id field (not shown) or a composite primary key somewhere
public class Product
{
public int Sku { get; set; }
public int ProductLineId { get; set; }
public int ItemCount { get; set; }
public List<PurchaseInfo> PurchaseInfos { get; set; }
}
Hopefully you can take this a get what you need. If this is way off, please update question with the class definitions (you can remove unneeded properities if you want) so a better answer can be produced.

join diffrent field type in linq

join diffrent field type in linq
public partial class Product
{
public int ID { get; set; }
public string CategoryID
{
get { return Myclass.increse(CategoryID); }
set { CategoryID = value; }
}
public string Name { get; set; }
}
public partial class ProductCategory
{
public int ID { get; set; }
public string Name { get; set; }
}
var query = (from c in dContext.ProductCategories
join p in dContext.Products
on Myclass.EncodeMD5(c.ID.ToString()) equals p.CategoryID
select new { id = p.ID, cat = p.CategoryID, name = p.Name, cat1 = c.Name }
).ToList();
The field should be converted to string
Then function runs EncodeMD5
error:
LINQ to Entities does not recognize the method 'System.String
EncodeMD5(System.String)' method, and this method cannot be translated
into a store expression.
You cannot call arbitrary .NET methods in LINQ-to-(some database backend) - the entire point of EF (etc) is that it wants to create SQL from your expression - something involving a where clause. It can work with simple properties and operators, and a few methods it knows about and can map into SQL, but it can't perform something it has never heard of (increse, EncodeMD5, etc) how would it know what SQL to write?
With something like MD5, your best bet would be to store the MD5 hash in the underlying table along with the ID. Likewise with the CategoryID's "increse" (whatever that is). So your query would end up working off these pre-calculated values:
on c.IDHash equals p.CategoryIDHash

Inserting item into DataGrid

I have the following tables:
I'm using Entity Framework Database First, therefore the following entity class is generated:
public partial class Sal1 {
public string SaleID { get; set; }
public string ItemID { get; set; }
public int Quantity { get; set; }
public decimal Total { get; set; }
public virtual Item Item { get; set; }
public virtual Sale Sale { get; set; }
}
Then put the Sal1 rows into a datagrid like this:
private List<Sal1> saleItems = new List<Sal1>();
...
var query = from sa in db.Sal1
where sa.SaleID.Equals(tempSale)
select sa;
foreach(Sal1 si in query) {
saleItems.Add(si);
}
...
dgDetails.ItemsSource = saleItems;
But it turns out like this:
My question is, how should I tweak the query above so that I get the equivalent of the following SQL:
select T0.SaleID, T0.ItemID, T1.Name, T0.Quantity, T0.Total
from Sal1 T0 inner join Item T1 on T0.ItemID = T1.ItemID;
Thanks in advance.
EDIT: I seem to have found a solution, but I had to do this:
var query = from sa in db.Sal1
where sa.SaleID.Equals(tempSale)
select new { sa.SaleID, sa.ItemID, sa.Item.Name,
sa.Item.Manufacturer, sa.Quantity, sa.Total };
And I had to change the type of saleItems to object.
private List<object> saleItems = new List<object>();
Is this the best way to do it?
Just like SQL, LINQ also supports JOINs. You can read more about their syntax here. You should change your query accordingly to get your results. Instead of spoonfeeding the exact answer, I'm guiding you to a more detailed explanation, as it contains valuable information that will help you in the future too.

Entity Framework LINQ Get all items part of another collection

Get all the NWatchRelation records from the DBContext that overlap those in the relationsCollection.
The same Id, RelatedNodeId, and RelationType (enum: int) should be what's considered a match.
public class NWatchRelation : INWatchRelation
{
public int Id { get; set; }
public int NodeId { get; set; }
public NWatchNode Node { get; set; }
public int RelatedNodeId { get; set; }
public NWatchNode RelatedNode { get; set; }
public NWatch.NWatchRelationType RelationType { get; set; }
}
INWatchRelation[] relationsCollection = GetRelations();
You can do a LINQ join between these 2 collections.
var result = from a in db.NWatchRelations.AsEnumerable()
join b in relationsCollection on a.RelatedNodeId equals b.RelatedNodeId
&& a.Id equals b.Id
&& a.RelationType equals b.RelationType
select a;
The only way you can do that fully in LINQ to Entities is to manually compose UNION ALL query by using Queryable.Concat like this:
IQueryable<NWatchRelation> query = null;
foreach (var relation in relationsCollection)
{
var m = relation;
var subQuery = db.NWatchRelations
.Where(r => r.Id == m.Id
&& r.RelatedNodeId == m.RelatedNodeId
&& r.RelationType == m.RelationType);
query = query == null ? subQuery : query.Concat(subQuery);
}
But please note that it's a limited approach and will not work if the relationsCollection is big.
You could create a kind of unique key using the three values:
//To create a unique key (an string, which is a primitive type) combining the three values
var keys=relationsCollection.Select(e=>e.Id+"-"+e.RelatedNodeId+"-"+ ((int)e.RelationType)).Distinct();
var query=db.NWatchRelations.Where(r=>keys.Any(k=>k == (SqlFunctions.StringConvert((double)r.Id)+"-"+
SqlFunctions.StringConvert((double)r.RelatedNodeId )+"-"+
SqlFunctions.StringConvert((double)((int)r.RelationType)) ));
If your NWatchRelations table doesn't have many rows or relationsCollection is a small collection, please, use one of the alternatives that were proposed earlier at your convinience.
Also you can have the directly linked like this
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public NWatchRelation()
{
this.INWatchRelation = new HashSet<INWatchRelation>();
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<INWatchRelation> INWatchRelation { get; set; }
But the entiry relation must be liked like this in order to work properly
Then you could select/list it like this
db.NWatchRelation.INWatchRelation.ToList();

Categories