NHibernate queryover join many to many - c#

I am really banging my head with this NHibernate query :) to write a join.
I have many to many relation with Asset and Product. Please find below the table model
public class Asset
{
string Id { get; set; }
List<Product> Products { get; set; }
}
public class Product
{
string Id { get; set; }
List<Asset> Assets { get; set; }
}
Here is the code i am trying with QueryOver
Product productAlias = null;
Asset assetAlias = null;
var query = Session.QueryOver<Asset>(()=>assetAlias);
if (!string.IsNullOrEmpty(title))
query.WhereRestrictionOn(x => x.Title).IsLike(title, MatchMode.Anywhere);
if (!string.IsNullOrEmpty(productNumber))
{
query.WhereRestrictionOn(asset => asset.Products.First().Id).Equals(productNumber);
}
var result = query.List<Asset>();
Can anyone help how to write the join queryover so that i want to find all the asset whose title is like title and the productnumber is equal to productnumber?
I am not getting the result with the above code.
The sql query i am trying to achieve is :
select a.* from Asset a ,
ManyToManyTable b on a.mat_id=b.mat_id
where a.title like '%test%' and b.prod_no='212300733'
Thanks

You need to use .JoinQueryOver() to change the context of what you're placing the WHERE restrictions on. Learn more at http://blog.andrewawhitaker.com/blog/2014/03/16/queryover-series-part-2-basics/. This should be close to what you are looking for. You state that you're looking for a left join, but since the join here is only applied when there is a product number specified, there should be no problems with that. It's possible to apply .Left.JoinQueryOver() if you find you need it though.
var query = Session.QueryOver<Asset>();
if (!string.IsNullOrEmpty(title))
query = query.WhereRestrictionOn(x => x.Title).IsLike(title, MatchMode.Anywhere);
if (!string.IsNullOrEmpty(productNumber))
{
query = query.JoinQueryOver(a => a.Products).Where(p => p.Id == productNumber);
}
var result = query.List<Asset>();

Related

Entity Framework Core - Not In

