Join with inner list - c#

I have this linq query:
var investorData = from investor in db.Investors
join investorLine in db.InvestorStatementLines
on investor.InvestorID equals investorLine.InvestorID
where investor.UserId == userId
select new InvestorViewModel()
{
InvestorId = investor.InvestorID,
InvestorName = investor.Name,
FundingDate = investor.FundingDate,
DueDate = investor.DueDate,
FundsCommitted = investor.FundsCommitted,
FundsInvested = investor.FundsInvested,
StatementLines =
db.InvestorStatementLines.Where(s => s.InvestorID == investor.InvestorID)
.Select(t => new InvestorStatementLineVM
{
Balance = t.Balance,
Credit = t.Credit,
Debit = t.Debit,
InvestorStatementLineDetails = t.Details,
Date = t.Date
}).ToList()
};
The viewmodel:
public class InvestorViewModel
{
public int InvestorId { get; set; }
public string InvestorName { get; set; }
public DateTime FundingDate { get; set; }
public DateTime? DueDate { get; set; }
public Decimal? FundsCommitted { get; set; }
public Decimal? FundsInvested { get; set; }
public List<InvestorStatementLineVM> StatementLines { get; set; }
}
What is happening is once I'm executing the query I'm getting 125 records, and that's the number of the StatementLines for that investor. So I'm getting 125 same records but I'm expecting one result which will have 125 statement lines in the inner list.
Is this query correct?

This is how you can do that with navigation properties
var investorData = from investor in db.Investors
where investor.UserId == userId
select new InvestorViewModel()
{
InvestorId = investor.InvestorID,
InvestorName = investor.Name,
FundingDate = investor.FundingDate,
DueDate = investor.DueDate,
FundsCommitted = investor.FundsCommitted,
FundsInvested = investor.FundsInvested,
StatementLines = investor.InvestorStatementLines
.Select(t => new InvestorStatementLineVM
{
Balance = t.Balance,
Credit = t.Credit,
Debit = t.Debit,
InvestorStatementLineDetails = t.Details,
Date = t.Date
}).ToList()
};

Use GroupJoin instead of Join: (_join x in y on x.a equals y.a
into z_)
var investorData = from investor in db.Investors
join investorLine in db.InvestorStatementLines
on investor.InvestorID equals investorLine.InvestorID
into investorLine
where investor.UserId == userId
select new InvestorViewModel()
{
InvestorId = investor.InvestorID,
InvestorName = investor.Name,
FundingDate = investor.FundingDate,
DueDate = investor.DueDate,
FundsCommitted = investor.FundsCommitted,
FundsInvested = investor.FundsInvested,
StatementLines = investorLine
.Select(t => new InvestorStatementLineVM
{
Balance = t.Balance,
Credit = t.Credit,
Debit = t.Debit,
InvestorStatementLineDetails = t.Details,
Date = t.Date
}).ToList()
};
Also instead of performing the sub-query just use the data from the join you just performed.
A better option, using entity framework, is using navigation properties and then you do not need to perform a join but you just have
InvestorStatementLines as a property of your investor.
To set the navigation properties:
public class InvestorViewModel
{
public int InvestorId { get; set; }
public string InvestorName { get; set; }
public DateTime FundingDate { get; set; }
public DateTime? DueDate { get; set; }
public Decimal? FundsCommitted { get; set; }
public Decimal? FundsInvested { get; set; }
public virtual ICollection<InvestorStatementLineVM> StatementLines { get; set; }
}
And the query will be as simple as:
var investorData = from investor in db.Investors
where investor.UserId == userId
select new InvestorViewModel()
{
InvestorId = investor.InvestorID,
....
StatementLines = investor.InvestorStatementLines.Select(....)
};

Related

Entity Framework Core: FromSqlInterpolated(). An item with the same key has already been added. Key: Id

