Linq query projecting Id's not names - c#

I have a simple setup.
FRUIT Table
Id Name
1 Gala Apples
2 Navel Oranges
3 Peach
4 Mandarin Oranges
5 Kiwi
6 Fuji Apples
INTERSECT TABLE
FruitId CrossRefFruitId
1 6
2 4
So if the user is looking at Gala Apples (1) they may also be interested in Fuji Apples (6).
I have a simple model that returns the Fruit
Model
public class FruitCategory
{
public int Id { get; set; }
public string FruitName { get; set; }
}
EF:
public IEnumerable<FruitCategory> GetFruitbyId(int id)
{
return _context.FruitTable.Where(q => q.FruitId == id);
}
This works fine but now I also want to add the "SeeAlso" fruit. So I create a crossref modal and a new field in my Model.
CrossReff Model
public class FruitCrossRef
{
public int Id { get; set; }
public string CrossRefName { get; set; }
}
Model
public class FruitCategory
{
public int Id { get; set; }
public string FruitName { get; set; }
public List<FruitCrossRef> SeeAlsoFruits {get; set;}
}
Now I come to my difficulty....how to get a LINQ projection that will populate this model.
Since I don't know how to write this I open LINQPAD and start hacking and googling.
So far this is what I have come up with but it returns the MATCHING id in the intersect table but what I want is to return the CrossReferenced ID and the FruitName in the Fruit Table.
var seeAlso =
(from frt in FruitTable
where frt.Id == 1
select frt.Id)
.Intersect
(from frtCross in IntersectTable
select frtCross.FruitId);
seeAlso.Dump();
Now I can see a path where I can get the job done by making several loops getting the seealso references and then for each one going back to the Fruit table and getting that record...however it seems there ought to be a way to leverage the power of the relationship and project my fully populated model???
Code Correction
For anyone else who may come across this there were a couple syntax errors in the answer but the answer was still exactly what I wanted.
var seeAlso =
(from frt in FruitTable
join intsec in IntersectionTable
on frt.Id equals intsec.CrossRefFruitId
where intsec.FruitId == 1
select frt);
seeAlso.Dump();
Remember this was written for Linq Pad a little more tweaking is needed for production code.

What you ultimately want is a list of FruitItems that are also of interest based on some other fruit, given that fruits Id. Therefore rather than selecting the fruit corresponding to the Id you want, you should select the Fruits that join to the Intersection table with that Id. For example.
var seeAlso =
(from frt in FruitTable
join intsec in IntersectionTable
on frt.Id = intsec.CrossRefFruitId
where intsec.FruitId == 1);

Related

Build LINQ query for One-to-One and filtering by child

I have very simple model with two entities related as One-to-One via reference navigation properties:
class Post //"Main"
{
public int RowId { get; set; }
public string SomeInfo { get; set; }
public FTSPost FTSPost { get; set; }
}
class FTSPost //"Child"
{
public int RowId { get; set; }
public Post Post { get; set; }
public string Content { get; set; }
public string Match { get; set; }
}
I'm not sure if it's important but FTSPost represents virtual table of FTS5 SQLite and I'm following this exmaple so FTSPost is used for free text search capability. What I need to do is just to retrieve the whole rows of data from both tables based on the text search result and not just the text itself as in the example. I.e. I'm searching by Content of FTSPost and need to get respective SomeInfo of Post, not just Content itself. Note: Match is the service property which is used for searching and it's bound to the name of FTSPosts table. It works, so I can retrieve just Content as in the example.
The obvious (for me) query doesn't work, it yields zero results:
//Doesn't work! -- zero results :(
results = _context.Posts.Where(m => m.FTSPost.Match == "text for search");
SELECT *
FROM "Posts" AS "m"
LEFT JOIN "FTSPosts" AS "f" ON "m"."RowId" = "f"."RowId"
WHERE "f"."FTSPosts" = "text for search"
However, the following nice raw SQL query works well but I can't wrap my mind how to make it in LINQ! I tried to repeat it as is, with double "from" clauses but it converts to CROSS JOIN and doesn't work either. Please, help! P.S. I use EF Core 5.
//It works!!
SELECT *
FROM "Posts" AS "m", "FTSPosts" AS "f"
WHERE "f"."FTSPosts" = "text for search" AND "m"."RowId" = "f"."RowId"
The reason why "obvious" query didn't work was the way how the one-to-one relation was created. Even though the one-to-one seems to be mirrored, the way matters. After swaping Message and FTSMessage in the following code everything started to work. LEFT JOIN in the generated SQL query was replaced by INNER JOIN.
Correct code:
modelBuilder.Entity<Message>(
x =>
{
x.HasOne(fts => fts.FTSMessage)
.WithOne(p => p.Message)
.HasForeignKey<Message>(p => p.RowId);
});