I'm trying to replicate a SQL statement in EF Core but cant seem to find a way to do it, to set the scene I have the following table structure
Slot -> SlotInstance -> SlotInstanceUser
(a Slot can have many SlotInstances, a SlotInstance can have many SlotInstanceUsers)
When a user registers for a SlotInstance a record is created in SlotInstanceUsers storing the SlotInstanceId and UserId - all good there.
I'm able to write SQL to get a list of slot instances which the user has not registered for e.g.
SELECT
S.StartDate, S.EndDate, S.StartTime, S.EndTime, S.DayOfWeek,
SI.Date
FROM
Slot S WITH (NOLOCK)
INNER JOIN
SlotInstance SI WITH (NOLOCK) ON S.Id = SI.SlotId
WHERE
SI.ID not in (
SELECT
SlotInstanceId
FROM
SlotInstanceUser SIU WITH (NOLOCK)
WHERE
SIU.UserId = #UserID
)
ORDER BY
SI.Date
But I just cant seem to replicate this in EF core - what am I missing?
You can write the LINQ query pretty much the same way as the SQL query. Just remember that in LINQ select is last, variables (aliases) are mandatory, and the equivalent of SQL NOT IN is !Contains. e.g.
var query =
from s in db.Slots
join si in db.SlotInstances on s.Id equals si.SlotId
where !(from siu in db.SlotInstanceUsers
where siu.UserId == userId)
select siu.SlotInstanceId).Contains(si.Id)
orderby si.Date
select new
{
s.StartDate, s.EndDate, s.StartTime, s.EndTime, s.DayOfWeek,
si.Date
};
But in EF Core you have more options, especially for joins, since normally the relationships (and associated joins) are encapsulated with navigation properties. So the model you are describing with words in EF Core/C# terms is something like
public class Slot
{
public int Id { get; set; }
// Other properties...
public ICollection<SlotInstance> SlotInstances { get; set; }
}
public class SlotInstance
{
public int Id { get; set; }
// Other properties...
public Slot Slot { get; set; }
public ICollection<SlotInstanceUser> SlotInstanceUsers { get; set; }
}
public class SlotInstanceUser
{
public int Id { get; set; }
// Other properties...
public SlotInstance SlotInstance { get; set; }
}
and the query would be like
var query =
from s in db.Slots
from si in s.SlotInstances
where !si.SlotInstanceUsers.Any(siu => siu.UserId == userId)
orderby si.Date
select new
{
s.StartDate, s.EndDate, s.StartTime, s.EndTime, s.DayOfWeek,
si.Date
};
(this actually translates to SQL NOT EXISTS, but that's not essential).
And if you don't need projection, but simply slot instances (with slot info) which the user has not registered for, then it would be simply
var query = db.SlotInstances
.Include(si => si.Slot)
.Where(si => !si.SlotInstanceUsers.Any(siu => siu.UserId == userId))

.NET Core Dapper: Get data by joining multiple tables on combined primary keys as object with list of objects

I've been using Dapper to access data from an older database with a specific structure and am having trouble with some issues. Many of them have some sort of answer but I can't seem to combine the solutions, here we go:
The object is to be created out of 3 different tables (inner join)
The objects are to be created in a nested object (a main object with lists of sub objects)
The required tables have combined primary keys (no separate unique keys)
The objects have identical property names (I'm not sure that this is an issue, haven't come far enough)
public class MainObject {
public long Id1 { get; set; }
public long Id2 { get; set; }
public string Id3 { get; set; }
public List<SubObject1> Subobject1 { get; set; }
public List<SubObject2> Subobject2 { get; set; }
public string OtherProps { get; set; }
}
public class SubObject1 {
public long Id1 { get; set; }
public long Id2 { get; set; }
public string Id3 { get; set; }
}
public class SubObject1 {
public long Id1 { get; set; }
public long Id2 { get; set; }
public string Id3 { get; set; }
}
I've been trying to combine issues 1 and 2 as described in this StackOverflow answer. After that I've been trying to add issue 3 as described in this StackOverflow answer, but haven't been able to make it work. An error I get frequently is System.ArgumentException: When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id, so I'm not sure I even understand the entire concept.
My query (which is able to return multiple rows) is structured as:
SELECT MainObject.*, SubObject1.*, SubObject2.*
FROM MainObject
INNER JOIN SubObject1 ON MainObject.Id1 = SubObject1.Id1
AND MainObject.Id2 = SubObject1.Id2 AND MainObject.Id3 = SubObject1.Id3
INNER JOIN SubObject2 ON MainObject.Id1 = SubObject2.Id1
AND MainObject.Id2 = SubObject2.Id2 AND MainObject.Id3 = SubObject2.Id3
WHERE MainObject.OtherProps = 'SomeValue'
Preferable the output would be of type List<MainObject>.
I'm open to all remarks and hints
E: The reason we chose Dapper is because we're reluctant to use Entity Framework and our current mapping has performance issues. At the moment we query for List, which loops and queries for every SubObject1 and SubObject2 separately (thus executing a lot of queries).
Good morning mate
Look at your simple query I suggest that you use the Linq, it would be easier and have the resource you want ...
The dapper is used to query more complex and heavy ones where you select the fields eg: "SELEC a.id as a_id, B.id as b_id" and then manually map.
I suggest in this case to use Linq.
Linq example:
var blogs1 = context.Blogs
.Include(b => b.Posts.Select(p => p.Comments))
.Include(b => b.Users)
.Include(b => b.Users.City)
.ToList();
After trying to combine the different solutions for a while I stumbled on an article that took a different approach. Instead of trying to map the objects from a single query, it split up the joins in a QueryMultiple statement. I then used Linq to map the multiple objects as one.
public List<MainObject> GetMainObjects(string conn)
{
List<MainObject> mos = new List<MainObject>();
using (IDbConnection connection = new SqlConnection(conn))
{
SqlMapper.GridReader results = connection.QueryMultiple(
$"SELECT * FROM MainObject; " +
$"SELECT * FROM SubObject1; " +
$"SELECT * FROM SubObject2;");
mos = results.Read<MainObject>().ToList();
IEnumerable<SubObject1> so1s = results.Read<SubObject1>();
mos.ForEach(mo => mo.SubObject1 = new List<SubObject1>());
mos.ForEach(mo => mo.SubObject1.AddRange(
so1s.Where(so1 => mo.Id1 == so1.Id1 && mo.Id2 == so1.Id2 && mo.Id3.Equals(so1.Id3))
));
IEnumerable<SubObject2> so2s = results.Read<SubObject2>();
mos.ForEach(mo => mo.SubObject2 = new List<SubObject2>());
mos.ForEach(mo => mo.SubObject2.AddRange(
so2s.Where(so2 => mo.Id1 == so2.Id1 && mo.Id2 == so2.Id2 && mo.Id3.Equals(so2.Id3))
));
}
return mos;
}

Filter list of entity objects by string child object property using Linq Lambda

I am trying to return an IQueryable lands filtered by a child object property Owner.Name. Is working well with the query style solution, but I want to use a lambda one.
On short these are my classes mapped by EntityFramework:
public class Land
{
public int Id { get; set; }
public virtual ICollection<Owner> Owners { get; set; }
}
public class Owner
{
public int Id { get; set; }
public string Name { get; set; }
public int LandId { get; set; }
public virtual Land Lands { get; set; }
}
The query which is working fine:
var list = from land in db.Lands
join owner in db.Owners on land.Id equals Owner.LandId
where owner.Name.Contains("Smit")
select land;
I was trying using this:
var list = db.Lands.Where(lnd => lnd.Owners.Count() > 0 &&
lnd.Owners.Where(own => own.Name.Contains("Smit")).Count() > 0);
It works only for small lists, but for some with thousands of records it gives timeout.
Well, one issue which may be causing the speed problem is that your lambda version and your non-lambda versions do very different things. You're non lambda is doing a join with a where on one side of the join.
Why not just write the lambda equivalent of it?
var list = db.Lands.Join(db.Owners.Where(x=> x.Name.Contains("Smit")), a=> a.Id, b => b.LandId, (a,b) => a).toList();
I mean, that is the more direct equivalent of your non lambda
I think you can use this one:
var list = db.Lands.Where(lnd => lnd.Owners.Any(x => x.Name.Contains("Smit")));
Try something more straightforward:
var lands = db.Owners.Where(o => o.Name.Contains("Smit")).Select(o => o.Lands);
You just need to make sure that Owner.Name is not null and LINQ will do the rest.

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 **/
});

