EF and LINQ querying by subcollection properties - c#

I'm trying to order a list of "parent" items based on a value in its sub-collection's sub-collection. Here's the specifics...
I have a Film entity (mapped to the Films table) that has a one-to-many collection of Release entities (mapped to the Releases table). Each Release has one or more ReleaseDate entities (mapped to the ReleaseDates table).
public class Film {
public int Id {get;set;}
public string Title {get;set;}
/* ... more properties here ...*/
}
public class Release {
public int Id {get;set;}
public int FilmId {get;set;}
public virtual Film Film {get;set;}
public virtual ICollection<ReleaseDate> ReleaseDates { get; set; }
/* ... more properties here ...*/
}
public class ReleaseDate {
public int Id {get;set;}
public DateTime Date {get;set;}
public int ReleaseId {get;set;}
public virtual Release Release {get;set;}
/* ... more properties here ...*/
}
Now, I want to order the Films by the earliest release date, but obviously a film could have no releases, and a release could have no release dates (again, 1-* relationships). The SQL equivalent would be...
SELECT * /* or whatever columns...*/
FROM dbo.Films F
LEFT OUTER JOIN dbo.Releases R ON R.FilmId = F.Id
LEFT OUTER JOIN dbo.ReleaseDates RD ON RD.ReleaseId = R.Id
ORDER BY RD.[Date] ASC /* or DESC */
How can I go about doing this?

var orderedFilms = Films.OrderBy(a=> a.Releases.Any() ?
a.Releases.Select(x=>x.ReleaseDates.Any() ?
x.ReleaseDates.Min(d=>d.Date).Date :
DateTime.Now).Min() : DateTime.Now);

Well, I changed my approach to this problem and resolved it based on the "normal" approaches out there, one of which was given as answer but subsequently deleted by the poster. What I ended up doing is moving my select down to the repository layer where I do have the DbContext and was able to do a "simple" left outer join style query like this...
var films = (from f in db.Films
join r in db.Releases on f.Id equals r.FilmId into FR
from a in FR.DefaultIfEmpty()
join d in db.ReleaseDates on a.Id equals d.ReleaseId into RRD
from b in RRD.DefaultIfEmpty()
orderby b.Date ascending
select f);
#KingKing, thanks for your answer, I think it may come in handy in some other places where we have these sort of aggregate fields based on properties of sub-collections or even properties of sub-sub-collections.

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))

How to get two table's common table using linq

I want to combine these two linq queries to single query
is it possible?
var chestProducts = (from w in WareHouse
join c in Chests on w.Id equals c.WareHouseId
join p in Products on c.Id equals p.ContainerId
where (p.IsContainerChest == true && w.Id == 1)
select p
).ToList();
var boxProducts = (from w in WareHouse
join b in Boxes on w.Id equals b.WareHouseId
join p in Products on b.Id equals p.ContainerId
where (p.IsContainerChest != true && w.Id == 1)
select p
).ToList();
var allProducts = chestProducts.AddRange(boxProducts);
Should I use two queries?
And is this relation is healty?
Edit: Boxes and Chests tables are simplifed they have different fields
OK, from your comments I can see that you are using EF6 with code first. In that case I would make use of Table per Hierarchy and put both Box and Chest into one table (they will be separate classes still). One (big) caveat: I have been working exclusively with EF Core for a while now, and I haven't tested this. But I have used this pattern repeatedly and it works nicely.
Your entities should look something like this:
public class WareHouse
{
[Key]
public int Id { get;set; }
public string Name {get;set;}
public ICollection<Container> Containers {get;set;}
}
public abstract class Container
{
[Key]
public int Id {set;set;}
public int WareHouseId {get;set;}
[ForeignKey(nameof(WareHouseId))]
public WareHouse WareHouse {get;set;}
public string Name {get;set;}
public ICollection<Product> Products {get;set;}
}
public class Box : Container
{
// box specific stuff here
}
public class Chest : Container
{
// chest specific stuff here
}
public class Product
{
[Key]
public int Id {set;set;}
public int ContainerId {get;set;}
[ForeignKey(nameof(ContainerId))]
public Container Container {get;set;}
}
And your context something like this:
public class MyContext : DbContext
{
public virtual DbSet<WareHouse> WareHouses {get;set;}
public virtual DbSet<Container> Containers {get;set;}
public virtual DbSet<Product> Products {get;set;}
protected override void OnModelCreating(ModelBuilder builder)
{
// puts the class name in a column, makes it human readable
builder.Entity<Container>().Hasdiscriminator<string>("Type");
// i don't think you need to do this, but if it doesn't work try this
// builder.Entity<Box>().HasBaseType(typeof(Container));
// builder.Entity<Chest>().HasBaseType(typeof(Container));
}
}
Then you can get all the products from the warehouse with id=1 like this:
int warehouseId = 1;
Product[] allProducts = myContext.WareHouses
.Where(wh => wh.Id == warehouseId)
.SelectMany(wh => wh.Container)
//.OfType<Box>() if you only want products in boxes
.SelectMany(wh => wh.Products)
.ToArray();
I know you said in your comment that you tend to use linq's lambda syntax, but I feel I should point out that you are doing a lot of unnecessary joins in your query syntax example. linq to entities will take care of all that for you if you have set things up correctly.
Try this:
var allProducts = chestProducts.Concat(boxProducts);
Or you can also use Union
var allProducts = Enumerable.Union(chestProducts, boxProducts);

