NHibernate executing a SELECT before every INSERT - c#

I have an entity with only one field (Value) and the following mapping:
Id(x => x.Value).Column("value").Length(150);
When I execute the following code
using (var tx = Database.BeginTransaction())
{
for (int i = 0; i < 10; i++)
{
var e = new Entity { Id = "Value" + i };
Database.Entities.Add(e);
}
tx.Commit();
}
NHibernate executes a SELECT statement before each INSERT call. Something like this:
NHibernate: SELECT * FROM entity entity_ WHERE entity_.value=#p0; #p0 = 'Value0'
NHibernate: INSERT INTO entity ...
NHibernate: SELECT * FROM entity entity_ WHERE entity_.value=#p0; #p0 = 'Value1'
NHibernate: INSERT INTO entity ...
NHibernate: SELECT * FROM entity entity_ WHERE entity_.value=#p0; #p0 = 'Value2'
NHibernate: INSERT INTO entity ...
If I enable the bulk mode (setting adonet.batch_size) it executes all the SELECT statements first and then the INSERT ones in bulk mode.
Is that the intended behavior? If so, what should I do to avoid that?

This behaviour is correct, related to these facts:
The ID beeing string -> has generator type assigned (the fluent-mapping line in a question)
While discouraged, the session.SaveOrUpdate(e) was used
See: 5.1.4.7. Assigned Identifiers, Extract:
Due to its inherent nature, entities that use this generator cannot be
saved via the ISession's SaveOrUpdate() method. Instead you have to
explicitly specify to NHibernate if the object should be saved or
updated by calling either the Save() or Update() method of the
ISession.
In this case, NHibernate is almost desperate. Why? Because there is no way how to assure, that the assigned id ('value1', 'value2'..) is already in DB or not. So, to be sure if the INSERT or UPDATE should be issued, it must ask the DB. That's why the SELECT before that decision.
Use the Save(e) only, behind the Database.Entities.Add(e) and no supporting infrastructural SELECT will be issued.

Related

EF6 Getting Count In Single Call

Try to do a single database call to get an entity, as well as the count of related child entities.
I know I can retrieve the count using
var count = Context.MyChildEntitySet.Where(....).Count();
or even MyEntity.ListNavigationProperty.Count()
But That means getting the entity first, followed by another call in order to get the count or use an Include which would retrieve the whole list of related entities.
I am wondering is it possible to add a "Computed" column in SQL Server to return the Count of related rows in another table?
If not how do I ask EF to retrieve the related count for each entity in once call?
I am thinking of possibly using Join with GroupBy, but this seems an Ugly solution/hack.
public class MyEntity
{
public uint NumberOfVotes{ get; private set; }
}
which ideally woudl generate SQL Similar to:
SELECT
*,
(SELECT Count(*) FROM ChildTable c WHERE c.ParentId = p.Id) NumberOfVotes
FROM ParentTable p
UPDATED
You can always drop down to using actual SQL in the following way...
string query = "SELECT *,
(SELECT Count(*) FROM ChildTable c
WHERE c.ParentId = p.Id) as NumberOfVotes
FROM ParentTable p";
RowShape[] rows = ctx.Database.SqlQuery<RowShape>(query, new object[] { }).ToArray();
I realize this is not ideal because then you are taking a dependency on the SQL dialect of your target database. If you moved form SQL Server to something else then you would need to check and maybe modify your string.
The RowShape class is defined with the properties that match the ParentTable along with an extra property called NumberOfVotes that has the calculated count.
Still, a possible workaround.

How to set the count property of a related entity without having to perform an extra query or include unnecessary data