I want to get a list of my friends' stories. It didn't work through LINQ, but I was able to write a good SQL query that really works and returns what I need. I use .NET 7 & EF Core 7.
int userId = 1;
StoryContainerRto test = await _context.StoryContainer
.FromSqlInterpolated($#"SELECT s."Id", s."AuthorId", u."Id", u."AccountHeaderUrl", u."AvatarUrl", u."Description", u."Email", u."IsAuthorOfQualityContent", u."IsDeleted", u."IsInShadowBan", u."IsVerifiedProfile", u."LinkInBio", u."MedalsAmount", u."Name", u."Nickname", u."PasswordHash", u."PasswordSalt", u."PhoneNumber", u."PhoneNumberPrefix", u."PhoneNumberVerefied", u."RegistrationDateTime", u."TelegramId", u."TelegramVerifyingChatId"
FROM ""StoryContainer"" as s
INNER JOIN ""StoryContentRefs"" as scr ON scr.""StoryId"" = s.""Id""
LEFT JOIN ""User"" as u ON u.""Id"" = s.""AuthorId""
LEFT JOIN ""StoryContentRefRtoUserRto"" as whoView ON whoView.""ViewedId"" = {userId} AND whoView.""ViewedStoriesId"" = scr.""Id""
WHERE s.""AuthorId"" IN (
SELECT u.""Id""
FROM ""UserFriend"" as f, ""User"" as u
WHERE
CASE
WHEN f.""FirstUserFriendId"" = {userId}
THEN f.""SecondUserFriendId"" = u.""Id""
WHEN f.""SecondUserFriendId"" = {userId}
THEN f.""FirstUserFriendId"" = u.""Id""
END
)
AND scr.""CreateTimestamp"" >= NOW() - '1 day'::INTERVAL
AND scr.""IsDelete"" = false").ToListAsync();
Output:
System.ArgumentException: An item with the same key has already been added. Key: Id
If you execute this code, the SQL query will be identical (given that the SQL query is lightweight. It just gets the story and the author, but does not get the rest of the information, because it is needed only for clarity)
StoryContainerRto entity = await _context.StoryContainer
.AsNoTracking()
.Include(e => e.Author)
.Where(e => e.AuthorId == 1)
.ToListAsync();
Generated SQL (note the duplication of the "Id" column):
SELECT s."Id", s."AuthorId", u."Id", u."AccountHeaderUrl", u."AvatarUrl", u."Description", u."Email", u."IsAuthorOfQualityContent", u."IsDeleted", u."IsInShadowBan", u."IsVerifiedProfile", u."LinkInBio", u."MedalsAmount", u."Name", u."Nickname", u."PasswordHash", u."PasswordSalt", u."PhoneNumber", u."PhoneNumberPrefix", u."PhoneNumberVerefied", u."RegistrationDateTime", u."TelegramId", u."TelegramVerifyingChatId"
FROM "StoryContainer" AS s
INNER JOIN "User" AS u ON s."AuthorId" = u."Id"
WHERE s."AuthorId" = 1
User table:
[Table("User")]
public class UserRto
{
[Key] public int Id { get; set; }
[Required] public string Nickname { get; set; }
public string? Email { get; set; }
[Required] public byte[] PasswordHash { get; set; }
[Required] public byte[] PasswordSalt { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? LinkInBio { get; set; }
public int MedalsAmount { get; set; }
public string? AvatarUrl { get; set; }
public bool IsDeleted { get; set; }
public bool? IsVerifiedProfile { get; set; }
public bool? IsAuthorOfQualityContent { get; set; }
public bool IsInShadowBan { get; set; }
public DateTime RegistrationDateTime { get; set; }
public string? PhoneNumberPrefix { get; set; }
public string? PhoneNumber { get; set; }
public bool PhoneNumberVerefied { get; set; }
public string? TelegramId { get; set; }
public string? TelegramVerifyingChatId { get; set; }
public string? AccountHeaderUrl { get; set; }
public List<FriendInvitationRto> FriendInvitationsSent { get; set; }
public List<FriendInvitationRto> FriendInvitationsReceived { get; set; }
public List<UserFriendRto> FirstFriends { get; set; }
public List<UserFriendRto> SecondFriends { get; set; }
public List<StoryContentRefRto> ViewedStories { get; set; }
public StoryContainerRto StoryContainer { get; set; }
}
Story table:
[Table("StoryContainer")]
public class StoryContainerRto
{
public int Id { get; set; }
public int AuthorId { get; set; }
public UserRto Author { get; set; }
public List<StoryContentRefRto> Items { get; set; }
}
How do I complete the request? I understand that the fact is that 3 columns of "Id" come from three tables. But at the same time, if you look at the request generated by EF itself, then there are also 2 "Ids". How do I execute such a request correctly?
https://makolyte.com/ef-core-select-queries-involving-multiple-table/
There is an example of code using INNER JOIN like mine. I do not know why the same approach does not work for me
I also applied the AS operator to all the "Id" columns, but in that case I get this error:
The required column 'Id' was not present in the results of a 'FromSql' operation
I was able to solve this problem using a direct SQL query to the database, and then analyzed the information myself
List<StoryRto> friendsStories = new();
using (NpgsqlCommand command = (NpgsqlCommand)(_context as PotokContext)!.Database.GetDbConnection().CreateCommand())
{
command.CommandText = $#"SELECT s.*, scr.*, u.*, whoView.""ViewedId"" as ""IViewThis""
FROM ""Stories"" as s
INNER JOIN ""StoryContentRefs"" as scr ON scr.""StoryId"" = s.""Id""
INNER JOIN ""User"" as u ON u.""Id"" = s.""AuthorId""
LEFT JOIN ""StoryContentRefRtoUserRto"" as whoView ON whoView.""ViewedId"" = 1 AND whoView.""ViewedStoriesId"" = scr.""Id""
WHERE s.""AuthorId"" IN (
SELECT u.""Id""
FROM ""UserFriend"" as f, ""User"" as u
WHERE
CASE
WHEN f.""FirstUserFriendId"" = 1
THEN f.""SecondUserFriendId"" = u.""Id""
WHEN f.""SecondUserFriendId"" = 1
THEN f.""FirstUserFriendId"" = u.""Id""
END
)
AND scr.""CreateTimestamp"" >= NOW() - '1 day'::INTERVAL
AND scr.""IsDelete"" = false
LIMIT {count}
OFFSET {skipCount}";
(_context as PotokContext)!.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
// Stories table
int storyId = reader.GetInt32(0);
int authorId = reader.GetInt32(1);
// StoryContentRefs table
int scrId = reader.GetInt32(2);
int scrStoryId = reader.GetInt32(3);
string scrStoryContentRef = reader.GetString(4);
bool scrIsDelete = reader.GetBoolean(5);
DateTime scrCreateTimestamp = reader.GetDateTime(6);
// UserTable
int userId = reader.GetInt32(7);
string userNickname = reader.GetString(8);
string? userEmail = reader.IsDBNull(9) ? null : reader.GetString(9);
// password hash and salt will not be included
string? userName = reader.IsDBNull(12) ? null : reader.GetString(12);
string? userDescription = reader.IsDBNull(13) ? null : reader.GetString(13);
string? userLinkInBio = reader.IsDBNull(14) ? null : reader.GetString(14);
int userMedalsAmount = reader.GetInt32(15);
string? userAvatarUrl = reader.IsDBNull(16) ? null : reader.GetString(16);
bool userIsDeleted = reader.GetBoolean(17);
bool? userIsVerifiedProfile = reader.IsDBNull(18) ? null : reader.GetBoolean(18);
DateTime userRegistrationDateTime = reader.GetDateTime(19);
bool? userIsAuthorOfQualityContent = reader.IsDBNull(20) ? null : reader.GetBoolean(20);
string? userPhoneNumber = reader.IsDBNull(21) ? null : reader.GetString(21);
string? userPhoneNumberPrefix = reader.IsDBNull(22) ? null : reader.GetString(22);
bool userPhoneNumberVerified = reader.GetBoolean(23);
string? userTelegramId = reader.IsDBNull(24) ? null : reader.GetString(24);
string? userTelegramVerifyingChatId = reader.IsDBNull(25) ? null : reader.GetString(25);
bool userIsInShadowBan = reader.GetBoolean(26);
string? userAccountHeaderUrl = reader.IsDBNull(27) ? null : reader.GetString(27);
// WhoViewStory relation
int? iViewThis = reader.IsDBNull(28) ? null : reader.GetInt32(28);
UserRto user = new()
{
Id = userId,
Nickname = userNickname,
Email = userEmail,
Name = userName,
Description = userDescription,
LinkInBio = userLinkInBio,
MedalsAmount = userMedalsAmount,
AvatarUrl = userAvatarUrl,
IsDeleted = userIsDeleted,
IsVerifiedProfile = userIsVerifiedProfile,
RegistrationDateTime = userRegistrationDateTime,
IsAuthorOfQualityContent = userIsAuthorOfQualityContent,
PhoneNumber = userPhoneNumber,
PhoneNumberPrefix = userPhoneNumberPrefix,
PhoneNumberVerefied = userPhoneNumberVerified,
TelegramId = userTelegramId,
TelegramVerifyingChatId = userTelegramVerifyingChatId,
IsInShadowBan = userIsInShadowBan,
AccountHeaderUrl = userAccountHeaderUrl
};
List<UserRto> viewed = new();
if (iViewThis != null) viewed.Add(user);
StoryContentRefRto storyContentRef = new()
{
Id = scrId,
StoryId = scrStoryId,
ContentRefs = scrStoryContentRef,
IsDelete = scrIsDelete,
CreateTimestamp = scrCreateTimestamp,
Viewed = viewed
};
if (friendsStories.FirstOrDefault(e => e.Id == scrStoryId) == null)
{
// Create new
StoryRto story = new()
{
Id = storyId,
AuthorId = authorId,
Author = user,
Items = new() { storyContentRef }
};
friendsStories.Add(story);
}
else
{
// Add to existing
StoryRto story = friendsStories.First(e => e.Id == scrStoryId);
story.Items.Add(storyContentRef);
}
}
}
else
{
Console.WriteLine("No rows found.");
}
}
}

