Entity Framework Interceptor to set Id field of patricular entities - entity-framework

In my current project i am working with the database which has very strange table structure (All Id Fields in most tables are marked as not nullable and primary while there is not auto increment increment enabled for them those Id fields need to be unique as well).
unfortunately there is not way i can modify DB so i find another why to handle my problem.
I have no issues while querying for data but during insert What i want to do is,
To get max Id from table where entity is about to be inserted and increment it by one or even better use SSELECT max(id) pattern during insert.
I was hoping to use Interceptor inside EF to achieve this but is looks too difficult for me now and all i managed to do is to identify if this is insert command or not.
Can someone help me through my way on this problem? how can i achieve this and set ID s during insert either by selecting max ID or using SELECT max(id)
public void TreeCreated(DbCommandTreeInterceptionContext context)
{
if (context.OriginalResult.CommandTreeKind != DbCommandTreeKind.Insert && context.OriginalResult.DataSpace != DataSpace.CSSpace) return;
{
var insertCommand = context.Result as DbInsertCommandTree;
var property = insertCommand?.Target.VariableType.EdmType.MetadataProperties.FirstOrDefault(x => x.Name == "TableName");
if (property == null) return;
var tbaleName = property?.Value as ReadOnlyCollection<EdmMember>;
var variableReference = insertCommand.Target.VariableType.Variable(insertCommand.Target.VariableName);
var tenantProperty = variableReference.Property("ID");
var tenantSetClause = DbExpressionBuilder.SetClause(tenantProperty, DbExpression.FromString("(SELECT MAX(ID) FROM SOMEHOWGETTABLENAME)"));
var filteredSetClauses = insertCommand.SetClauses.Cast<DbSetClause>().Where(sc => ((DbPropertyExpression)sc.Property).Property.Name != "ID");
var finalSetClauses = new ReadOnlyCollection<DbModificationClause>(new List<DbModificationClause>(filteredSetClauses) { tenantSetClause });
var newInsertCommand = new DbInsertCommandTree(
insertCommand.MetadataWorkspace,
insertCommand.DataSpace,
insertCommand.Target,
finalSetClauses,
insertCommand.Returning);
context.Result = newInsertCommand;
}
}
Unfortunately that concept of Interceptor is a little bit new for me and i do not understand it completely.
UPDATE
I manage to dynamically build that expression so that ID field is now included in insert statement, but the problem here is that I can not use SQL query inside it. whenever i try to use this it always results in some wrong SQL query so is there anyway i tweak insert statement so that this SELECT MAX(ID) FROM TABLE_NAME is executed during insert?

