I create a stored procedure which returns a table (physically stored table).
CREATE PROCEDURE uspTest
AS
BEGIN
SELECT * from Table1
END
When I capture the output using Entity Framework, it loads up the entity properly.
var output = entities.Database.SqlQuery<Table1>("dbo.uspTest").ToList<Table1>();
the "output" variable contains the data returned from SP but it this List of Table1 object doesn't not load up foreign key tables automatically. I added below code to mitigate this problem
foreach (var member in output)
{
entities.Table1s.Attach(member);
}
After attaching each entity all child tables are linked for each Table1 member. But, when I come back to this same method second time, it gives me an error failing to attach.
Attaching an entity of type 'Table1' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values.
i tried setting the state of entity to detached but of no luck!! Does anyone have any clue what i should do here?
I'm using Database first approach.
The results of Database.SqlQuery<TElement> are never tracked by the context even if the type of object returned is an entity type. If you want the returned entities are tracked by the context, then you should use DbSet<TEntity>.SqlQuery method:
var output = entities.Table1s.SqlQuery("dbo.uspTest").ToList();
This way you don't need to use extra code to attach the entities.
But, why do you want to access to the Table1 using a SP when you already have the DBSet<Table1> Table1s?. I guess you have a more complex stored procedure and you did this to explain your issue, but if that is your SP, as #DLeh commented above, you should use Linq to Entities.
Related
I have code like this:
db.Insert(myObject);
The key for the type myObject is of type System.Guid which is auto generated by RDBMS.
I then want to insert child objects that will use that key to relate to their parents.
foreach(var child in myObject.ChildObjects) {
child.ParentId = parentIdThatISomehowSaved; //this is what I don't know how to do
db.Insert(child);
}
I am assigning a new Guid to the record via SQL Server when the insert occurs.
What is the best way to get the Id of the new record so I can populate it as a foreign key on child objects? The Dapper.Contrib.Extensions.Insert method returns a long, so is there a way to make this work with a Guid?
UPDATE: 02-May-2022
From source code, it appears that this feature is now implemented. I have not tested it though.
It appears that Insert<T> method will return (it will NOT map to entity property) newly generated numeric ID (return type of method is long) if only single entity is being inserted. If list of entities are being inserted, it will return number of rows inserted.
I am not sure how will this work with GUID IDs.
Following is the code-comment:
/// Inserts an entity into table "Ts" and returns identity id or number of inserted rows if inserting a list.
...
...
/// <returns>Identity of inserted entity, or number of inserted rows if inserting a list</returns>
Also, observe the code that inserts single entity and should return newly generated ID as per code-comment above:
if (!isList) //single entity
{
returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(),
sbParameterList.ToString(), keyProperties, entityToInsert);
}
else
{
//insert list of entities
var cmd = $"insert into {name} ({sbColumnList}) values ({sbParameterList})";
returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout);
}
Original Answer:
The Insert method of Dapper Contrib does not return the newly generated ID. This is because Contrib is built over Dapper which neither map the newly generated ID to the entity by default nor does it returns it. But there are ways to return the newly generated ID using Dapper. Those are discussed here, here and here.
Dapper Extensions (other query generator for Dapper) support this by default. Please refer to this answer for more details.
One way is to bypass the Contrib and use Dapper; use any of the way explained in links to get the newly generated ID (OUTPUT parameter or ##IDENTITY etc.).
Other way to do this with Contrib is to get the newly generated ID by separately calling Dapper (as mentioned in links; ##IDENTITY) immediately after a call to Insert.
Third way is to explicitly assign the ID instead of automatically generating it. You can use [ExplicitKey] attribute to achieve this. You also need to change your database accordingly.
[Key] should be used for database-generated keys (e.g. autoincrement columns), while [ExplicitKey] should be used for explicit keys generated in code.
More aggressive solution is to modify the Contrib code to make Insert return newly generated ID.
By the way, I found an issue similar to your problem; see if that helps you.
In my program, I'm using Entity Framework and added a table to my .edmx model. I mapped the insert, update, and delete functions to only one stored procedure, my update procedure, since it is the only procedure I will be using on this table. In my program, I loop through a bunch of users getting values and adding them to the entity before finally saving the changes.
Start loop
var newGroup = new ru_Groups();
newGroup.adsPath = adsPath;
newGroup.DisplayName = displayName;
newGroup.Domain = domain;
newGroup.AccountName = accountName;
newGroup.Email = email;
newGroup.GroupType = groupType;
UserEntities.ru_Groups.Add(newGroup);
end loop
UserEntities.SaveChanges();
The problem is when I save the changes I get this error
The changes to the database were committed successfully, but an error
occurred while updating the object context. The ObjectContext might be
in an inconsistent state. Inner exception message: Saving or accepting
changes failed because more than one entity of type
'AD.Data.ru_Groups' have the same primary key value. Ensure that
explicitly set primary key values are unique. Ensure that
database-generated primary keys are configured correctly in the
database and in the Entity Framework model. Use the Entity Designer
for Database First/Model First configuration. Use the
'HasDatabaseGeneratedOption" fluent API or
'DatabaseGeneratedAttribute' for Code First configuration.
Why is this happening? The ID StoredGeneratedPattern property is set to Identity, however the stored procedure which I've mapped to the Update function does not require an ID as a parameter, let alone a unique ID.
Thank you
Violation of PRIMARY KEY constraint 'PrimaryKeyId'. Cannot insert duplicate key in object 'dbo.Table'. The duplicate key value is (xxx).\r\nThe statement has been terminated.
This has been occurring off and on for several weeks now, and every time I think I have it fixed, and it pops up a couple days later. I am using:
dbContext.Table.Add(myObject);
dbContext.SaveChanges();
This is in a using statement, trying to add an object with a current id of 0. The PrimaryKeyId is the identity in the table, and is set to auto-increment by 1. However, Entity Framework seems to be taking a random Id and trying to assign it to my object and then add said object to the database.
This only happens on this one table and this same process is used on many other tables without any problems. The table being acted on is set up identical to other tables where this process is being used without error. Any ideas as to what could be causing this? To clarify, Entity Framework appears to be attempting to assign an already existing Primary Key to a new object.
Solution to my specific problem: Tar and feather a DBA
Explanation: While running a lengthy/complex import script, our DBA set it up to reseed the table at x, which is way below the current value in the identity column. So there technically hasn't been a problem these past several weeks, it was just human error. This question could/should/maybe ought to be dragged out behind a woodshed and put out of its misery. The tarring/feathering is his suggestion by the way (I do not endorse abuse of coworkers without their consent).
In the event that this question isn't deleted, the recommended fix is to check the current identity value on the table by using
select ident_current('tableName')
and comparing it to the highest value in the table. Especially if there are manual imports/modifications being done through a script where the seed might be manually reset.
This should prove helpful (Particularly the paragraph I have made bold): Taken from Working with Entity Keys
Entity Keys and Added Objects
When a new entity is created, the Entity Framework defines temporary key and sets the IsTemporary property to true. When you call the SaveChanges method, the Entity Framework assigns a permanent key and sets the IsTemporary property to false.
If the corresponding column value is an identity that is generated in the database, set the StoreGeneratedPattern attribute of the property element of an entity in the storage model to Identity. When the Entity Data Model tools generate a data model from an existing data source, the StoreGeneratedPattern attribute is added to each property element (CSDL) element that represents an identity or a computed column in the data source. The Entity Framework replaces the value of the property in a temporary key with the identity value that is generated by the data source after SaveChanges is called.
The following details the internal process that replaces the temporary key with a permanent key that contains the server-generated values:
The entity object is constructed.
At this point the key properties all have default values, either null or 0.
The new object is added to the ObjectContext either by calling the AddObject method on ObjectContext or ObjectSet or by adding an object to the collection of objects on the "many" end of the relationship.
At this point, the Entity Framework generates a temporary key, which is used to store the objects in the ObjectStateManager.
SaveChanges is called on the ObjectContext.
An INSERT statement is generated by the Entity Framework and executed on the data source.
If the INSERT operation succeeds, server-generated values are written back to the ObjectStateEntry.
The ObjectStateEntry updates the object with the server-generated value.
When AcceptChanges is called on the ObjectStateEntry, a permanent EntityKey is computed by using the new server-generated values.
AcceptChanges is called automatically at the end of the SaveChanges execution, or when the SaveChanges method is called with the AcceptAllChangesAfterSave flag.
The ObjectStateManager replaces all instances of the temporary key with the new permanent key.
I'm trying save entity to db using dbContext.
Type entityType = Type.GetType("class");
object ob = db.Set(entityType).Create(entityType);
ob.GetMethod("set_Id").Invoke(ob, new object[] { newId });
//...other set code...
db.Set(entityType).Add(ob);
db.SaveChanges(); -- here fires exception
But after SaveChanges fire the Exception
"Cannot insert explicit value for identity column in table 'TableName'
when IDENTITY_INSERT is set to OFF".
In the profiler I see the standard insert batch with the id I set. How can I add entity object to db with identity insert ON or how can I just save the new entityObject?
Entity Framework assumes that integer primary keys are database generated. If you don't want that you have to turn it off with the attribute HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) or calling Property(e => e.EventID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None) using the Fluent API.
You now have a problem because this only works first time around. You will have to drop the table or use one of the other options here how to switch identity on/off in entity framework code first migrations
It seems you have a table definition that says the database should inserts ID's and not allow the application to do this. At the same time an entity-framework layout that tries to insert values for id's.
You can allow the application to insert ID's by using this:
http://technet.microsoft.com/en-us/library/aa259221(v=sql.80).aspx
to allow it.
The question is do you really want to allow that applications can choose their own IDs? Or do you want to let the database decide?
In this case you should check the properties in your dbml file. The column Id should have a property "Auto Generated Value". It has to be set to True.
I have simple model with a "BaseEntity" and a derived "Fund" entity.
When I try to insert a new fund:
HybridModelContainer container = new HybridModelContainer();
//Create new Fund
Fund fund = new Fund();
fund.Id = Guid.NewGuid();
fund.DateCreated = DateTime.Now;
fund.Name = "Fund 1";
fund.Number = 1;
container.BaseEntities.AddObject(fund);
container.SaveChanges();
I get the following error:
"Cannot insert the value NULL into column 'Id', table 'HybridData.dbo.BaseEntities'; column does not allow nulls. INSERT fails.
The statement has been terminated."
It seems that the ID assigned to the fund entity is not inserted into the BaseEntity table. Why not?
I did this "model first". If I design the database first, and create the model from it, everything works fine....But I need model first!
Also...why isn't there an ObjectSet for "Funds" in my DataContext (i.e., container.Funds)? Thanks in advance for your help!
I can only answer for the second part of your question: "Also...why isn't there an ObjectSet for "Funds" in my DataContext (i.e., container.Funds)?"
It's normal that the ObjectContext has only an ObjectSet of the base class in your class hierarchy. There isn't really a need for an ObjectSet of the derived classes. For adding and deleting derived objects to/from the ObjectContext you can just use the AddObject and DeleteObject methods of the ObjectSet<BaseEntity> / BaseEntities as you already did in your example.
For querying derived objects you can leverage the OfType method of the ObjectSet. It returns an ObjectQuery of the derived type where you can build further queries upon:
ObjectQuery<Fund> query = container.BaseEntities.OfType<Fund>();
(The first part of your question sounds like a mapping error between the storage and the conceptual model. It might have a better cif you could show the relevant parts of your edmx file.)
Edit: Possible solution to the first part of the question:
I've created your example with Model-first in VS2010 and I could reproduce your issue. The problem seems to be related to the fact that the primary key in your model isn't an Int32 but a Guid. When you add a new Entity to the Model the designer always proposes an Int32 as primary key with StoreGeneratedPattern set to Identity.
If you change now the type of the key in the model designer from Int32 to Guid but leave the StoreGeneratedPattern unchanged being Identity, the DDL creates a database in SQL Server with Id set to type uniqueidentifier but Identity specification for that column is "No".
So, when EF sends an INSERT command to the DB it "thinks" from the model definition the primary will be autogenerated in the DB and doesn't send the Guid to the DB which you have assigned in code. But the DB doesn't create the key, resulting in a NULL value for the key. Hence the error we get.
Solution: Set in the model designer for the Id property of BaseEntity the StoreGeneratedPattern to None. For me it worked then. Of course, you are responsible then to always assign a Guid to Id before you add a new object to the ObjectSet, but that seems to be what you want anyway.