Filter data from 2 lists with diferent models C#

I have this models
public class RoutingAttributeModel
{
public int Bus_No { get; set; }
public int Attribute_No { get; set; }
public string Attribute_Name { get; set; }
public string Status { get; set; }
public string Notes { get; set; }
}
public class AgentRoutingAttributeModel
{
public int Agent_No { get; set; }
public int Bus_No { get; set; }
public int Attribute_No { get; set; }
public string Attribute_Name { get; set; }
public string Status { get; set; }
}
List<RoutingAttributeModel> lstComplete = new List<RoutingAttributeModel>();
List<AgentRoutingAttributeModel> lstAssigned = new List<AgentRoutingAttributeModel>();
Filled this with some data
Is it possible to filter with Linq? I want to save in a new list the diferent content between lstComplete and lstAssigned
I was trying to join both lists but got stuck there
var results1 = from cl in lstComplete
join al in lstAssigned
on cl.Attribute_No equals al.Attribute_No
select cl;
you can use linq
as my understanding, you try to find linked by attribute_No records and have a list of not matching properties?
lstComplete.Add(new RoutingAttributeModel(){
Attribute_Name = "aaa",
Attribute_No = 1,
Bus_No = 1,
Notes = "",
Status = "status"
});
lstAssigned.Add(new AgentRoutingAttributeModel()
{
Attribute_No = 1,
Agent_No = 10,
Bus_No = 1,
Attribute_Name = "bbb",
Status = "status2"
});
var lst = lstComplete
.Join(lstAssigned,
complete => complete.Attribute_No,
assigned => assigned.Attribute_No,
(complete, assigned) => new { lstComplete = complete, lstAssigned = assigned })
.Select(s => new { s.lstComplete, s.lstAssigned})
.Where(w=>
w.lstAssigned.Attribute_Name != w.lstComplete.Attribute_Name
|| w.lstAssigned.Bus_No != w.lstComplete.Bus_No
)
.ToList()
.Dump();
so result would be
You could try the following query
var filteredList = lstComplete
.Where(x => !lstAssigned.Any(y => y.Attribute_No == x.Attribute_No));