Get the next id from the context, and then set the parameter of the insert command accordingly.
void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
{
var context = interceptionContext.DbContexts.First() as WhateverYourEntityContainerNameIs;
// get the next id from the database using the context
var theNextId = (from foo in context...)
// update the parameter on the command
command.Parameters["YourIdField"].Value = theNextId;
}
Just bear in mind this is not terribly thread safe; if two users update the same table at exactly the same time, they could theoretically get the same id. This is going to be a problem no matter what update method you use if you manage keys in the application instead of the database. But it looks like that decision is out of your hands.
If this is going to be a problem, you might have to do something more drastic like alter the command.CommandText to replace the value in the values clause with a subquery, for example change
insert into ... values (#YourIdField, ...)
to
insert into ... values ((select max(id) from...), ...)

Related

How to retrieve generated fields such as generated id or updated_at field by a trigger during insert/update in springboot

In postgres it's possible to retrieve computed values using returning. I have a generated id field and a field called updated_at that is updated by a trigger whenever a row is modified.
I'd like to achieve something like this but looks like it's not working. Not sure it's the right syntax for bulk insert.
#Query(value = "INSERT INTO entities VALUES :values RETURNING *",nativeQuery = true)
List<Entity> insertAndReturn(#Param("values") List<Entity> entities);
The error is:
java.lang.IllegalStateException: org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing
I have also looked into jdbctemplate but it seems we are losing the benefit of entity mapping and we need to write own customized mapper.
That's not going to work. Native SQL doesn't support multi-valued parameters. You have to list all parameters individually. You can try something like this
default List<Entity> insertAndReturn(#Param("values") List<Entity> entities) {
for (int i = 0; i < entities.size(); i++ ) {
entities.set(i, insertAndReturn(entities.get(i)));
}
return entities;
}
default Entity insertAndReturn(Entity entity) {
return insertAndReturn(entity.getColumn1(), entity.getColumn2());
}
#Query(value = "INSERT INTO entities_table(column1, column2) VALUES (:column1, :column2) RETURNING *",nativeQuery = true)
Entity insertAndReturn(#Param("column1") String column1, #Param("column2") String column2);

Fetching id field of an entity in the SQL Transaction

I have the following schema.
Person(pid, pname)
Beer(bid, bname)
Likes(pid,bid)
I would like to insert a likes item. However, I am accepting the following format for the new users : (Pid, pname, bid, bname).
I would like to create a transaction for that to avoid conflict ( This is a highly simplified version of my real problem but the issue is the same). In my Person table, I set pid Auto-Increment(or Serial in Postgresql). Also the same goes for bid.
I have stuck in a point where I know the Person does not exist but the beer exists. So, I have to create a Person, then add an entity to Likes relation.
As far as I know, when I use the Autocommit(false) in dB, the transaction won't save until the commit. So, should I change the db design:
Change the auto-increment field to a normal integer, not null field.
In the transaction, after the autoCommit(false) has begun, read the last entry of the person
Increment it by one while creating the new person
Then create likes relation
Or, is there any other way around or do I miss something about transactions?
Here is what I have done so far:
try {
String add_person_sql = "INSERT INTO Person (name) VALUES(?)";
PreparedStatement add_person_statement = mydb.prepareStatement(add_person_sql);
String add_likes_sql = "INSERT INTO Likes (pid, bid) VALUES(?, ?)";
PreparedStatement add_likes_statement = mydb.prepareStatement(add_likes_sql);
mydb.setAutoCommit(false);
add_person_statement.setString(1, pname);
// The problem is, without saving the person I cannot know the id of the person
// AFAIK, this execution is not finished until commit occurs
add_person_statement.executeQuery();
// How can I fetch person's id
add_likes_statement.setString(1, pid);
add_likes_statement.setString(2, bid);
add_likes_statement.executeQuery();
mydb.commit();
}
catch(Exception e){
System.out.println(e);
mydb.rollback();
}
You can tell JDBC to return the generated ID from the insert statement, then you can use that ID to insert into the likes table:
mydb.prepareStatement(add_person_sql, new String[]{"pid"});
The second parameter tells the driver to return the generated value for the pid column.
Alternatively you can use
mydb.prepareStatement(add_person_sql, Statement.RETURN_GENERATED_KEYS);
that tells the driver to detect the auto increment columns.
Then run the insert using executeUpdate()
add_person_statement.setString(1, pname);
add_person_statement.executeUpdate();
int newPid = -1;
ResultSet idResult = add_person.getGeneratedKeys();
if (idResult.next()) {
newPid = idResult.getInt(1);
}
add_likes_statement.setString(1, newPid);
add_likes_statement.setString(2, bid);
add_likes_statement.executeUpdate();
mydb.commit();

Many-to-Many Inserts with Entity Framework

Say I have two entities with about 20 properties per entity and a Many-to-Many relationship like so:
User (Id int,Name string, .......)
Issue (Id int,Name string, .......)
IssueAssignment (UserId,RoleId)
I want to create a new Issue and assign it to a number of existing Users. If I have code like so:
foreach(var userId in existingUserIds)
{
int id = userId
var user = _db.Users.First(r => r.Id == id);
issue.AssignedUsers.add(user);
}
_db.Users.AddObject(user);
_db.SaveChanges();
I noticed it seems terrribly inefficient when I run it against my SQL Database. If I look at
the SQL Profiler it's doing the following:
SELECT TOP(1) * FROM User WHERE UserId = userId
SELECT * FROM IssueAssignment ON User.Id = userId
INSERT INTO User ....
INSERT INTO IssueAssignment
My questions are:
(a) why do (1) and (2) have to happen at all?
(b) Both (1) and (2) bring back all fields do I need to do a object projection to limit the
fields, seems like unnecessary work too.
Thanks for the help
I have some possible clues for you:
This is how EF behaves. _db.Users is actaully a query and calling First on the query means executing the query in database.
I guess you are using EFv4 with T4 template and lazy loading is turned on. T4 templates create 'clever' objects which are able to fixup their navigation properties so once you add a User to an Issue it internally triggers fixup and tries to add the Issue to the User as well. This in turns triggers lazy loading of all issues related to the user.
So the trick is using dummy objects instead of real user. You know the id and you only want to create realtion between new issue and existing user. Try this (works with EFv4+ and POCOs):
foreach(var userId in existingUserIds)
{
var user = new User { Id = userId };
var _db.Users.Attach(user); // User with this Id mustn't be already loaded
issue.AssignedUsers.Add(user);
}
context.Issues.AddObject(issue);
context.SaveChanges();

Implementing if-not-exists-insert using Entity Framework without race conditions

Using LINQ-to-Entities 4.0, is there a correct pattern or construct for safely implementing "if not exists then insert"?
For example, I currently have a table that tracks "user favorites" - users can add or remove articles from their list of favorites.
The underlying table is not a true many-to-many relationship, but instead tracks some additional information such as the date the favorite was added.
CREATE TABLE UserFavorite
(
FavoriteId int not null identity(1,1) primary key,
UserId int not null,
ArticleId int not null
);
CREATE UNIQUE INDEX IX_UserFavorite_1 ON UserFavorite (UserId, ArticleId);
Inserting two favorites with the same User/Article pair results in a duplicate key error, as desired.
I've currently implemented the "if not exists then insert" logic in the data layer using C#:
if (!entities.FavoriteArticles.Any(
f => f.UserId == userId &&
f.ArticleId == articleId))
{
FavoriteArticle favorite = new FavoriteArticle();
favorite.UserId = userId;
favorite.ArticleId = articleId;
favorite.DateAdded = DateTime.Now;
Entities.AddToFavoriteArticles(favorite);
Entities.SaveChanges();
}
The problem with this implementation is that it's susceptible to race conditions. For example, if a user double-clicks the "add to favorites" link two requests could be sent to the server. The first request succeeds, while the second request (the one the user sees) fails with an UpdateException wrapping a SqlException for the duplicate key error.
With T-SQL stored procedures I can use transactions with lock hints to ensure a race condition never occurs. Is there a clean method for avoiding the race condition in Entity Framework without resorting to stored procedures or blindly swallowing exceptions?
You can also write a stored procedure that uses some new tricks from sql 2005+
Use your combined unique ID (userID + articleID) in an update statement, then use the ##RowCount function to see if the row count > 0 if it's 1 (or more), the update has found a row matching your userID and ArticleID, if it's 0, then you're all clear to insert.
e.g.
Update tablex set userID = #UserID, ArticleID = #ArticleID (you could have more properties here, as long as the where holds a combined unique ID) where userID = #UserID and ArticleID = #ArticleID
if (##RowCount = 0)
Begin
Insert Into tablex ...
End
Best of all, it's all done in one call, so you don't have to first compare the data and then determine if you should insert. And of course it will stop any dulplicate inserts and won't throw any errors (gracefully?)
You could try to wrap it in a transaction combined with the 'famous' try/catch pattern:
using (var scope = new TransactionScope())
try
{
//...do your thing...
scope.Complete();
}
catch (UpdateException ex)
{
// here the second request ends up...
}

Entity Framework, How to include related entities in this example

I have a table AccountSecurity which is a many-to-many table that relates Account entities and Securities. When I write the query below it returns all Securities that satisfy the where clause. However each Security instance in the list no longer has the reference to the AccountSecurity it came from. So when I do list[0].AccountSecurity it is empty. Is there anyway to include that information? I know I can rewrite the query to return AccountSecurities instead and use .Include("Security") on that, but I wonder if it can be done another way.
var list = (from acctSec in base.context.AccountSecurities
where acctSec.AccountId == accountId
select acctSec.Security).ToList();
UPDATE
Of course if I do two queries the graph gets populated properly, there has to be a way to do this in one shot.
var securities = (from acctSec in base.context.AccountSecurities
where acctSec.AccountId == accountId
select acctSec.Security).ToList();
//this query populates the AccountSecurities references within Security instances returned by query above
var xref = (from acctSec in base.context.AccountSecurities
where acctSec.AccountId == accountId
select acctSec).ToList();
var list = (from sec in base.context.Securities
.Include("AccountSecurity")
where sec.AccountSecurities.Any(as => as.AccountId == accountId)
select sec).ToList();
Try this:
var list = (from acctSec in base.context.AccountSecurities.Include("Security")
where acctSec.AccountId == accountId
select acctSec).ToList();
Then simply use the Security property as needed, and since it's read at the same time AccountSecurities is (single SQL with join), it will be very efficient.