How To Select All Columns From Inherited Class with LINQ and C#

I've got an entity I created using Entity Framework's auto code gen and don't want to modify that entry, as I regenerate that code on every table alter. For the sake of a single EF query, I've created an inherited entity that adds one extra column, SessionId as showing below.
public class SpeakerWithSessionIdAdded : Speaker
{
public int SessionId { get; set; }
}
Where Speaker is defined as below but with about 100 total column (I do know there should be less columns, but it is was it is right now).,
public class Speaker {
public int Id {get;set;}
public string firstName {get;set;}
public string lastName {get;set;}
... MANY MORE COLUMNS
}
I have a LINQ query as follows where I want to get out all the columns plus the one I added (SessionId). Is there any easy way to do this without using either reflection, or by hand listing every column I want to show?
var speakersWithSessionIdAdded = await
(from sessionPresenter in _dbContext.SessionPresenter
join speaker in _dbContext.Speakerson sessionPresenter.SpeakerId equals attendee.Id
where sessionIds.Contains(sessionPresenter.SessionId)
select new SpeakerWithSessionIdAdded
{
SessionId = sessionPresenter.SessionId,
Id = attendee.Id,
UserFirstName = attendee.UserFirstName,
UserLastName = attendee.UserLastName,
Email = attendee.Email,
Username = attendee.Username,
UserBio = attendee.UserBio,
AllowHtml = attendee.AllowHtml
.. I DON'T WANT TO LIST OUT 100 MORE LINES HERE FOR EVERY PROPERTY
}).ToListAsync();
return speakersWithSessionIdAdded.ToLookup(r => r.SessionId);
}

C# aggregate data from mulitple columns taken from a resultset of an RAW SQL query with Entity Framework6 and add those data to Lists in a ViewModel