Insert results of 2 collections into a new model

I have a wpf c# app.
I have to list collections.
I want to join these 2 lists and return the results that match a criteria.
I want the result to be loaded into a new list/model.
This is my code so far:
var Results = from j in res
join c in Customer.GetBaseData() on j.CustomerRef equals c.CustomerRef
where j.JobStatusRef == jobStatusRef
select new {
c.CustomerRef,
c.CustomerId,
c.Add1,
c.Town,
c.FName,
c.SName,
j.DateReq,
j.JobId,
j.JobRef,
j.JobStatus
};
This is my destination model:
public class CustomerJobs
{
public int JobId { get; set; }
public string CustomerRef { get; set; }
public string DateReq { get; set; }
public string JobRef { get; set; }
public string JobStatus { get; set; }
public int CustomerId { get; set; }
public string SName { get; set; }
public string FName { get; set; }
public string Add1 { get; set; }
public string Town { get; set; }
}
I do not know how to do this final step?
Instead of creating an anonymous type with new { ... }, directly use your model in the select:
var Results = from j in res
join c in Customer.GetBaseData() on j.CustomerRef equals c.CustomerRef
where j.JobStatusRef == jobStatusRef
// Note the "new CustomerJobs" part.
select new CustomerJobs {
c.CustomerRef,
c.CustomerId,
c.Add1,
c.Town,
c.FName,
c.SName,
j.DateReq,
j.JobId,
j.JobRef,
j.JobStatus
};
When you select in linq you can specify the type of the object like:
var Results = from j in res
join c in Customer.GetBaseData() on j.CustomerRef equals c.CustomerRef
where j.JobStatusRef == jobStatusRef
select new CustomerJobs {
CustomerRef = c.CustomerRef,
CustomerId = c.CustomerId,
Add1 = c.Add1,
Town = c.Town,
FName = c.FName,
SName = c.SName,
DateReq = j.DateReq,
JobId = j.JobId,
JobRef = j.JobRef,
JobStatus = j.JobStatus
};

