C# - Entity Framework - Join method - c#

I have a table called "PublicUserOfferingSignUp" which contains the following columns.
Id
PublicUserId - foreign key to PublicUser.Id
OfferingId
Created
My application is using Entity Framework, but I am getting stuck with how to join from the PublicUserOfferingSignUp table to the PublicUser table.
I want to obtain a list of PublicUserOfferingSignUp records but ordered by the Name column of the PublicUser table.
Currently I have this ....
return DataBase.PublicUserOfferingSignUps.Join(PublicUser,
But I can't seem to work it out, any ideas ....
Steven
Can anybody help.

Something like that
DataBase.PublicUserOfferingSignUps.Join(Database.PublicUsers,
puosu => puosu.PublicUserId,//PublicUserOfferingSignUps key
pu => pu.Id,//PublicUser key
(puosu, pu) => new {
publicUsersOfferingSignUp = puosu,//we take all data from PubliUserOfferingSignUps
puName = pu.Name//and Name from PublicUser
})
.OrderBy(x => x.puName)//we order by PublicUser Name
.Select(x => x.publicUsersOfferingSignUp );//we take only the PublicOfferingSignUps
Edit : as #M.Schenkel noticed, it would be easier to have a
public virtual PublicUser PublicUser {get;set;}
in your PublicUserOfferingSignUp model
then the query would be
DataBase.PublicUserOfferingSignUps
.OrderBy(puosu => puosu.PublicUser.Name);
easier, no ?

When you use the Entity Framework, the public user should be a property of your PublicUserOfferingSignUp-entity. If not, you can write a LINQ query to join them. For example:
var result = from pu in context.PublicUserOfferingSignUp
join u in context.PublicUser on u.id equals pu.PublicUserId
select pu;
(this code is untested, but should give you the idea).

Related

How to select top result for a given ID then join that into another table EF Core LINQ

For the life of me I am unable to google my way out of this one.
I have 2 tables within a database
1. Computers
2. UserLogins
Essentially, I'm trying to get the latest login entry from the "UserLogins" table, and join it with the corresponding entry in the "Computers" table.
This sounds simple enough, but I haven't sat through enough LINQ/EF Core courses yet to figure out how to do this correctly it seems.
Here is some SQL that I know functions how I expect it to:
SELECT * FROM ComputerInfo
LEFT JOIN (
SELECT LoginID, UserID, l.ComputerName, IpAddress, l.LoginTime FROM UserLogins as l
INNER JOIN (
SELECT ComputerName, MAX(LoginTime) as LoginTime
FROM UserLogins
GROUP BY ComputerName) as max on max.ComputerName = l.ComputerName and max.LoginTime = l.LoginTime
) as toplogin on toplogin.ComputerName = ComputerInfo.ComputerName
For reference, I am going to be implementing this in my Controller.cs class, and I am using :
EF Core (3.1.2)
ASP.NET Core (3.1)
I do have a couple queries I was experimenting with that return the results, but I can't join them without errors:
var computerQuery = _context.ComputerInfo
.OrderBy(on => on.ComputerName)
var userQuery = _context.UserLogins
.Select(p => p.ComputerName)
.Distinct()
.Select(id => _context.UserLogins
.OrderByDescending(p => p.LoginTime)
.FirstOrDefault(p => p.ComputerName == id))
.ToListAsync();
So I kind of found a shotty way to get this done I think. Not sure if it is correct but here is what I came up with:
I Created a new class called "ComputerInfoFull" which basically was just "ComputerInfo" && "UserLogins" combined, and used this for the linq query:
var initial = from computerInfo in _context.ComputerInfo
from userInfo in _context.UserLogins
.Where(o => o.ComputerName == computerInfo.ComputerName)
.OrderByDescending(o => o.LoginTime).Take(1)
select new ComputerInfoFull(computerInfo, userInfo);
I'm very sure there is a cleaner Lambda way of writing this, but I can't figure out how to make it work right. Too much stuff going on for my tiny brain to handle lol. If anyone has any ideas on how I can make this cleaner please let me know so I can learn.

Write a LINQ query for 2 tables

I have got 2 tables, Student and CourseTaken. I need to write a LINQ code that displays all CourseTaken, that has Active student status set as true.
I wrote part of the LINQ statement that will display all CourseTaken for a particular Id. How can I further filter it by showing the coursetaken for Active students? (S_ID in CourseTaken contains the student Id.)
List<CourseTaken> courseTakenList =
await dbcont
.CourseTaken
.Where(c => c.CId == courseId)
.ToListAsync();
public class Student
{
public int Id;
public string Name;
public string School;
public bool Active;
}
public class CourseTaken
{
public int CId;
public string CourseName;
public int S_Id;
}
Note: I need to use LINQ and Lambda expressions.
This will give you a list of all courses that has an active student, this assumes you have a navigation property from courses to student called Students
var result = dbcont.CourseTaken.Where(c => c.Students.Any(s => s.Active));
If this is not correct, i think you need to explain your structure better, whether this is Entity framework and you have the appropriate navigation property, and some example data
Update
No, I don't have navigation properties in place. Is there another way
I could get this done ?
Well you probably should, as you are going to have to query the database twice now.
var ids = dbcont.Students.Where(s => s.Active)
.Select(x => x.id)
.ToList();
var result = dbcont.CourseTaken.Where(c => ids.Contains(c.S_Id));
Lastly, take a look at a few entity framework tutorials, your column naming is a little weird, and you really need to hook this up in the spirit of EF. with navigation properties
It sounds to me that you need this query:
from ct in dbcont.CourseTaken
where ct.CId == courseId
join s in dbcont.Student.Where(s => s.Active) on ct.S_Id equals s.Id into gsc
where gsc.Any()
select ct
This is only returning a CourseTaken once, regardless of how many active students are taking the course, as long as their is at least one, of course.
int[] StudentsId =( from s in dbcont.Students
where s.Active ==true
select s.Id).ToArray<int>();
List<CourseTaken> courseTakenList = dbcont.CourseTaken.
Where(c=> StudentsId.Contains(c.S_Id) )
.ToList();
var result =
(from C in db.CourseTakens
join S in db.Students.Where(s => s.Active == true) on C.S_Id equals S.Id
select C
).ToList();
This can get only CourseTaken data. You can add Student data to select clause.

Entity Framework Union and Projection fail

This is a multi-progned question so please bear with me! I have two queries:
var dynamicResult = Repository.Select<Table1>()
.Where(b => b.InactivatedD == null)
.Select(b => b.Table2);
var staticResult = Repository.Select<Table2>()
.Where(b => b.column == "CONSTANT");
return dynamicResult.Union(staticResult).ToList();
This works fine. Now I have added an additional property to the the Table2 class and have instructed EF to ignore the field within my configuration like so:
Ignore(e => NewColumn);
This too is working well as I can set the field properly without EF throwing an exception. Now I don't know if there is an easy way to do what I want. In my first query Table1 has a column that I want to use to hydrate this new column on Table2 but I don't know of an easy way to do this. The only thing I have been able to come up with is:
var dynamicResult = Repository.Select<Table1>()
.Where(b => b.InactivatedD == null)
.Select(b => new Table2 { Column1 = b.Table2.Column1, NewColumn = b.SomeColumn ... <additional initialization> });
This is a little messy and the initialization would get pretty long since this entity has about 15 columns I'd need to hydrate. I could, of course, just traverse the association between Table2 and Table1 in my property rather than trying to set it in the above query but that seems like additional work and one more query to maintain. Additionally, when using the method above, my union no longer works. If my queries look like this:
var dynamicResult = Repository.Select<Table1>()
.Where(b => b.InactivatedD == null)
.Select(b => new Table2 { Column1 = b.Table2.Column1, NewColumn = b.SomeColumn })
var staticResult = Repository.Select<Table2>()
.Where(b => b.column == "CONSTANT")
.Select(b => new Table2 { Column1 = b.Table2.Column1, NewColumn = b.SomeColumn })
return dynamicResult.Union(staticResult).ToList();
I get an exception that the Entity or Complex type, Table 2, can not be constructed by an Entity Framework query which kind of has me at a loss. I understood why I was getting this error before I told EF to ignore the NewColumn but now I am not sure why this error is popping up.
In summation: is there a better way to hydrate my new column then what I have proposed above and can anyone identify why I am unable to union entities created using new from within a query?
Thanks!
It is never allowed to create an entity type in an EF query irrespective of which properties you address. The reason is that EF has no way to track such entities, because it did not materialize them itself.
You should define a type that looks like Table2, including the new column and project to that type in both queries. You will be able to union the queries.

LINQ Query - Only get Order and MAX Date from Child Collection

I'm trying to get a list that displays 2 values in a label from a parent and child (1-*) entity collection model.
I have 3 entities:
[Customer]: CustomerId, Name, Address, ...
[Order]: OrderId, OrderDate, EmployeeId, Total, ...
[OrderStatus]: OrderStatusId, StatusLevel, StatusDate, ...
A Customer can have MANY Order, which in turn an Order can have MANY OrderStatus, i.e.
[Customer] 1--* [Order] 1--* [OrderStatus]
Given a CustomerId, I want to get all of the Orders (just OrderId) and the LATEST (MAX?) OrderStatus.StatusDate for that Order.
I've tried a couple of attempts, but can seem to get the results I want.
private IQueryable<Customer> GetOrderData(string customerId)
{
var ordersWithLatestStatusDate = Context.Customers
// Note: I am not sure if I should add the .Expand() extension methods here for the other two entity collections since I want these queries to be as performant as possible and since I am projecting below (only need to display 2 fields for each record in the IQueryable<T>, but thinking I should now after some contemplation.
.Where(x => x.CustomerId == SelectedCustomer.CustomerId)
.Select(x => new Custom
{
CustomerId = x.CustomerId,
...
// I would like to project my Child and GrandChild Collections, i.e. Orders and OrderStatuses here but don't know how to do that. I learned that by projecting, one does not need to "Include/Expand" these extension methods.
});
return ordersWithLatestStatusDate ;
}
---- UPDATE 1 ----
After the great solution from User: lazyberezovsky, I tried the following:
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.Select(o => new Customer
{
Name = c.Name,
LatestOrderDate = o.OrderStatus.Max(s => s.StatusDate)
});
In my hastiness from my initial posting, I didn't paste everything in correctly since it was mostly from memory and didn't have the exact code for reference at the time. My method is a strongly-typed IQueryabled where I need it to return a collection of items of type T due to a constraint within a rigid API that I have to go through that has an IQueryable query as one of its parameters. I am aware I can add other entities/attributes by either using the extension methods .Expand() and/or .Select(). One will notice that my latest UPDATED query above has an added "new Customer" within the .Select() where it was once anonymous. I'm positive that is why the query failed b/c it couldn't be turn into a valid Uri due to LatestOrderDate not being a property of Customer at the Server level. FYI, upon seeing the first answer below, I had added that property to my client-side Customer class with simple { get; set; }. So given this, can I somehow still have a Customer collection with the only bringing back those 2 fields from 2 different entities? The solution below looked so promising and ingenious!
---- END UPDATE 1 ----
FYI, the technologies I'm using are OData (WCF), Silverlight, C#.
Any tips/links will be appreciated.
This will give you list of { OrderId, LatestDate } objects
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
.
UPDATE construct objects in-memory
var query = Context.Customers
.Where(c => c.CustomerId == SelectedCustomer.CustomerId)
.SelectMany(c => c.Orders)
.AsEnumerable() // goes in-memory
.Select(o => new {
OrderId = o.OrderId,
LatestDate = o.Statuses.Max(s => s.StatusDate) });
Also grouping could help here.
If I read this correctly you want a Customer entity and then a single value computed from its Orders property. Currently this is not supported in OData. OData doesn't support computed values in the queries. So no expressions in the projections, no aggregates and so on.
Unfortunately even with two queries this is currently not possible since OData doesn't support any way of expressing the MAX functionality.
If you have control over the service, you could write a server side function/service operation to execute this kind of query.

Complex EntityFramework query with multiple tables and many to many relationship

I'm having a heck of a time getting Entity Framework to do what I want. I'm writing a feed aggregator so I can add multiple rss feeds to a "FeedList" that will group all the individual podcasts and order by pubDate.
Classes (all but FeedListFeed have identity column called Id):
Feed (Id is identity primary key, has List Items property)
FeedItem (Id is identity primary key, as int FeedId and Feed Feed properties)
FeedList (FeedListName is string and List Feeds property)
FeedListFeed (many-to-many linking table, FeedListId and FeedId properties)
These mappings seems to work:
modelBuilder.Entity<FeedListFeed>().HasKey(x => x.FeedId).HasKey(x => x.FeedListId);
modelBuilder.Entity<FeedList>()
.HasMany(fl => fl.Feeds).WithMany(f => f.FeedLists)
.Map(t => t.MapLeftKey("FeedListId")
.MapRightKey("FeedId")
.ToTable("FeedListFeeds"));
Now what I want to do is get the latest 20 FeedListItem entries for Feeds in a FeedList given the FeedListName. I've come up with this, but is there a better way to do it? Will the query actually expand all the items, or will it be smart enough to do it on the SQL side?
var query =
from fl in ctx.FeedLists.Include("Feeds").Include("FeedItems")
from f in fl.Feeds
from fi in f.Items
where fl.FeedListName == id
orderby fi.PubDate descending
select fi;
List<FeedItem> items = query.Take(20).ToList();
If I try to link the tables manually using the Id columns, I get the error Invalid object name 'dbo.FeedListFeeds1'. If I took out the Lists that link the tables to each other would this help? Is there some other mapping that let this work?
var query =
from fl in ctx.FeedLists
join flf in ctx.FeedListFeeds on fl.Id equals flf.FeedListId
join fi in ctx.FeedItems on flf.FeedId equals fi.FeedId
where fl.FeedListName == id
orderby fi.PubDate descending
select fi;
Remove this line from your mapping ...
modelBuilder.Entity<FeedListFeed>()
.HasKey(x => x.FeedId)
.HasKey(x => x.FeedListId);
... because it has 2 issues:
1) If you want a composite key you must not chain HasKey but create the key via an anonymous type:
modelBuilder.Entity<FeedListFeed>()
.HasKey(x => new { x.FeedId, x.FeedListId });
2) (more important) This line lets EF consider FeedListFeed as an entity which it isn't in your model. The result is that EF creates a separate table for it with the name FeedListFeeds1 because FeedListFeeds is reserved as table name in your many-to-many mapping (.ToTable("FeedListFeeds")). For many-to-many mapping you don't need to create a class for the linking table. The linking table is managed by EF internally.
Edit
You can then also remove the FeedListFeed class completely of course.
For the query I would try then:
var query = from fi in ctx.FeedItems
where fi.Feed.FeedLists.Any(fl => fl.FeedListName == id)
orderby fi.PubDate descending
select fi;
I think you have all the necessary navigation properties in your model classes so that this query should be possible.

Categories