How do you insert or update many to many tables in .net entity framework - entity-framework

This seems like it should be quite obvious but something about the entity framework is confusing me and I cannot get this to work.
Quite simply, I have three tables where the Id values are identity columns:
Users (userId, username)
Categories (categoryId, categoryName)
JoinTable (UserId, CategoryId) composite.
In the entities designer (this is .net 4.0), when I import these tables, as expected the join table does not appear but Users and Categories show a relationship. The following code:
var _context = new MyContext();
var myUser = new User();
myUser.UserName = "joe";
var myCategory = new Category();
myCategory.CategoryName = "friends";
_context.Users.AddObject(myUser);
myUser.Categories.Add(myCategory);
var saved = _context.SaveChanges();
Returns an error of (though nothing was added to the database):
An item with the same key has already been added.
If I add the following before saving:
_context.Categories.AddObject(myCategory);
myCategory.Users.Add(myUser);
I get the same error and nothing saved to the db. If I save the myUser and myCategory object before trying to associate them, they both save, but the second save throws an error, with nothing added to the join table:
Cannot insert the value NULL into column 'UserId', table '...dbo.JoinTable'; column does not allow nulls. INSERT fails. The statement has been terminated.
I'm clearly failing to understand how many to many relationships are inserted. What am I missing?

You do need to call SaveChanges() after adding User and Category entities to the database, and then set your association between them.
However, the real problem here is the second exception you listed. If you look at SqlProfiler or the ADO.NET profiler within the debugger, you will see that during the second SaveChanges call it looks something like this:
insert [dbo].[JoinTable]([UserId]) values (#0) select [CategoryId] from
[dbo].[JoinTable] where ##ROWCOUNT > 0 and [UserId] = #0 and [CategoryId] = scope_identity()
Obviously this won't work if you programmed your JoinTable correctly (composite PK on both columns).
If I look at the EntityModel store through Model Browser, it shows that the CategoryId column inside JoinTable does indeed have StoreGeneratedPattern set to Identity while UserId is set to None. Why EF did this during the generation phase when a composite PK was present is beyond me. I'll be posting a bug about this to MS, however in the mean time you can manually edit the edmx/ssdl file after generation to remove the Identity specifier. Find the StoreGeneratedPattern="Identity" string under the Property tag of the EntityType tag for your JoinTable and remove it:
Change:
<Property Name="CategoryId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
To:
<Property Name="CategoryId" Type="int" Nullable="false" />
Then when you run your code you will get a much better insert query (and no more exception!):
insert [dbo].[JoinTable]([UserId], [CategoryId]) values (#0, #1)

The way I have done this is to first generate a valid Category entity with the entity key.
Category myCategory = _context.Categories.First(i => i.CategoryID == categoryIDToUse);
Or you can try to create the entity as a stub to save the hit to the DB:
Category myCategory = new Category{CategoryID = categoryIDToUse };
Then add that entity to the entity set(CategorySet) on the ObjectContext using the AttachTo method(you may want to check if it is already attached). Then you can add the Category to your User entity using the Add method. Something like this:
myUser.Categories.Add(myCategory);
Call SaveChanges(). That has worked for me.

When new parent added to Context, state of all object in object tree changes to Added and hence EF tries to save all such objects as new record.
Refer this link:
http://nileshhirapra.blogspot.in/2012/03/entity-framework-insert-operation-with.html

Related

Updating entity without having the know primary key

Given the following code, how can I add an element to one of the properties of an entity without knowing its Id and retrieving it from the database?
public async Task BookInPersonVisitAsync(Guid propertyId, DateTime dateTime, CancellationToken token)
{
var entity = new OnBoardingProcessEntity{ ExternalId = propertyId };
DbContext.OnBoardingProcesses.Attach(entity);
entity.OnBoardingProcessVisits.Add(new OnBoardingProcessVisitEntity
{
DateTime = dateTime,
Occurred = false
});
await DbContext.SaveChangesAsync(token);
}
ExternalId is just a guid we use for external reference. This doesnt work cause it does not have the id set, but without hitting the database we cant have it.
With entity framework if you have to reference an entity (referencedEntity) from another entity (entity) you have to know referencedEntity.
Otherwise you can add just add the entity setting the referencedEntity to null.
To know the referencedEntity or you know the Id or you have to retrieve it in some ways (from the database).
In SQL (DML) if (and only if) ExternalId is a candidate key noy nullable you can insert the OnBoardingProcessVisit record with a single roundtrip but the insert statement will contain an inner query.
OnBoardingProcessVisit.OnBoardingProcess_Id = (
SELECT
Id
FROM
OnBoardingProcess
WHERE
ExternalId = #propertyId)
EDIT
No way to generate that query with EF. You can have a look to external components (free and not free, for example EntityFramework Extended but in this case I think that doesn't help).
In this case I probably would try to use standard entity framework features (so 1 roundtrip to retrieve the OnBoardingProcess from the ExternalId).
Then, if the roundtrip is too slow, run the SQL query directly on the database.
About performances (and database consistency) add a unique index on OnBoardingProcess.ExternalId (in every case).
Another suggestion if you decide for the roundtrip.
In your code, the entity will be a proxy. If you don't disable lazy load, using your code you will do one more roundtrip when you will access to property
entity.OnBoardingProcessVisits (in the statement entity.OnBoardingProcessVisits.Add).
So, in this case, disable lazy load or do the same using a different way.
The different way in your case is something like
var onBoardingProcessVisitEntity new OnBoardingProcessVisitEntity
{
DateTime = dateTime,
Occurred = false,
OnBoardingProcess = entity
});
DbContext.OnBoardingProcessVisits.Add(onBoardingProcessVisitEntity);
await DbContext.SaveChangesAsync(token);

Entity Framework: removing entity when One-to-One PK association is specified

I have existing DB with the following structure:
I'm using EF fluent API to configure relationships between tables:
public GroupEntityConfiguration()
{
HasMany(x => x.Employees).WithRequired().HasForeignKey(x => x.GroupId).WillCascadeOnDelete(true);
}
public EmployeeEntityConfiguration()
{
HasOptional(x => x.InnerGroupMember).WithRequired();
}
With this configuration applied I can add new Employee, new InnerGroupMember or fetch data. The problem appears when I try to remove Employee. Then I get an exception:
The relationship could not be changed because one or more of the foreign-key properties is non-nullable. When a change is made to a relationship, the related foreign-key property is set to a null value. If the foreign-key does not support null values, a new relationship must be defined, the foreign-key property must be assigned another non-null value, or the unrelated object must be deleted.
As far as I understand above exception is connected with GroupId foreign key. Trying to fix it I'm adding following line to EmployeeEntityConfiguration:
HasKey(x => new { x.Id, x.GroupId});
But after adding it I get another exception which I believe is connected with InnerGroupMember object:
Invalid column name 'Guest_Id'. Invalid column name 'Guest_GroupId'.
If I comment out InnerGroupMember navigation property and remove it's configuration, Employee can be removed.
Could you please give me a hint what I'm doing wrong and how to configure entities to be able to perform all needed operations? Thanks!
I have an existing Group entity and I want to remove Employee from the Employees Group collection:
var group = groupRepository.Find(groupId);
group.RemoveEmployee(employeeId);
_unitOfWork.Save();
RemoveEmployee function inside Group entity looks like this:
public void RemoveEmployee(int employeeId)
{
var employee = Employees.Single(n => n.Id == employeeId);
Employees.Remove(employee);
}
That's why I get an exeption:
The relationship could not be changed because one or more of the foreign-key properties is non-nullable....
After reading this post I wanted to fix it adding HasKey(x => new { x.Id, x.GroupId}); function inside EmployeeEntityConfiguration what leads to the second exception:
Invalid column name 'Guest_Id'. Invalid column name 'Guest_GroupId'.
Actually I made this step (I mean adding HasKey function) without changing DB structure. To make it work, inside Employees table I have to create composite key - combination of Id and GroupId which is also a foreign key. This modification forces changes inside InnerGroupMembers table. DB structure looks now as following:
Now I'm able to remove Employee in a way I showed at the beginning.
Anyway I'm not going for this solution. They are different ways to achieve what I want. Here are some links:
Removing entity from a Related Collection
Delete Dependent Entities When Removed From EF Collection
The relationship could not be changed because one or more of the
foreign-key properties is non-nullable
For one-to-one relationships cascading delete is not enabled by default, even not for required relationships (as it is the case for required one-to-many relationships, that is: The WillCascadeOnDelete(true) in your one-to-many mapping is redundant). You must define cascading delete for a one-to-one relationship always explicitly:
HasOptional(x => x.InnerGroupMember).WithRequired().WillCascadeOnDelete(true);
When you delete an Employee now, the database will delete the related InnerGroupMember as well and the exception should disappear.

History in database using entity framework

I want to store history in my table.
I have table Employee.
Employee
{
[Id],
[name],
[phone],
[isActive],
[createdDate],
[closedDate](default '23:59:59 9999-12-31'),
[DepartmentId] References Department(id)
}
When Employee is changed, I retrieve original values by Id and do isActive=False, closedDate=DateTime.Now and then I save modified value as new Employee with modified original values.
void UpdateEmployee(Employee employee)
{
ContextDB db=new ContextDB();
var employeeToUpdate=db.Employees.Find(it=>it.Id==employee.Id);
employeeToUpdate.isActive=false;
employeeToUpdate.closeDate=DateTime.Now;
var newEmployee=new Employee
{
Name=employee.Name,
Phone=employee.Phone,
....
}
db.Employees.AddObject(newEmployee);
// How I can do this with EF
db.Employees.Modify(employeeToUpdate);
db.SaveChanges();
}
How can I do this? And another question, what I need do if I have reference to another table Department and also want store history in this table. How should I do if changes Department in Employee object.
P.S. I use Self-Tracking Entities.
It should simply work without calling any Modify. You loaded entity from the database and you modified its fields while it is attached to the context so the context should know about changes and persist them to the database.
What I find totally bad about your architecture is that each change to your employee will result in active record with another Id. So if you are using and relation with employee table, foreign keys will not point to active record any more. If you want to do it this way you should not create a new record for active record but you should instead create a new record for deactivated record.

Entity Framework / EF4: Multiple inserts of related entities in a transactionscope

I have a similar problem.
I want to make two inserts in the same transactionscope. The objects are related and have a FK relationship between them, but for several reasons I do not want to connect them via the navigation property, but only by ID.
This is a simplification of what I what I want to accomplish:
Order o = new Order();
OrderDetails d = new OrderDetails();
new Repository().SaveNew(o, d);
class Repository{
void SaveNew(Order o, OrderDetails d){
using (TransactionScope transaction = new TransactionScope())
{
_context.Connection.Open();
// order
_context.Orders.ApplyChanges(o);
_context.SaveChanges();
// details
d.OrderID = o.ID;
_context.OrderDetails.ApplyChanges(d);
_context.SaveChanges(); <--- UpdateException
_context.Connection.Close();
transaction.Complete();
}
}
}
The problem is that I get an UpdateException because the FK evaluation fails. I tried to remove the FK relationship and running the exact same piece of code, and it worked fine, and both objects had the right properties set. So why does this approach fail? And how should this instead be done? Again, I do not want to attach the entites via their navigation properties.
Thank you!
I would leave the FK relationship in the database, but delete the AssociationSet and Association from the SSDL. The designer won't let you do this, you have to edit the XML manually.
I am using EF 4 btw.
Then use AddObject and SaveChanges in your SaveNew method to add the first (parent) object. Set the foreign key Property on the child and add it with AddObject and SaveChanges.
I do not have development environment running to test this, but what I think is happening is:
Assuming that the id is generated in the database. At the point when you save the order you do not know the ID.
Then the order ID of the order detail is set to the ID of the order, but the order was not reloaded from the database. I suspect that the value is 0.
When you try to save the order detail with FK of 0, you get an error.
Either save both at the same time so that EF does the work for you, or reload the order.

Entity Framework: Model doesn't reflect DB

I'm probably thinking about this all wrong but I have the following db tables:
When I run the EF Wizard in VS2008 I get the following model:
You'll notice that in the EF model shows that the Entity has no field for EntityTypeID or EntityStatusId. Instead it shows it as a navigation property, so the field appears to not be addressable when I instantiate an Entity (pardon the terminology confusion: Entity is a Table/Class in my name space not in the EF namespace). How can I assign an EntityTypeID and StatusTypeID when instantiating an Entity?
Yes, the entity framework hides foreign key ID properties and shows navigation properties instead. There is a lengthy discussion about why it does that, here. The usual means of assigning a reference to another entity is to assign the entity instance, rather than the foreign key ID value, like this:
var foo = new Entity();
var status = (from .... select ...).FirstOrDefault();
foo.StatusCodes = status;
However, it is possible to assign a foreign key ID directly, if you happen to know what it is:
foo.StatusCodesReference = new EntityKey(
"MyEntityContextName.StatusCodesEntitySetName", "StatusCodeId", value);
Obviously, substitute the real values in the above.