I have a class "Campaign" that has a navigation property "Students". The campaign class has an attribute "StudentsCount" to store the amount of students. I do not want to include all the students in the query results. How can I query for campaigns while attaching their respective student counts? I ideally do not want to iterate through all my campaigns after the initial query to grab the counts.
IQueryable<TEntity> query = this._objectSet.AsQueryable(); //this is my campaigns object set
query = query.Where(c => c.UserId == id);
query = query.Include("");
return query.ToArray();
Update: --
Please note that my initial query is grabbing more than one campaign
I'm thinking maybe I could do something with a select but I am not exactly sure how to accomplish this
Querying for counts without loading the collection of child items is called an extra lazy query if you need the term to allow you to Google around this.
In this case, you would do something like this:
var campaign = query.Single();
var studentsQuery = context.Entry(campaign).Collection(c => c.Students).Query();
var count = studentsQuery.Count();
This will materialise the count of entities without bringing them all back.
I ended up adding a computed column with sql onto the Campaign Table.
CREATE FUNCTION dbo.getStudentCount(#studentCount int)
RETURNS int
AS
BEGIN
DECLARE #r int
select #r = COUNT(*) from Student where CampaignId = #studentCount
RETURN #r
END
GO
ALTER TABLE Campaign ADD StudentCount AS dbo.getStudentCount(Id)
this automatically sets the column to be a generated attribute in the EDMX.

Error in MySQL syntax when adding object to objectcontext

I seem to have trouble adding objects to tables that have a 'n to n' relationship.
Tables are defined as follows:
Table A
ID (PRIMARY)
...
...
...
Table B
ID (PRIMARY)
...
...
...
Table C
TableA_ID (index)
TableB_ID (index)
So basically Table C links Table A and B, by their IDs. Using the entity framework we now have an object TableA containing an Entity Collection of TableB entities.
However when I add an existing object of type TableB to the TableA.TableBs entity collection property, I receive an exception:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '(SELECT\n TableC.TableA, \n ' at line 1
It seems that I'm trying to do a very normal / common thing, however I've not been successful getting this to work.
C# code:
var database = new DatabaseEntities();
var tableAObject = database.SingleOrDefault(e => e.ID == 1);
var tableBObject = database.SingleOrDefault(e => e.ID == 1);
tableA.TableBEntities.Add(tableBObject);
database.SaveChanges();
Apparently I'm doing something wrong, so my question is, how should I add an object to Table C?

Entity Framework is slow because of derived tables

I am using MySQL Connector/Net 6.5.4 with LINQ to entities, and I frequently get terrible query performance because the entity framework generates queries that use derived tables.
Here is a simplified example of what I've encountered several times. In C#, I write a query like this:
var culverCustomers = from cs in db.CustomerSummaries where cs.Street == "Culver" select cs;
// later...
var sortedCustomers = culverCustomers.OrderBy(cs => cs.Name).ToList();
Instead of generating simple a query like this:
SELECT cust.id FROM customer_summary cust WHERE cust.street = "Culver" ORDER BY cust.name
The entity framework generates a query with a derived table like this:
SELECT Project1.id FROM (
SELECT cust.id, cust.name, cust.street FROM customer_summary cust
WHERE Project1.street = "Culver"
) AS Project1 -- here is where the EF generates a pointless derived table
ORDER BY Project1.name
If I explain both queries I get this for the first query:
id, select_type, table, type, possible_keys, rows
1, PRIMARY, addr, ALL, PRIMARY, 9
1, PRIMARY, cust, ref, PRIMARY, 4
... and something awful like this for the entity framework query
id, select_type, table, type, possible_keys, rows
1, PRIMARY, <derived2>, ALL, 9639
2, DERIVED, addr, ALL, PRIMARY, 9
2, DERIVED, cust, ref, PRIMARY, 4
Note the first row, where MySQL explains that it's scanning 9000+ records. Because of the derived table, MySQL is creating a temp table and loading every row. (Or so I'm deducing based on articles like this one: Derived Tables and Views Performance)
How can I prevent the Entity Framework from using a derived table, or how can I convince MySQL to do the obvious optimization for queries like this?
For completion, here is the view that is the source for this linq query:
create view customer_summary as
select cust.id, cust.name, addr.street
customers cust
join addresses addr
on addr.customer_id = cust.id
I think your query statement is missing 'select'. You have not identified the record(s) you want.
your query:
var culverCustomers = from cs in db.CustomerSummaries
where cs.Street == "Culver";
//no select
what are you selecting from the table? try this
example:
var culverCustomers = from cs in db.CustomerSummaries
where cs.Street == "Culver"
select cs;

DeleteObject from Entity Framework loads the whole table?

I have run into a problem with Entity Framework. My code tries to delete 1 or more objects mostly less then 10 from a table.
foreach (var val in vals)
{
int id = Convert.ToInt32(val);
var item = _container.Users.First(x => x.Id == id);
_container.Subscribers.DeleteObject(item);
}
_container.SaveChanges();
The current table "Users" has around 20 000 rows. When i run the code, if it only tries to delete one entity, it take around 10 secounds. I debuged the code and looked in the SQL Profiler. Everything runs smoothly until we hit the DeleteObject() method. It sends this sql query to the database:
exec sp_executesql N'SELECT
-- Yada
FROM [dbo].[Users] AS [Extent1]
WHERE [Extent1].[UserListId] = #EntityKeyValue1',N'#EntityKeyValue1 int',#EntityKeyValue1=1
Why are entity framework loading all the entites in the list? Straaange!
EDIT:
When i changed the code to:
int id = Convert.ToInt32(val);
Users u = new Users();
u.Id = Convert.ToInt32(val);
_container.Users.Attach(s);
_container.Users.DeleteObject(s);
It works like a charm! Still. The code before "_container.Users.First(x => x.Id == id)" did go to the database to find this object, but the after that loaded the whole table.
Below statement anyways makes a call to your database, it is not a strange but feature of EF.
If you have proper primary key created on User table in your database, ideally it will not take more time. It seems you are missing PK.
var item = _container.Users.First(x => x.Id == id);

Categories