I'm using EF6 have some confusion on seeding a many to many relationship.
I have the following:
A User has many saved ChartQueries (that they can execute to get a chart).
A ChartQuery typically belongs to only one user, but there are several "shared" ChartQuerys that every User can execute. As a result I set up a many to many relationship using a join table UserChartQuery. The tables are up in the database just fine at 1-to-many on each side of the join table.
However, I'm not quite understanding how to seed or use this relationship. I don't want to end up with several duplicates of the "shared" ChartQuerys (a duplicate for each User). Instead, there should only be a single row for each "shared" ChartQuery that is a part of each User's SavedChartQueries collection (along with other, non-shared ChartQuerys that belong to that User only).
It seems like I'm forced to duplicate for each user:
var sharedChartQuery = new ChartQuery { ... };
var nonSharedChartQuery = new ChartQuery { ... };
var userOneChartQueryOne = new UserChartQuery { User = userOne, ChartQuery = sharedChartQuery };
var userTwoChartQueryOne = new UserChartQuery { User = userTwo, ChartQuery = sharedChartQuery };
var userTwoChartQueryTwo = new UserChartQuery { User = userTwo, ChartQuery = nonSharedChartQuery };
context.UserChartQueries.Add(userOneChartQueryOne);
context.UserChartQueries.Add(userOneChartQueryTwo);
context.UserChartQueries.Add(userTwoChartQueryTwo);
So first of all is this the right way to seed (through UserChartQueries table directly) or should I seed each User's SavedChartQueries navigation property?
And will this result in duplicate sharedChartQuery in the join table for each User? If so is there any way to avoid this?
Ok I understand how this works now. The following works as expected:
var userOne = new User {};
var userTwo = new User {};
var chartQuery = new ChartQuery { };
context.Users.Add(userOne);
context.Users.Add(userTwo);
context.UserChartQueries.Add(new UserChartQuery { User = userOne, ChartQuery = chartQuery });
context.UserChartQueries.Add(new UserChartQuery { User = userTwo, ChartQuery = chartQuery });
context.ChartQueries.Add(chartQuery);
The last line adds it to the table where the record actually resides. Checking the join table in SSMS shows that it only holds the foreign keys and nothing else. There are no duplicates.
Related
Apparently, EF6 doesn't like objects that have multiple foreign key properties that use the same key value, but do not share the same reference. For example:
var user1 = new AppUser { Id = 1 };
var user2 = new AppUser { Id = 1 };
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
When I attempt to insert this record, EF throws this exception:
Saving or accepting changes failed because more than one entity of type
'AppUser' have the same primary key value. [blah blah blah]
I've discovered that doing this resolves the issue:
var user1 = new AppUser { Id = 1 };
var user2 = user1; //same reference
I could write some helper code to normalize the references, but I'd rather EF just know they're the same object based on the ID alone.
As for why EF does this, one explanation could be that its trying to avoid doing multipe CRUD operations on the same object since separate instances of the same entity could contain different data. I'd like to be able to tell EF not to worry about that.
Update
So it's as I suspected per my last paragraph above. In absense of a means to tell EF not to do CRUD on either instance, I will just do this for now:
if (address.ModifiedBy.Id == address.CreatedBy.Id)
{
address.ModifiedBy = address.CreatedBy;
}
Works well enough so long as I am not trying to do CRUD on either.
Update2
I've previously resorted to doing this to prevent EF from validating otherwise-required null properties when all I need is the child entity's ID. However, it doesn't keep EF from going into a tizzy over separate instances with the same ID. If it's not going to do CRUD on either AppUser object, why does it care if the instances are different?
foreach (var o in new object[] { address.ModifiedBy, address.CreatedBy })
{
db.Entry(o).State = EntityState.Unchanged;
}
If you get AppUser from context, then you will not need to do anything, because Entity Framework will track entities:
var user1 = context.AppUsers.Find(1);
var user2 = context.AppUsers.Find(1);
var address = new Address
{
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
};
Now, they both will point to same objects and will not cause to conflict.
You can add two extra properties to have the Id for the main objects which is the AppUser, then you can use only one AppUser object and reference it for both the created and modified by properties.
CreatedById = user1.Id,
ModifiedById = user1.Id
Otherwise, your code will end up by saving two instances of AppUser with the same primary key.
Another approach is to set both the foreign key properties to only one AppUserobject
The explanation is that EF's change tracker is an identity map. I.e. a record in the database is mapped to one, and only one, CLR object.
This can be demonstrated easily by trying to attach two objects with the same key:
context.AppUsers.Attach(new AppUser { Id = 1 });
context.AppUsers.Attach(new AppUser { Id = 1 });
The second line will throw an exception:
Attaching an entity of type 'AppUser' failed because another entity of the same type already has the same primary key value.
This also happens if you assign
CreatedBy = user1, //different reference
ModifiedBy = user2 //different reference
Somewhere in the process, user1 and user2 must be attached to the context, giving rise to the exception you get.
Apparently, you have a function that receives two Id values that can be different or identical. Admittedly, it would be very convenient if you could simply create two AppUser instances from these Ids, not having to worry about identical keys. Unfortunately, your solution ...
if (address.ModifiedBy.Id == address.CreatedBy.Id)
... is necessary. Solid enough, though.
I have some code like this:
var db = new MYContext();
var invoice = new Invoice { InvoiceId = 7 };
db.Set<Invoice>().Add(invoice);
var invoiceFound = db.Set<Invoice>().Find(7);
var invoices = db.Set<Invoice>().ToList();
invoiceFound gets populated with the invoice.
The problem is invoices is returning an empty list.
Could someone please explain this to me?
If I remember correctly, calling ToList() makes a call to the database and returns the result set. Since you have not saved your changes (add of the invoice) before calling ToList(), the Invoice you added will not be in the result set. There is a Local property on DbSet that returns your in memory collection of Invoices. This collection will contain the Invoice you added even if you don't SaveChanges().
Please try this one:
var db = new MYContext();
var invoice = new Invoice { ID = 7 };
db.AddToInvoice(invoice);
db.SaveChanges();
var qry = from item in db.Country select item;
IList<Invoice> list = qry.ToList<Invoice>();
For example I have 3 tables:
Users -- the master table
{ Id, Name }
Permissions -- details
{ Id, PermissionTitle }
UserPermissions -- is a relation table between User and its Permissions
{ UserId , PermissionId}
I have 2 users in the tbUsers ( {1,"user1"} , {2,"user2"} )
and I have 3 permissions in the tbPermissions ( {1,"perm1"} , {2,"perm2"} , {3,"perm3"} )
now I want to add perm1 and perm2 to user1. What should I do in EF?
(I don't want to create/insert any Users or Permissions, I just want to add a relationship between them in the relation table)
because of EF, I don't have UserPermissions table in my dataModel.
If you want to load entities first you can do:
using (var context = new YourContext())
{
var user1 = context.Users.Single(u => u.Id == 1);
var perm1 = context.Permissions.Single(p => p.Id == 1);
var perm1 = context.Permissions.Single(p => p.Id == 2);
user1.Permissions.Add(perm1);
user1.Permissions.Add(perm2);
context.SaveChanges();
}
If you know Ids and you don't want to load entities first you can do:
using (var context = new YourContext())
{
var user1 = new User {Id = 1};
var perm1 = new Permission {Id = 1};
var perm1 = new Permission {Id = 2};
context.Users.Attach(user1);
context.Permissions.Attach(perm1);
context.Permissions.Attach(perm2);
user1.Permissions.Add(perm1);
user1.Permissions.Add(perm2);
context.SaveChanges();
}
These two approaches can be combined - for example you can load user from DB and create dummy objects only for permissions.
Users should have a navigation property Permissions so you need to add the permission to that collection. Should look similar to this:
user.Permissions.Add(permission1);
user.Permissions.Add(permission2);
context.SaveChanges();
I m getting this Exception-"The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects."
I ve user table and country table. The countryid is referred in user table.
I am getting the above Exception when I am trying to add entry in user table.
This is my code-
using (MyContext _db = new MyContext ())
{
User user = User .CreateUser(0, Name, address, city, 0, 0, email, zip);
Country country = _db.Country.Where("it.Id=#Id", new ObjectParameter("Id",countryId)).First();
user.Country = country;
State state = _db.State.Where("it.Id=#Id", new ObjectParameter("Id", stateId)).First();
user.State = state;
_db.AddToUser(user );//Here I am getting that Exception
_db.SaveChanges();
}
Try adding the user first, then adding the relationships.
See http://www.code-magazine.com/article.aspx?quickid=0907071&page=4
Or, don't use User.CreateUser where you are explicitly setting an Id = 0, instead use User user = new User() {Name = Name, Address = ...}
BTW, with Entity Framework 4 you can set the foreign key IDs directly removing the need to load the related object if you know its ID.
I have a User table and a Group table. Between these is a UserGroups intersection table to allow a user to belong to any number of groups.
The groups table is already populated with values.
How do I add a group to this user so that in the intersection table the relationship between the user and a group is created?
My Primary Keys auto increment.
My DB structure:
My EF structure:
(source: livefilestore.com)
Looks like i was missing the plot aboit.
the solutions is very simple.
Here is a little examle.
Thanks
using (UserEntities ctx = new UserEntities())
{
var group = (from g in ctx.Group
select g).FirstOrDefault();
User user = new User();
user.UserName = "Ian";
user.UserGroups.Add(new UserGroups { Group = group });
ctx.AddToUser(user);
ctx.SaveChanges();
}