C# Entity Framework CS1941

I am trying to use linq with Entity Framework. In below code first join is red squiggle and I have this error.
Severity Code Description Project File Line Suppression State
Error CS1941 The type of one of the expressions in the join clause is
incorrect. Type inference failed in the call to 'Join'
Code
var vals = (from o in db.Words
join r in db.Results
on o.Id equals r.root
join s in db.Senses on r.Id equals s.results_id
select o ).Take(10) ;
EDIT:
After comments and realized my mistaken code I have decided add my entity class.
When I run my project then entity framework creates Id and root_Id columns on Sql Server then I considered I can use Resultset.root equals Words.Id structure
public class Word{
[Key]
public int Id { get; set; }
...}
public class Result{
[Key]
public int Id { get; set; }
public virtual Word root { get; set; }
...}
public class Result{
[Key]
public int Id { get; set; }
public virtual Result result { get; set; }
...}
And Context class
public class DatabaseContext : DbContext{
...
public DbSet<Word> Words { get; set; }
public DbSet<Result> Results { get; set; }
public DbSet<Sens> Senses { get; set; }
...
}
Id and root isnot compatible types but when I change the query with compatible ones problem is continuing. What do I go wrong. Thanks. Greetings
r.Id and s.Id have different types. Check them.
Maybe they are int and long or something else.
From #OzanTopal:
For more information check this link: https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/compiler-messages/cs1941
You are joining 3 tables but the result is just:
select o?
What is the purpose of your join?
Check the answer above or check also this if it is different types:
on o.Id equals r.root
on r.Id equals s.Id
r.val== SomeVal
This error is because the Id properties of Sense and Result types are incompatible. They don't have to be same. For example, its Okay (from compiler's perspective) for one to be int and another to be long. However, for example, string and int or Guid or int would be incompatible. The solution is to make both types compatible if not same. I understand that you may not be able to do so since your entity model may be built from the existing database and you may not have authority or may not want to change the underlying type of the columns.
If you want to solve the compilation problem in the code itself then you will have to do explicit type casting:
var vals = (from o in db.Words
join r in db.Results
on o.Id equals r.root
join s in db.Senses on r.Id equals Convert.ToString(s.Id)
where r.val== SomeVal
select o ).Take(10);
This assumes that the Id property of the Result class is string while the Id of the Sense class is some numeric type.

Fetching complex objects by raw SQL query in Entity Framework

I would like to fetch from database complex object using single query. Let's look at the following example:
SELECT TableA.*, TableB.*
FROM TableA
INNER JOIN TableA.B_Id = TableB.Id
and corresponding classes:
public class QueryResult
{
public TableA A { get; set; }
public TableB B { get; set; }
}
public class TableA
{
public int Id { get; set; }
public string SomeContentA { get; set; }
public int B_Id { get; set; }
}
public class TableB
{
public int Id { get; set; }
public int SomeContentB { get; set; }
}
I would like to execute the raw SQL query from above against the database and get collection of QueryResult objects with correctly set A and B properties. So far I tried using SqlQuery method, but I only managed to get collection of QueryResult objects with nulls in A and B properties (apparently returned result set was not correctly binded to properties):
var results = ctx.Database.SqlQuery<QueryResult>(\\example_query).ToList();
Note that:
I shouldn't list manually columns in SELECT statement. TableA and TableB classes and SQL tables are likely to change over time, but those changes will be consistent.
Three queries (one to fetch IDs from TableA and TableB, second to fetch objects from TableA, third for objects from TableB) will hurt performance and I should try avoid it if possible.
I am using Entity Framework 4.3 and SQL Server 2012.
Thanks,
Art
You can still use regular EF constructions by just mapping your classes to their corresponding tables and forcing the join in LINQ-To-Entities:
using(var ctx = new MyDbContext())
{
return ctx.TableA
.Join(ctx.TableB, a=>a.B_Id, b=>b.Id, (a,b)=>
new QueryResult{TableA=a, TableB=b});
}
I think that's the only way, at least up to EF6.

Linq to Entities Join to Show Name in Lookup Table

I'm using EF Code First (hybrid, database generation disabled) and I have two models/tables. I'm try to select and return all values in T1 and one field in a reference/lookup table so I can perform filtering on the list without requerying the database. I need to have the value of ItemName available so I can do comparisons.
If I were using SQL I'd just do something like this:
SELECT s.*, im.ItemName
FROM Specs s
INNER JOIN ItemMake im ON s.ItemMakeID = im.ID
My classes look something like this:
public class Spec {
public int ID {get; set;}
public int ItemMakeID {get; set;}
[ForeignKey("ItemMakeID")]
public ItemMake itemMake {get; set;}
}
public class ItemMake {
public int ID {get; set;}
public string ItemName {get; set;}
}
Currently my Linq to EF query looks like this. It doesn't work. I can't get at the ItemName property like I need to.
var specs = (from s in db.Specs
join im in db.ItemMakes on s.ItemMakeID equals im.ID
orderby s.modelNo select s).ToList();
What am I doing wrong?
That's because you're selecting just s in select clause. Use anonymous type declaration to get ItemName too:
var specs = (from s in db.Specs
join im in db.ItemMakes on s.ItemMakeID equals im.ID
orderby s.modelNo select new { s, im.ItemName }).ToList();

Categories