Any of the properties equals any of a list of objects

I have a problem in Entity-Framework, using Code-First, that I couldn't solve.
Having entities of the type
public class Product {
public int ID {get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category {
public int ID {get; set;}
public virtual ICollection<Product> Products { get; set; }
// rest omitted
}
in my database, i try to get all Products that have at least one Category from a list of given Categories. I need an Expression as this expression is combined with other expressions later.
Ie. i tried:
var searchFor = new List<Category>{...};
var expression = product => product.Categories.Any(cat => searchFor.Contains(cat))
Executing this later against a DbContext
context.Products.Where(expression).ToList();
creates an exception stating mainly that This context supports primitive types only.
Changing it to
var expression = product => product.Categories.Any(
cat => searchFor.Any(d => d.ID == cat.ID));
to get rid of the object comparison didn't help. I'm stuck. How can I manage that?
You should get rid of List<Category>, replacing it with a list of IDs, like this:
// I'm assuming that ID is of type long; please fix as necessary
var searchFor = new List<long>{...};
var expression = product =>
product.Categories.Any(cat => searchFor.Contains(cat.ID))
If you've already got a list of categories, you can build a list of IDs outside the query:
var searchForIds = searchFor.Select(x => x.ID).ToList();
var query = context.Products
.Where(product => product.Categories
.Any(cat => searchForIds.Contains(cat.ID)));
I don't know that that will work, but it might. (Apologies for the indentation... it's just to avoid scrolling.)

Categories