C# EntityFramework Join all records

I created an EF Join. The frameStart has 19 records and frameEnd has 42 records, but when I do the join ( framing ), there are only 10 records. I believe there are only 10 records because it is joining records that can be grouped with the matching key. The code is below. How do I make it so that when it does the join, I get a list of all the records, even the ones that do not "match"?
public class s84_Report_FrameLabor
{
public int CustomerID { get; set; }
public string CustomerName { get; set; }
public int SubdivisionID { get; set; }
public string SubdivisionName { get; set; }
public int LotNumber { get; set; }
public string InstallManagerStart { get; set; }
public string InstallManagerComplete { get; set; }
public DateTime FrameLaborStart { get; set; }
public DateTime FrameLaborComplete { get; set; }
public int Duration { get; set; }
/*
Frame Labor Start ------ Product ID: 26
Frame Labor Complete ------ Product ID: 8
*/
public static List<s84_Report_FrameLabor> getDurationReport()
{
using (var context = PrimaryConnection.returnNewConnection())
{
var frameStart = (from c in context.s84_Schedule
where c.ProductID == 26 && c.Completed == false
select new
{
CustomerID = c.CustomerID,
CustomerName = c.s84_Customer.CustomerName,
SubdivisionID = c.SubdivisionID,
SubdivisionName = c.s84_Subdivision.SubdivisionName,
LotNumber = c.LotNumber,
FrameLaborStart = c.CustomerExpectedDate
}).ToList();
var frameEnd = (from c in context.s84_Schedule
where c.ProductID == 8 && c.Completed == false
select new
{
CustomerID = c.CustomerID,
SubdivisionID = c.SubdivisionID,
LotNumber = c.LotNumber,
FrameLaborComplete = c.CustomerExpectedDate
}).ToList();
var framing = from c in frameStart
join e in frameEnd on new { c.CustomerID, c.SubdivisionID, c.LotNumber } equals new { e.CustomerID, e.SubdivisionID, e.LotNumber }
select new s84_Report_FrameLabor
{
CustomerID = c.CustomerID,
CustomerName = c.CustomerName,
SubdivisionID = c.SubdivisionID,
SubdivisionName = c.SubdivisionName,
LotNumber = c.LotNumber,
FrameLaborStart = c.FrameLaborStart,
FrameLaborComplete = e.FrameLaborComplete,
Duration = (e.FrameLaborComplete - c.FrameLaborStart).Days
};
return framing.ToList();
}
}
}
Thanks to Andre, stuartd, and James R., I found out the solution was to use the EntityFramework DefaultIfEmpty().
var framing = from c in frameStart
join e in frameEnd on new { c.CustomerID, c.SubdivisionID, c.LotNumber } equals new { e.CustomerID, e.SubdivisionID, e.LotNumber } into jointable
from z in jointable.DefaultIfEmpty()
select new s84_Report_FrameLabor
{
CustomerID = c.CustomerID,
CustomerName = c.CustomerName,
SubdivisionID = c.SubdivisionID,
SubdivisionName = c.SubdivisionName,
LotNumber = c.LotNumber,
FrameLaborStart = c.FrameLaborStart,
FrameLaborComplete = z.FrameLaborComplete,
Duration = c.FrameLaborStart == null ? z.FrameLaborComplete == null ? (z.FrameLaborComplete - c.FrameLaborStart).Days : 0 : 0
};
return framing.ToList();

