I am trying to query two tables and need to pull related records from both the tables. I am using enityframeworkcore 3 One is system versioned table and the other is history table. My resultset is containing data only from history table
and not system-versioned table. Could somebody tell me what is wrong with my statement . I am ensured that the personid in the systemversion table matches the history table.
If I try to add the following to the select list I am getting duplicates initialising of member
select new PersonNote
{
AuthorId = pn.AuthorId,
Created = pn.Created,
Note = pn.Note,
AuthorName = p.FirstName + " " + p.LastName,
AuthorId = pnh.AuthorId,
Created = pnh.Created,
Note = pnh.Note,
AuthorName = p.FirstName + " " + p.LastName,
Original query
public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
var personNotes = (from pn in _context.PersonNotes
join pnh in _context.PersonNotesHistory on pn.PersonId equals pnh.PersonId
join p in _context.Person on pn.PersonId equals p.Id
select new PersonNote
{
AuthorId = pn.AuthorId,
Created = pn.Created,
Note = pn.Note,
AuthorName = p.FirstName + " " + p.LastName,
}
);
return personNotes;
}
Data
Change the table order for joins. Start from Person, join it with PersonNotes on personId, and then with PersonNotesHistory (pn.PersonId equals pnh.PersonId).
public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
var personNotes = (from p in _context.Person
join pn in _context.PersonNotes on pn.PersonId equals p.Id
join pnh in _context.PersonNotesHistory on pn.PersonId equals pnh.PersonId
select new PersonNote
{
AuthorId = pn.AuthorId,
Created = pn.Created,
Note = pn.Note,
AuthorName = p.FirstName + " " + p.LastName,
}
);
return personNotes;
}
This projection wont give you the data from pnh because nothing is selected.
Suppose I have the following tables:
tb_1: |user_id|user_name|email|age|
tb_2: |item_id|item_name|value|
tb_3: |user_id|item_id|
And I have the models below:
Item:
public class Item {
public string Name {get; set;}
public int Value {get; set;}
}
User:
public class User {
public Guid UserId {get; set;}
public List<Item> Itens {get; set;}
}
I am using the following Query to do the search:
using(var connection = ...)
{
var query1 = "SELECT ... FROM tb_1";
var query2 = "SELECT ... FROM tb_2 JOIN tb_3 ON ... WHERE tb_3.user_id = #UserId";
var users = await connection.QueryAsync<User>(query1);
foreach(var user in users)
{
user.Itens = await connection.QueryAsync<Item>(query2, user.UserId);
}
return users;
}
Is it possible to remove the foreach and use only one query?
PS: The tables are N to N.
I was able to solve the problem. I researched and I found a solution in Dapper documentation from the "one to many" query.
string sql = "SELECT TOP 10 * FROM Orders AS A INNER JOIN OrderDetails AS B ON A.OrderID = B.OrderID;";
using (var connection = new SqlCeConnection("Data Source=SqlCe_W3Schools.sdf"))
{
var orderDictionary = new Dictionary<int, Order>();
var list = connection.Query<Order, OrderDetail, Order>(
sql,
(order, orderDetail) =>
{
Order orderEntry;
if (!orderDictionary.TryGetValue(order.OrderID, out orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
},
splitOn: "OrderID")
.Distinct()
.ToList();
Console.WriteLine(list.Count);
FiddleHelper.WriteTable(list);
FiddleHelper.WriteTable(list.First().OrderDetails);
}
Reference: Query Multi-Mapping (One to Many)
I would say "yes, but: rewrite the query to use a join", i.e.
var query = #"
SELECT ... FROM tb_1
SELECT ... FROM tb_2 JOIN tb_3 ON ...
INNER JOIN (SELECT UserId FROM tb_1) x -- your original query here
on tb_3.user_id = x.UserId -- but as a sub-query
";
then use QueryMultiple. This returns two grids, so you'll need to read twice - then group and partition at the call-site.
using (var multi = connection.QueryMultiple(sql, ...))
{
var users = multi.Read<User>().AsList();
var allItems = multi.Read<Item>().AsList();
var usersById = users.ToDictionary(x => x.Id);
foreach(var item in allItems) {
usersById[item.UserId].Items.Add(item);
}
return users;
}
So I most likely have something wrong, I know this already. I am just unable to figure out what exactly is wrong. I have tried this two different ways and I get different results from each way.
Well here goes, I am trying to use Stored Procedures to get data for the view. I have two View Models that are as such:
public class CharacterCraftNamesListViewModel
{
public string CharFullName { get; set; }
public string ProfName { get; set; }
}
and
public class CharacterCraftCraftListViewModel
{
public string CraftClassName { get; set; }
public int CharCraftCharID { get; set; }
public int CharCraftClassID { get; set; }
public int CharCraftLevelID { get; set; }
public bool CraftLevelSet { get; set; }
public string CraftLevelName { get; set; }
public bool CraftLevelMastery { get; set; }
}
I also have the two corresponding Stored Procedures in the database.
CREATE PROCEDURE [dbo].[GetCharacterCraftCharacterNameProfessionName]
#CharID int = NULL
AS
WITH CHCRNames_CTE ( [CCCID], [CharFull], [ProfName] )
AS
(SELECT
Character_Char_ID,
CASE
WHEN b.Char_Last_Name IS NULL THEN b.Char_First_Name
ELSE b.Char_First_Name + ' ' + b.Char_Last_Name
END AS FullName,
c.Profession_Name
FROM CharacterCraft a LEFT OUTER JOIN
[Character] b ON a.Character_Char_ID = b.Char_ID LEFT OUTER JOIN
[Profession] c ON c.Profession_ID = b.Profession_Profession_ID
)
SELECT DISTINCT CharFull, ProfName
FROM CHCRNames_CTE
WHERE CCCID = #CharID
and
CREATE PROCEDURE [dbo].[GetCharacterCraftRank]
#CharID int = NULL,
#Rank int = NULL
AS
WITH CHCR_CTE ( [Rank], [CCID], [CCCCID], [CCName], [CLCLID], [CLName], [CLTier], [CLS], [CLM])
AS
(SELECT
DENSE_RANK() OVER(PARTITION BY(a.Character_Char_ID)ORDER BY (a.CraftClass_Craft_Class_ID)) AS [Rank],
a.Character_Char_ID,
CraftClass_Craft_Class_ID,
c.Craft_Class_Name,
CraftLevel_Craft_Level_ID,
d.Craft_Level_Name,
d.Craft_Level_Tier,
Craft_Level_Set,
Craft_Level_Mastery
FROM CharacterCraft a LEFT OUTER JOIN
[Character] b ON a.Character_Char_ID = b.Char_ID LEFT OUTER JOIN
[CraftClass] c ON a.CraftClass_Craft_Class_ID = c.Craft_Class_ID LEFT OUTER JOIN
[CraftLevel] d ON a.CraftLevel_Craft_Level_ID = d.Craft_Level_ID
)
SELECT [CCID], [CCCCID], [CCName], [CLCLID], [CLS], [CLName], [CLM]
FROM CHCR_CTE
WHERE [CCID]= #CharID AND [Rank] = #Rank
ORDER BY [Rank], [CLTier]
Inside my controller I have the following:
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var names = await db.Database.SqlQuery<CharacterCraftNamesListViewModel>( sql: "GetCharacterCraftCharacterNameProfessionName", parameters: new object[] { id } ).ToListAsync();
var alist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( sql: "GetCharacterCraftRank", parameters: new object[] { id, 1 } ).ToListAsync();
var blist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( sql: "GetCharacterCraftRank", parameters: new object[] { id, 2 } ).ToListAsync();
var clist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( sql: "GetCharacterCraftRank", parameters: new object[] { id, 3 } ).ToListAsync();
var characterCraft = new CharacterCraftViewModel()
{
CharNames = names.AsEnumerable(),
CraftListA = alist.AsEnumerable(),
CraftListB = blist.AsEnumerable(),
CraftListC = clist.AsEnumerable()
};
if (characterCraft == null)
{
return HttpNotFound();
}
return View(characterCraft);
}
When I look at the debugger I see the following:
id 1
names Count=0
alist Count=0
blist Count=0
clist Count=0
characterCraft
{LotroMvc.Models.CharacterCraftViewModels.CharacterCraftViewModel}
So with this I just end up getting a blank page.
Now I have tried placing the stored procedures in the controller itself, and have ended up with a different output in the debugger.
Inside the controller I tried:
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var query = "WITH CHCRNames_CTE( [CCCID], [CharFull], [ProfName] ) "
+ "AS "
+ "( SELECT "
+ "Character_Char_ID, "
+ "CASE "
+ "WHEN b.Char_Last_Name IS NULL THEN b.Char_First_Name "
+ "ELSE b.Char_First_Name + ' ' + b.Char_Last_Name "
+ "END AS FullName, "
+ "c.Profession_Name "
+ "FROM CharacterCraft a LEFT OUTER JOIN "
+ "dbo.[Character] b ON a.Character_Char_ID = b.Char_ID LEFT OUTER JOIN "
+ "dbo.[Profession] c ON c.Profession_ID = b.Profession_Profession_ID "
+ ") "
+ "SELECT DISTINCT CharFull, ProfName "
+ "FROM CHCRNames_CTE "
+ "WHERE CCCID = #p0";
var names = await db.Database.SqlQuery<CharacterCraftNamesListViewModel>( query, id ).ToListAsync();
var rank = "WITH CHCR_CTE( [Rank], [CCID], [CCCCID], [CCName], [CLCLID], [CLName], [CLTier], [CLS], [CLM] )"
+ "AS "
+ "( SELECT "
+ "DENSE_RANK() OVER(PARTITION BY(a.Character_Char_ID)ORDER BY (a.CraftClass_Craft_Class_ID)) AS [Rank], "
+ "a.Character_Char_ID, "
+ "CraftClass_Craft_Class_ID, "
+ "c.Craft_Class_Name, "
+ "CraftLevel_Craft_Level_ID, "
+ "d.Craft_Level_Name, "
+ "d.Craft_Level_Tier, "
+ "Craft_Level_Set, "
+ "Craft_Level_Mastery "
+ "FROM CharacterCraft a LEFT OUTER JOIN "
+ "[Character] b ON a.Character_Char_ID = b.Char_ID LEFT OUTER JOIN "
+ "[CraftClass] c ON a.CraftClass_Craft_Class_ID = c.Craft_Class_ID LEFT OUTER JOIN "
+ "[CraftLevel] d ON a.CraftLevel_Craft_Level_ID = d.Craft_Level_ID "
+ ") "
+ "SELECT [CCID], [CCCCID], [CCName], [CLCLID], [CLS], [CLName], [CLM] "
+ "FROM CHCR_CTE "
+ "WHERE [CCID]= #p0 AND [Rank] = #p1 "
+ "ORDER BY [Rank], [CLTier]";
var alist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( rank, parameters: new object[] { id, 1 } ).ToListAsync();
var blist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( rank, parameters: new object[] { id, 2 } ).ToListAsync();
var clist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( rank, parameters: new object[] { id, 3 } ).ToListAsync();
var characterCraft = new CharacterCraftViewModel()
{
CharNames = names.AsEnumerable(),
CraftListA = alist.AsEnumerable(),
CraftListB = blist.AsEnumerable(),
CraftListC = clist.AsEnumerable()
};
if (characterCraft == null)
{
return HttpNotFound();
}
return View(characterCraft);
}
This gives me the following in the debugger:
this {LotroMvc.Controllers.CharacterCraftsController}
id 1
query "WITH CHCRNames_CTE( [CCCID], [CharFull], [ProfName] ) AS (
SELECT Character_Char_ID, CASE WHEN b.Char_Last_Name IS NULL THEN
b.Char_First_Name ELSE b.Char_First_Name + ' ' + b.Char_Last_Name
END AS FullName, c.Profession_Name FROM CharacterCraft a LEFT OUTER
JOIN dbo.[Character] b ON a.Character_Char_ID = b.Char_ID LEFT OUTER
JOIN dbo.[Profession] c ON c.Profession_ID =
b.Profession_Profession_ID ) SELECT DISTINCT CharFull, ProfName FROM
CHCRNames_CTE WHERE CCCID = #p0" names Count = 1
[0] {LotroMvc.Models.CharacterCraftViewModels.CharacterCraftNamesListViewModel}
CharFullName null
ProfName "Historian"
rank "WITH CHCR_CTE( [Rank],
[CCID], [CCCCID], [CCName], [CLCLID], [CLName], [CLTier], [CLS],
[CLM] )AS ( SELECT DENSE_RANK() OVER(PARTITION
BY(a.Character_Char_ID)ORDER BY (a.CraftClass_Craft_Class_ID)) AS
[Rank], a.Character_Char_ID, CraftClass_Craft_Class_ID,
c.Craft_Class_Name, CraftLevel_Craft_Level_ID, d.Craft_Level_Name,
d.Craft_Level_Tier, Craft_Level_Set, Craft_Level_Mastery FROM
CharacterCraft a LEFT OUTER JOIN [Character] b ON
a.Character_Char_ID = b.Char_ID LEFT OUTER JOIN [CraftClass] c ON
a.CraftClass_Craft_Class_ID = c.Craft_Class_ID LEFT OUTER JOIN
[CraftLevel] d ON a.CraftLevel_Craft_Level_ID = d.Craft_Level_ID )
SELECT [CCID], [CCCCID], [CCName], [CLCLID], [CLS], [CLName],
[CLM] FROM CHCR_CTE WHERE [CCID]= #p0 AND [Rank] = #p1 ORDER BY
[Rank], [CLTier]"
alist Count = 9
[0] {LotroMvc.Models.CharacterCraftViewModels.CharacterCraftCraftListViewModel}
CharCraftCharID 0
CharCraftClassID 0
CharCraftLevelID 0
CraftClassName null
CraftLevelMastery false
CraftLevelName null
CraftLevelSet false
(and so forth)
While the data in the alist is definitely wrong the count for it is correct. The names displays the correct ProfName but the incorrect data on the CharFullName. So I am lost with what to do here. If I execute the stored procedures in T-SQL I get the correct data displayed, but that is on the server alone. I have been unable to make MVC and SQL play correctly, and I know it is my code. I just cannot see what is wrong with the code. Any thoughts to where I went wrong?
Ok, I have figured this out. There were two things wrong that were causing the issue. With the following code, the program was not calling it to the SQL Server correctly:
var names = await db.Database.SqlQuery<CharacterCraftNamesListViewModel>( sql: "GetCharacterCraftCharacterNameProfessionName", parameters: new object[] { id } ).ToListAsync();
var alist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( sql: "GetCharacterCraftRank", parameters: new object[] { id, 1 } ).ToListAsync();
var blist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( sql: "GetCharacterCraftRank", parameters: new object[] { id, 2 } ).ToListAsync();
var clist = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>( sql: "GetCharacterCraftRank", parameters: new object[] { id, 3 } ).ToListAsync();
While the SQLQuery call is correct through everything I have read, when I placed into SSMS what was outputted by the program, I got null sets. So I changed the sql: "GetCharacterNameProfessionName" and sql: "GetCharacterCraftRank" to sql: "GetCharacterNameProfessionName #p0" and sql: "GetCharacterCraftRank #p0, #p1". This then game me an output similar to when I wrote the queries out in the controller.
The next issue came down to naming convention. I was really baffled when I did a Database First Model to see what would happen, and it actually worked. But I realized as I was doing that I was able to map the names of my stored procedure columns to columns I had in the code. That is when it dawned on me, as simple change to my stored procedures, to have the column names match what I was putting in the program and everything worked correctly. A few minor tweaks and now my get controller looks like this:
public async Task<ActionResult> Edit(int? id)
{
if (id == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
var characterCraft = new CharacterCraftViewModel()
{
CharNames = await db.Database.SqlQuery<CharacterCraftNamesListViewModel>(sql: "GetCharacterCraftCharacterNameProfessionName #p0", parameters: new object[] { id }).FirstAsync(),
CraftListA = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>(sql: "GetCharacterCraftRank #p0, #p1", parameters: new object[] { id, 1 }).ToListAsync(),
CraftListB = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>(sql: "GetCharacterCraftRank #p0, #p1", parameters: new object[] { id, 2 }).ToListAsync(),
CraftListC = await db.Database.SqlQuery<CharacterCraftCraftListViewModel>(sql: "GetCharacterCraftRank #p0, #p1", parameters: new object[] { id, 3 }).ToListAsync()
};
if (characterCraft == null)
{
return HttpNotFound();
}
return View(characterCraft);
}
I hope that this helps someone else.
I'm trying to re-write the following SQL LEFT OUTER JOIN query using Entity Framework 6:
select tblA.*, tblB.*
from dbo.TableA tblA left outer join dbo.TableB tblB on
tblA.Student_id=tblB.StudentId and tblA.other_id=tblB.OtherId
where tblB.Id is null
Here's my current C# code:
using (var db = EFClass.CreateNewInstance())
{
var results = db.TableA.GroupJoin(
db.TableB,
tblA => new { StudentId = tblA.Student_id, OtherId = tblA.other_id },
tblB => new { tblB.StudentId, tblB.OtherId },
(tblA, tblB) => new { TableAData = tblA, TableBData = tblB }
)
.Where(x => x.TableBData.Id == null)
.AsNoTracking()
.ToList();
return results;
}
And here's the following compiler error I'm getting:
The type arguments cannot be inferred from the usage. Try specifying
the type arguments explicitly.
In a nutshell: I need to OUTER JOIN the two DbSet objects made available via Entity Framework, using more than one column in the join.
I'm also fairly certain this won't do a LEFT OUTER JOIN properly, even if I wasn't getting a compiler error; I suspect I need to involve the DefaultIfEmpty() method somehow, somewhere. Bonus points if you can help me out with that, too.
UPDATE #1: It works if I use a strong type in the join... is it simply unable to handle anonymous types, or am I doing something wrong?
public class StudentOther
{
public int StudentId { get; set; }
public int OtherId { get; set; }
}
using (var db = EFClass.CreateNewInstance())
{
var results = db.TableA.GroupJoin(
db.TableB,
tblA => new StudentOther { StudentId = tblA.Student_id, OtherId = tblA.other_id },
tblB => new StudentOther { StudentId = tblB.StudentId, OtherId = tblB.OtherId },
(tblA, tblB) => new { TableAData = tblA, TableBData = tblB }
)
.Where(x => x.TableBData.Id == null)
.AsNoTracking()
.ToList();
return results;
}
can you please try this solution? I'm not sure about the result :(
(from tblA in dbo.TableA
join tblB in dbo.TableB on new { tblA.Student_id, tblA.other_id } equals new { blB.StudentId, tblB.OtherId }
into tblBJoined
from tblBResult in tblBJoined.DefaultIfEmpty()
where tblBResult.Id == null
select new {
TableAData = tblA,
TableBData = tblB
}).ToList();
How do I do sorting when generating anonymous types in linq to sql?
Ex:
from e in linq0
order by User descending /* ??? */
select new
{
Id = e.Id,
CommentText = e.CommentText,
UserId = e.UserId,
User = (e.User.FirstName + " " + e.User.LastName).Trim()),
Date = string.Format("{0:d}", e.Date)
}
If you're using LINQ to Objects, I'd do this:
var query = from e in linq0
select new
{
Id = e.Id,
CommentText = e.CommentText,
UserId = e.UserId,
User = (e.User.FirstName + " " + e.User.LastName).Trim()),
Date = e.Date.ToString("d")
} into anon
orderby anon.User descending
select anon;
That way the string concatenation only has to be done once.
I don't know what that would do in LINQ to SQL though...
If I've understood your question correctly, you want to do this:
from e in linq0
order by (e.User.FirstName + " " + e.User.LastName).Trim()) descending
select new
{
Id = e.Id,
CommentText = e.CommentText,
UserId = e.UserId,
User = (e.User.FirstName + " " + e.User.LastName).Trim()),
Date = string.Format("{0:d}", e.Date)
}
Would this work, as a way of avoiding Jon's select...into?
from e in linq0
let comment = new
{
Id = e.Id,
CommentText = e.CommentText,
UserId = e.UserId,
User = (e.User.FirstName + " " + e.User.LastName).Trim()),
Date = string.Format("{0:d}", e.Date)
}
orderby comment.User descending
select comment
I'm going to get a necromancer badge for this answer, but I still think it's worth showing this snippet.
var records = await (from s in db.S
join l in db.L on s.LId equals l.Id
where (...)
select new { S = s, Type = l.MyType }
).ToListAsync();
//Data is retrieved from database by now.
//OrderBy below is LINQ to Objects, not LINQ to SQL
if (sortbyABC)
{
//Sort A->B->C
records.OrderBy(sl => sl.Type, new ABC());
}
else
{
//Sort B->A->C
records.OrderBy(sl => sl.Type, new BAC());
}
ABC and BAC implement IComparer<MyType>.