I want to process the data from my database-query using raw SQL in Entity Framework 6 as follows and need a best practice by the use of native functions of C# and LINQ:
PICTURE 1: Resultset taken from database
I have created a class for the resultset above, it looks like that:
public class ProjectQueryModel {
public int Project { get; set; }
public string Projectname { get; set; }
public int RoomId { get; set; }
public string RoomName { get; set; }
public int? EmployeeId { get; set; }
public string EmployeeName { get; set; }
public int? QualificationId { get; set; }
public string QualificationName { get; set; }
public int? QualificationLevel { get; set; }
}
To this point the query works and I got all my data from it stored in a List of type ProjectQueryModel. Now I want to add this data to my ViewModel and don't know how to use the functions C# offers me to process the data of resultsets. How can I achieve the following by saving every entity of type ProjectViewModel in a List, whose objects have the following structure:
PICTURE 2: data organisation in ViewModel
An example dataset for project 1 in the target list should look like this:
ProjectId = 1
Projectname = T1
RoomId = 1
RoomName = Delta Room
======================
Employees *(Attribute of type List <ProjectEmployeesVM> )*
[0].EmployeeId = 2
[0].EmployeeName = Mee
[0].EmployeeQualifications *(Attribute of type List<EmployeeQualificationsVM)*
[0].EmployeeQualifications[0].QualificationId = 1
[0].EmployeeQualifications[0].QualificationName = Programmer
[0].EmployeeQualifications[0].QualificationLevel = 3
...any other qualification of the employee
[1].EmployeeId = 2
[1].EmployeeName = Mee
[1].EmployeeQualifications
[1].EmployeeQualifications[0]
...Any other employee in this project and all of his qualifications
What I also want to achieve is to save a empty list in case the project has no employees, because the resultset is achieved by the use of LEFT OUTER JOINS. For the qualifications it is not necessary, because every employee has at least one qualification.
VERY BIG THANKS in advance
I'm supposing you have a constructor in every class involved that takes all the properties as arguments.
Here's how i would do it:
List<ProjectQueryModel> queryResult = ...;
List<ProyectViewModel> views = queryResult
// Take all the rows that belong to one proyect
.GroupBy(m => m.Proyect)
// Convert every group into a ProyectViewModel
// First use Select to Map every Group into a new Proyect using a function that takes a group of rows and return a Proyect
// Then we use Aggregate inside that mapping function to collapse the entire group of rows into a single ProyectViewModel
// We'll need a contructor in ProyectViewModel that gives us a completly empty instance
// Aggregate takes a starting point, and a function that takes that starting point, and passes it every element of the IEnumerable we're using. The return value of that function is the "new starting point".
// Using this we'll build the Proyect from every row.
.Select(g => g.Aggregate(new ProyectViewModel(), (pvm, nxtRow) => {
// Check if we haven't initialized the instance, and do so.
if (pvm.ProyectId == null) pvm.ProyectId = nxtRow.Proyect;
if (pvm.ProyectName == null) pvm.ProyectName = nxtRow.ProyectName;
if (pvm.RoomId == null) pvm.RoomId = nxtRow.RoomId;
if (pvm.RoomName == null) pvm.RoomName = nxtRow.RoomName;
if (pvm.Employees == null) pvm.Employees = new List<ProyectEmployeeViewModel>();
// If the row has an employee
if (nxtRow.EmployeeId.HasValue) {
// If the Employee is not yet on the Proyect add it
if (!pvm.Employees.Any(e => e.EmployeeId == nxtRow.EmployeeId))
{
// This constructor should create the empty List of Qualifications
pvm.Employees.Add(new ProyectEmployeeViewModel(nxtRow.EmployeeId.Value, nxtRow.EmployeeName);
}
// If the row has a qualification
if (nxtRow.QualificationId.HasValue)
{
// Find it's employee
pvm.Employees.First(e => e.EmployeeId == nxtRow.EmployeeId)
// Add the current row's qualification to the employee
.Qualifications.Add(new EmployeeQualificationsViewModel(nxtRow.QualificationId.Value, nxtRow.QualificationName, nxtRow.QualificationLevel.Value));
}
}
// Return the Proyect with the changes we've made so we keep building it
return pvm;
})).ToList();
LINQ is quite a beauty isn't it?
There might be errors, but use this as a starting point.
Start by making sure that your database has the right foreign key constraints between your tables, then update your model. This will automatically create the correct navigation properties. I've assumed they will be called Employees and Qualifications, but change as appropriate.
Then your query just becomes:
var result=db.Projects
.Include(p=>p.Employees)
.Include(p=>p.Employees.Select(e=>e.Qualifications))
.Where(p=>p.id==1)
.AsEnumerable(); // or .ToList() if you prefer
Then just pass IEnumerable<Project> to your view (or just Project if your view will always only get 1 Project -- in that case, just end the query with .First() instead of .AsEnumerable()) . Unless of course you like creating ViewModels, but I'm guessing you don't and this isn't a project that needs the added complexity or abstractions.
The above code assumes you have the following tables:
Project (int Id, varchar(50) Name, int RoomId)
Room (int Id, int Name)
Employee (int Id, varchar(50) Name)
Qualification (int Id,varchar(50) Name, int Level)
Cross Reference tables:
ProjectEmployees (int ProjectId, int EmployeeId)
EmployeeQualifications (int EmployeeId, int QualificationId)
Foreign Keys:
Project.RoomId -> Room.Id
ProjectEmployees.ProjectId -> Project.Id
ProjectEmployees.EmployeeId -> Employee.Id
EmployeeQualifications.EmployeeId -> Employee.Id
EmployeeQualifications.QualificationId -> Qualification.Id

How to properly make a LINQ query with clauses based on complex calculated data

I have a pretty vast experience with LINQ to Entities and LINQ to SQL but this query is making my head burn.
I need some guidance on how to successfully create a clean LINQ to Entities query, when it has conditional clauses based on calculated columns, and when projecting the data into an object, to also use that calculated columns.
The projected results class is the following:
public class FilterClientsResult
{
public enum ClientType
{
ALL,
LIST,
VIP
}
public int ClientId { get; set; }
public string Document { get; set; }
public string Email { get; set; }
public string LastName { get; set; }
public string Name { get; set; }
public ClientType Type { get; set; }
public int Visits { get; set; }
}
Then the data comes from a primary source and 3 complimentary sources.
The primary source is the Client entity, that among all its properties, it has all the projected properties, with the exception of the last 2 (Type and Visits)
The other sources are:
Subscription, where 1 Client can have many Subscription.
ClientAccess, where 1 Subscription can have many ClientAccess.
GuestListClient, where 1 Client can have many GuestListClient.
So the ClientType Type property is set to FilterClientsResult.ClientType.ALL when that Client is related with both ClientAccess and GuestListClient entities.
It's set to FilterClientsResult.ClientType.VIP when that Client is related only to ClientAccess entities, and FilterClientsResult.ClientType.LIST when that Client is only related to GuestListClient entities.
Then the Visits property is set to the sum of ClientAccess and GuestListClient entities that the Client is related to.
So the query I need should be capable of filtering clients by FilterClientsResult.ClientType but also projecting that FilterClientsResult.ClientType.
And I also need to do filtering by a Visits range, but also projecting that Visits value.
So, what is the optimal way to build a query like that
This works best with query syntax, using the let keyword to store intermediate results as variables:
from c in Clients
let vipCount = c.Subscriptions.SelectMany(s => s.ClientAccesses).Count()
let listCount = c.GuestListClients.Count()
select new FilterClientsResult {
c.ClientId,
...
Type = vipCount > 0 ? listCount > 0 ? ClientType.ALL
: ClientType.VIP
: listCount == 0 ? 0
: ClientType.LIST,
Visits = vipCount + listCount
}
(not syntax checked)
Notice that the ClientType enum must be known to EF, otherwise you first have to project the required properties where Type is an int. Then, after an AsEnumerable(), cast the int to the enum.
Also notice that there is an indecisive value when both counts are 0.

Linq to compare 2 different lists and select the outer join

I have 2 different classes that represent 2 types of data. The first is the unposted raw format. The second is the posted format.
public class SalesRecords
{
public long? RecordId { get; set; }
public DateTime RecordDate { get; set; }
public string RecordDesc { get; set; }
// Other non-related properties and methods
}
public class PostedSalesRecords
{
public long? CorrelationId { get; set; }
public DateTime RecordDate { get; set; }
public DateTime? PostedDate { get; set; }
public string RecordDesc { get; set; }
// Other non-related properties and methods
}
Our system has a list of sales records. These sales records are posted to a different system at a time determined by the users. I am creating a screen that will show all of the posted sales records along with the unposted sales records as a reconciliation. The datasource for my grid will be a list of PostedSalesRecords. What I need to do is find out which records out of the List<SalesRecords> that are not in List<PostedSalesRecords> and then map those unposted SalesRecords to a PostedSalesRecords. I am having trouble finding a way to quickly compare. Basically I tried this, and it was EXTREMELY slow:
private List<SalesRecords> GetUnpostedSalesRecords(
List<SalesRecords> allSalesRecords,
List<PostedSalesRecords> postedSalesRecords)
{
return allSalesRecords.Where(x => !(postedSalesRecords.Select(y => y.CorrelationId.Value).Contains(x.RecordId.Value))).ToList();
}
My biggest issue is that I am filtering through a lot of data. I am comparing ~55,000 total sales records to about 17,000 posted records. It takes about 2 minutes for me to process this. Any possible way to speed this up? Thanks!
You can try an outer join, please see if this helps with the performance:
var test = (from s in allSalesRecords
join p in postedSalesRecords on s.RecordId equals p.CorrelationId into joined
from j in joined.DefaultIfEmpty()
where j == null
select s).ToList();
Or in your implementation, you can create a dictionary of only Ids for postedSalesRecords and then use that collection in your query, it'll definitely help with performance because the lookup time will be O(1) instead of traversing through the whole collection for each record.
var postedIds = postedSalesRecords.Select(y => y.CorrelationId.Value)
.Distinct().ToDictionary(d=>d);
return allSalesRecords.Where(x => !(postedIds.ContainsKey(x.RecordId.Value))).ToList();
Using a left outer join as described on MSDN should work much more efficiently:
private List<SalesRecords> GetUnpostedSalesRecords(
List<SalesRecords> allSalesRecords,
List<PostedSalesRecords> postedSalesRecords)
{
return (from x in allSalesRecords
join y in postedSalesRecords on x.RecordId.Value
equals y.CorrelationId.Value into joined
from z in joined.DefaultIfEmpty()
where z == null
select x).ToList();
}
This will probably be implemented with a hash set. You could implement this yourself (arguably clearer that way): build a HashSet<long> of the ID values in one or both lists to ensure that you don't need repetitive O(N) lookups each time you go through the outer list.

Categories

Resources