EF Core Model Builder exception from duplicate column names - c#

I have a person table and an alias table, and want to return all person where the name matches their name or one of their alias. So I want the person when the name matches and there are no matching aliases (a left outer join). In sql server this query works:
Select * from People as p left join alias as a on p.PersonID = a.PersonID
where p.Name = 'Walter White' or a.Name = 'Walter White'
There is no alias match so my result contains all of my person fields, as well as all of my alias fields, both of which have a PersonID column, and in SQL server there is no '1' appended to either of them (ie, two columns with the same name does not cause any issue).
In my program (c# WPF) I fill a datagrid with the results of that same query:
var matches = from p in db.People
from a in db.Alias.
Where(n => n.PersonlId == p.PersonId).DefaultIfEmpty()
where p.Name == "Walter White" || a.Name == "Walter White"
select p;
foreach(var m in matches) {} //<-- do stuff here, but this throws an exception
I get an exception that seems to stop in my foreach loop, but is related to the linq query (I think because of the lazy loading it does not actually try to fill until I use it). The exception says Invalid Column Name PersonId1, so somehow sql server knows how to handle the duplicate column names gracefully, but .net runtimes do not. I only really need the name field from alias, but since I'm only selecting the person in my linq query I did not expect to see any of the alias stuff. How do I explicitly select fields to avoid having duplicate column names in my results on the linq side?
I have tried using select new { p, a.Name } but still get the error. I also tried renaming on of the ID fields: select new { p, AID = a.PersonID } and still get the error.
I have also tried this syntax with the same result:
var matches = db.People.Where(p => p.Name == "Walter White" ||
p.Alias.Where(a => a.Name == "Walter White").Any());
In the DBContext class I notice the alias entity is set up with the FK but the person entity is not:
modelBuilder.Entity<Alias>(entity =>
{
entity.HasKey(e => e.Uid);
entity.Property(e => e.Uid).HasColumnName("UID");
entity.Property(e => e.PersonId).HasColumnName("PersonID");
entity.Property(e => e.Name).HasMaxLength(50);
entity.HasOne(d => d.Person).WithMany(p => p.Alias)
.HasForeignKey(d => d.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Alias_People");
});
But the Person class has the virtual alias collection property. This is EF Core 7.0.2.
I just simplified to just return all people, no join:
var matches = db.People;
And I still get the error. So now I'm convinced that the model is not being built properly, and the entity set up needs to alias the column names so that there is not an issue. How is this done?

Related

Why is Group By translating in Order By by Entity Framework?

I am using Entity Framework in .NET 7.
I have 3 entities:
Course that contains a ProfessorId among other things
Grade that has a CourseId among other things
Professor
I want to get all the courses that are assigned to a professor and have at least 1 grade associated with them and filter them in a Dictionary<string, CourseViewModel> where string is the semester.
I have written the following LINQ query:
var professorGradedCourses = _dbContext.Courses
.Where(course => course.ProfessorId == professorId && course.Grades.Any())
.Select(course => new CourseViewModel
{
Title = course.Title,
Semester = course.Semester,
})
.GroupBy(course => course.Semester)
.OrderBy(course => course.Key)
.ToDictionary(group => group.Key, group => group.ToList());
When that executes I get an exception saying it can't be translated.
If I remove the OrderBy and keep only the GroupBy, it works and the translated SQL in Microsoft SQL Server is:
SELECT [c].[Semester], [c].[Title]
FROM [Courses] AS [c]
WHERE [c].[ProfessorId] = #__professorId_0
AND EXISTS (SELECT 1
FROM [Grades] AS [g]
WHERE [c].[Id] = [g].[CourseId])
ORDER BY [c].[Semester]
As you can see it adds ORDER BY anyway, even though I have removed it and kept only GroupBy(). Can someone explain why is that? What if I wanted to order by descending would that be possible? Also the weird thing is that if I remove GroupBy() and keep only OrderBy() and replace the ToDictionary with ToList, it works and the exact same query is produced (only now I can't really use the results without further actions).
LINQ GroupBy :
Groups the elements of a sequence.
SQL GROUP BY :
A SELECT statement clause that divides the query result into groups of rows, usually by performing one or more aggregations on each group. The SELECT statement returns one row per group.
They aren't equivalent. The main difference is LINQ GroupBy return a collection by key, when SQL GROUP BY return ONE element (column) by key.
If the projection ask ONE element by key, then EF Core translate LINQ GroupBy to SQL GROUP BY :
// Get the number of course by semester
context
.Courses
.GroupBy(c => c.Semester)
.Select(cs => new { Semester = cs.Key, Count = cs.Count() })
.ToList();
Translated to :
SELECT [c].[Semester], COUNT(*) AS [Count]
FROM [Courses] AS [c]
GROUP BY [c].[Semester]
But if the projection ask several element, then EF Core translate LINQ GroupBy to SQL ORDER BY and group by itself.
context
.Courses
.Select(c => new { c.Id, c.Semester })
.GroupBy(c => c.Semester)
.ToDictionary(cs => cs.Key, cs => cs.ToList());
Translated to :
SELECT [c].[Semester], [c].[Id]
FROM [Courses] AS [c]
ORDER BY [c].[Semester]
If the result is :
Semester
Id
2023 S1
1
2023 S1
4
2023 S2
2
...
...
Then EF Core read like :
Read first row : Semester is "2023 S1"
No group
Then create a group and add the row in.
Read second row : Semester is "2023 S1"
The key is the same that precedent element
Then Add the row in the group
Read the third row : Semester is "2023 S2"
The key is different that precedent element
Then create a new group and the row in.
And so on...
You understand the interest of sorting.
About the error, I don't know that EF Core can't. The query sound legit. Maybe this should not be implemented at this time.
About that you try, to convert a sorted grouping enumeration to a dictionary. This is weird because the dictionary isn't sortable. Then this sorts elements and put them in loose.
If Dictionary seem sorted, it's a coincidence, not a feature. In intern, the dictionary sort element by key's has code, that is generally the sorted order... But not every time.
If you want a sorted dictionary, you can use SortedDictyonary. But it can be tricky if you need a custom sort rule, like :
context
.Courses
.Select(c => new { c.Id, c.Semester })
.GroupBy(c => c.Semester)
.ToImmutableSortedDictionary(cs => cs.Key, cs => cs.ToList(), new ReverseComparer<string>());
public class ReverseComparer<T> : IComparer<T>
{
private IComparer<T> _comparer = Comparer<T>.Default;
public int Compare(T? x, T? y)
{
return _comparer.Compare(x, y) * -1;
}
}
The exception you are encountering is most likely due to the fact that the OrderBy clause cannot be translated into SQL by Entity Framework. The OrderBy clause is executed in memory after the data has been retrieved from the database, which is why it works when you remove it and keep only the GroupBy clause.
However, if you want to order the dictionary by descending, you can simply call the Reverse method on the ToDictionary result:
var professorGradedCourses = _dbContext.Courses
.Where(course => course.ProfessorId == professorId && course.Grades.Any())
.Select(course => new CourseViewModel
{
Title = course.Title,
Semester = course.Semester,
})
.GroupBy(course => course.Semester)
.OrderByDescending(course => course.Key)
.ToDictionary(group => group.Key,
group => group.ToList())
.Reverse();
This way, the dictionary will be sorted in descending order based on the semester.
Give this a try and let me know how it works for you.
EDIT:
Converting the IEnumerable back to a Dictionary should work like this:
var professorGradedCourses = _dbContext.Courses
.Where(course => course.ProfessorId == professorId && course.Grades.Any())
.Select(course => new CourseViewModel
{
Title = course.Title,
Semester = course.Semester,
})
.GroupBy(course => course.Semester)
.OrderByDescending(course => course.Key)
.ToDictionary(group => group.Key,
group => group.ToList())
.Reverse()
.ToDictionary(pair => pair.Key,
pair => pair.Value);

Fluent NHibernate Querying: OrderBy column of joined table

I've been struggling quite some time now with trying to query some stuff using NHibernate.
I've managed to do some very simple queries, but now I'm trying to build some custom pagination through NHibernate.
What I want to do is twofold.
First (1) off, I want to be able to order my resultset based on a column from a joined table.
Let's say I have a 'Person' table that has a one-to-one (actually many-to-one) reference with an 'Address' table.
I want to query over the 'Person' table (using its fields), but want to sort using fields from the 'Address' table.
How do I do this? I've tried the following two ways
var resultset = GetCurrentSession().QueryOver<Person>()
.Where(x => x.Name == "...")
.JoinQueryOver<Address>(x => x.Address)
.OrderBy(x => x.HouseNumber).Desc
.Skip(...)
.Take(...)
.List();
// ==> could not execute query
Person person = null;
Address address = null;
var resultset = GetCurrentSession().QueryOver<Person>()
.JoinAlias(() => person, () => address.Persons)
.Where(() => person.Name == "...")
.OrderBy(() => address.HouseNumber).Asc
.Skip(...)
.Take(...)
.List();
// ==> could not resolve property: person of: Person
My second (2) question is, I would like to split the ordering up.
Based on conditional statements, I want to add a different OrderBy.
Can I just do it like so?
var query = GetCurrentSession().QueryOver<Person>()
.Where(x => x.Name == "...")
.JoinQueryOver<Address>(x => x.Address);
if(foo)
{
query = query.OrderBy(() => address.HouseNumber).Asc
}
else if (bar)
{
query = query.OrderBy(() => address.Street).Desc
}
var resultset = query
.Skip(...)
.Take(...)
.List();
Thanks a lot!
All of your examples should work, with the small caveat that you need to assign your aliases properly in order to use them (the message could not resolve property: person of: Person indicates that person was never actually set as the alias). For example:
Person person = null;
Address address = null;
// Assign the person alias using () => person
var resultset = GetCurrentSession().QueryOver<Person>(() => person)
.JoinAlias(() => person.Address, () => address) // Assign the address alias here
.Where(() => person.Name == "...")
.OrderBy(() => address.HouseNumber).Asc
.Skip(...)
.Take(...)
.List();
This should work fine.
As for your second question, what you're trying to achieve is very possible, an in fact a great reason to use QueryOver to begin with. You just need a few tweaks:
var query = GetCurrentSession().QueryOver<Person>()
.Where(x => x.Name == "...")
.JoinQueryOver<Address>(x => x.Address, () => address); // assign address alias
if(foo)
{
query.OrderBy(() => address.HouseNumber).Asc(); // <-- use the *method* .Asc()
}
else if (bar)
{
query.OrderBy(() => address.Street).Desc(); // <-- use the *method* .Desc()
}
Note that since .Asc() and .Desc() actually modify the query, you don't need to reassign the query. Be sure to use the methods .Asc() and .Desc() so that you don't get a build error.
Here is dynamic sorting
string orderColumn = "";
bool isAsc = (param.sSortDir_0 == "asc");
switch (param.iSortCol_0)
{
case 1: orderColumn = "Approved"; break;
case 2: orderColumn = "CreateDate"; break;
case 3: orderColumn = "Rate"; break;
}
result.UnderlyingCriteria.AddOrder(new NHibernate.Criterion.Order(orderColumn, isAsc));

NHibernate - selecting data from referenced objects (traversing many-to-one relationship) in a query

i want to build a query that will select some columns from a joined table (many to one relationship in my data model).
var q = ses.QueryOver<Task>().Select(x => x.Id, x => x.CreatedDate, x => x.AssigneeGroup.Name, x => x.AssigneeGroup.IsProcessGroup);
Here i'm retrieving properties from AssigneeGroup which is a reference to another table, specified in my mapping. But when I try to run this query I get
Exception: could not resolve property: AssigneeGroup.Name of: Task
So it looks like NHibernate is not able to follow relations defined in my mapping and doesn't know that in order to resolve AssigneeGroup.Name we should do a join from 'Task' to 'Groups' table and retrieve Group.Name column.
So, my question is, how to build such queries? I have this expression: x => x.AssigneeGroup.Name, how to convert it to proper Criteria, Projections and Aliases? Or is there a way to do this automatically? It should be possible because NHibernate has all the information...
Your query need association and should look like this:
// firstly we need to get an alias for "AssigneeGroup", to be used later
AssigneeGroup assigneeGroup = null;
var q = ses
.QueryOver<Task>()
// now we will join the alias
.JoinAlias(x => x.AssigneeGroup, () => assigneeGroup)
.Select(x => x.Id
, x => x.CreatedDate
// instead of these
// , x => x.AssigneeGroup.Name
// , x => x.AssigneeGroup.IsProcessGroup
// use alias for SELECT/projection (i.e. ignore "x", use assigneeGroup)
, x => assigneeGroup.Name
, x => assigneeGroup.IsProcessGroup
);
More and interesting reading:
NHibernate - CreateCriteria vs CreateAlias, to get more understanding when to use JoinAlias (CreateAlias) and when JoinQueryOver (CreateCriteria)
Criteria API for: 15.4. Associations
QueryOver API for 16.4. Associations
You have to join the two tables if you wish to select columns from something other than the root table/entity (Task in our case).
Here is an example:
IQueryOver<Cat,Kitten> catQuery =
session.QueryOver<Cat>()
.JoinQueryOver<Kitten>(c => c.Kittens)
.Where(k => k.Name == "Tiddles");
or
Cat catAlias = null;
Kitten kittenAlias = null;
IQueryOver<Cat,Cat> catQuery =
session.QueryOver<Cat>(() => catAlias)
.JoinAlias(() => catAlias.Kittens, () => kittenAlias)
.Where(() => catAlias.Age > 5)
.And(() => kittenAlias.Name == "Tiddles");
Alternatively you could use the nhibernate linq provider (nh > 3.0):
var q = ses.Query<Task>()
.Select(x => new
{
Id = x.Id,
CreatedDate = x.CreatedDate,
Name = x.AssigneeGroup.Name,
IsProcessGroup = x.AssigneeGroup.IsProcessGroup
});

How do I express a query containing a WHERE..IN subquery using NHibernate's QueryOver API?

I have an object model where an Order contains many LineItems, and each LineItem has an associated Product. In the object model, these are one-way associations -- a LineItem does not know anything about its Order.
I want to query for orders that contain a line item with a product name matching a string, returning one row for each order (so that paging can be performed).
SELECT * FROM Orders
WHERE OrderID IN (
SELECT DISTINCT OrderID
FROM LineItems
INNER JOIN Products on LineItems.ProductID = Products.ProductID
WHERE Products.Name = 'foo'
)
Given that I have an ICriteria or an IQueryOver representing the subquery, how do I actually apply it to my root Order query?
var subquery = QueryOver.Of<LineItem>
.Where(l => l.Product.Name == "foo")
.TransformUsing(Transformers.DistinctRootEntity);
I've found plenty of examples that assume the root object in the query is on the "many" side of a one-to-many relationship, but I can't figure out how to add a restriction on something that the root object has many of.
I'd make it a bi-directional relationship between order and line item to allow efficient queries (reduce the number of joins required). But, if for some weird reason you can't, you'll need to start the sub-query from the Order...
LineItem lineItemAlias = null;
Product productAlias = null;
var subQuery = QueryOver.Of<Order>()
.JoinAlias(x => x.LineItems, () => lineItemAlias)
.JoinAlias(() => lineItemAlias.Product, () => productAlias)
.Where(() => productAlias.Name == "foo")
.Select(Projections.Group<Order>(x => x.Id));
var results = Session.QueryOver<Order>()
.WithSubquery.WhereProperty(x => x.Id).In(subQuery)
.List();
The direct translation of the SQL that you provided can be acheived using this
var subQuery =
QueryOver.Of<LineItem>(() => lineItem)
.JoinAlias(() => lineItem.Products, () => product)
.Where(() => product.Name == "foo")
.Select(Projections.Distinct(
Projections.Property(()=> lineItem.Order.Id)));;
var theQueryYouNeed =
QueryOver.Of<Orders>(() => order)
.WithSubquery.WherePropertyIn(() => order.Id).In(subQuery);
However if your LineItem entity does not have a Order Property then you cannot really use the subquery.
If you need to find
All Orders which have a LineItem where the Product Name is "foo" then
var theQueryYouNeed =
QueryOver.Of<Orders>(() => order)
.JoinAlias(() => order.LineItems, () => lineItem)
.JoinAlias(() => lineItem.Product, () => product)
.Where(() => product.Name == "foo")
.TransformUsing(new DistinctRootEntityResultTransformer())

How to select a single column with Entity Framework?

Is there a way to get the entire contents of a single column using Entity Framework 4? The same like this SQL Query:
SELECT Name FROM MyTable WHERE UserId = 1;
You can use LINQ's .Select() to do that. In your case it would go something like:
string Name = yourDbContext
.MyTable
.Where(u => u.UserId == 1)
.Select(u => u.Name)
.SingleOrDefault(); // This is what actually executes the request and return a response
If you are expecting more than one entry in response, you can use .ToList() instead, to execute the request. Something like this, to get the Name of everyone with age 30:
string[] Names = yourDbContext
.MyTable
.Where(u => u.Age == 30)
.Select(u => u.Name)
.ToList();
I'm a complete noob on Entity but this is how I would do it in theory...
var name = yourDbContext.MyTable.Find(1).Name;
If It's A Primary Key.
-- OR --
var name = yourDbContext.MyTable.SingleOrDefault(mytable => mytable.UserId == 1).Name;
-- OR --
For whole Column:
var names = yourDbContext.MyTable
.Where(mytable => mytable.UserId == 1)
.Select(column => column.Name); //You can '.ToList();' this....
But "oh Geez Rick, What do I know..."
Using LINQ your query should look something like this:
public User GetUser(int userID){
return
(
from p in "MyTable" //(Your Entity Model)
where p.UserID == userID
select p.Name
).SingleOrDefault();
}
Of course to do this you need to have an ADO.Net Entity Model in your solution.
You could use the LINQ select clause and reference the property that relates to your Name column.
If you're fetching a single item only then, you need use select before your FirstOrDefault()/SingleOrDefault(). And you can use anonymous object of the required properties.
var name = dbContext.MyTable.Select(x => new { x.UserId, x.Name }).FirstOrDefault(x => x.UserId == 1)?.Name;
Above query will be converted to this:
Select Top (1) UserId, Name from MyTable where UserId = 1;
For multiple items you can simply chain Select after Where:
var names = dbContext.MyTable.Where(x => x.UserId > 10).Select(x => x.Name);
Use anonymous object inside Select if you need more than one properties.

Categories

Resources