Cannot implicitly convert type 'System.Collections.Generic.List<AnonymousType#1>' to 'System.Collections.Generic.List<FirstApp.Model.TeamDetails>

I am getting this error
Cannot implicitly convert type
System.Collections.Generic.List<AnonymousType#1> to
System.Collections.Generic.List<FirstApp.Model.TeamDetails>
What's wrong with my code?
Here is my code
TeamDetails Class
public class TeamDetails
{
[Key]
public int TeamId { get; set; }
public string TeamName { get; set; }
public string Description { get; set; }
public int? UserCount { get; set; }
}
ViewModel
public class ViewTeamList
{
public List<TeamDetails> TeamNext { get; set; }
}
Controller
public ActionResult Next(int dataid)
{
ViewTeamList viewTeamList = new ViewTeamList();
var a = from t in tDbContext.Teams
join u in tDbContext.Users on t.TeamId equals u.TeamId into g
where t.Deleted != true
select new { TeamId= t.TeamId,TeamName = t.TeamName, Description = t.Description, UserId = g.Count() };
var next = a.OrderBy(t1 => t1.TeamId).Where(t1 => t1.TeamId > dataid).FirstOrDefault();
viewTeamList.TeamNext = a.ToList();
return PartialView("_ViewTeamDetails", viewTeamList);
}
I'm not able to assign this value to
viewTeamList.TeamNext = a....;
This constructs an anonymous type instead of TeamDetails
select new { TeamId= t.TeamId,TeamName = t.TeamName, Description = t.Description, UserId = g.Count() }
You should change it to below
select new TeamDetails { TeamId = t.TeamId, TeamName = t.TeamName, Description = t.Description, UserCount = g.Count() }
so a.ToList() would be a List<FirstApp.Model.TeamDetails>

Categories