MVC4 save data using Many to Many relationship - entity-framework

Many to Many Relationship
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Tags)
.WithMany(t => t.Companies)
.Map(m =>
{
m.MapLeftKey("Companyid");
m.MapRightKey("tagid");
m.ToTable("CompanyTags");
}
}
Add company
var company = new Company() { Name = "FooBar Inc" };
Add Tag
int tagId = _db.Tags.Where(x => x.Title == tag).Select(x => x.Id).SingleOrDefault();
if (tagId==0)
company.Add(new Tag { Title = tag});
else
?????? //still create a relationship in CompanyTags (companyid,tagid)
context.Companies.Add(company);
context.SaveChanges();
How to configure so when a new company gets created and if the tag exits in the Tag table. Dont create the tag but still create the relationship in the CompanyTags table
UPDATE
without if condition, if user for example adds tag title dog and if it exists a new record gets created in the tag table. Instead I want no tag to be created in the tag table just in the mapping table, see screenshot below

solved by implementing
_db.Tags.FirstOrDefault(x => x.Title == tag)
instead of doing new Tags...

Related

How to soft delete one to many relationships in EF core?

Let's say that I have a Category and Product entity with one to many relationship and when I delete Category I want to delete all products that belong to the category. And by deleting I mean setting IsDeleted flag to true since I don't want to delete it for real (which I could by specifying on delete cascade). I found out the way to set IsDeleted to true when Category is deleted however I can't figure out how to find products of that category and do the same thing for them. Any help?
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
foreach(var item in ChangeTracker.Entries<Category>().Where(e => e.State == EntityState.Deleted))
{
item.State = EntityState.Modified;
item.CurrentValues["IsDeleted"] = true;
}
return base.SaveChanges();
}
I also specified query filter so that I don't get deleted items
builder.Entity<Category>().Property<bool>("IsDeleted");
builder.HasQueryFilter(c => !EF.Property<bool>(c, "IsDeleted"));
Just use on cascade delete. When you delete category also all products of that category will be marked as deleted based on navigation property. This will be done by EFCore itself. I'm using this aproach in my code to soft delete one to many relation. Then use (so your code repeated on products):
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
foreach(var item in ChangeTracker.Entries<Category>().Where(e => e.State
== EntityState.Deleted))
{
item.State = EntityState.Modified;
item.CurrentValues["IsDeleted"] = true;
}
foreach(var item in ChangeTracker.Entries<Product>().Where(e => e.State
== EntityState.Deleted))
{
item.State = EntityState.Modified;
item.CurrentValues["IsDeleted"] = true;
}
return base.SaveChanges();
}

AspNetUserRoles not in EDMX when generating from database

I have did some searching around on this issue and have come across a few questions in regards to AspNetUserRoles not being in the EDMX designer when generating from the database. However its in the ModelBrowser and I can't get this table to show up so I can use Roles Authorization.
When I hit this method in my Roles class
public override string[] GetRolesForUser(string username)
{
DTE = new DatabaseTestingEntities();
string userID = DTE.AspNetUsers.Where(w => w.Email == username).Select(s => s.Id).FirstOrDefault();
string roleID = DTE.AspNetUsers.Include("AspNetRoles").Where(s => s.Id == userID).FirstOrDefault().ToString();//.AspNetUserRoles.Where(w => w.UserId == userID).Select(s => s.RoleId).FirstOrDefault();
string roleName = DTE.AspNetRoles.Where(w => w.Id == roleID).Select(s => s.Name).FirstOrDefault();
string[] results = { roleName };
return results;
}
The results always come back as null..
However it should look like this instead
public override string[] GetRolesForUser(string username)
{
DTE = new DatabaseTestingEntities();
string userID = DTE.AspNetUsers.Where(w => w.Email == username).Select(s => s.Id).FirstOrDefault();
string roleID = DTE.AspNetUserRoles.Where(w => w.UserId == userID).Select(s => s.RoleId).FirstOrDefault();
string roleName = DTE.AspNetRoles.Where(w => w.Id == roleID).Select(s => s.Name).FirstOrDefault();
string[] results = { roleName };
return results;
}
But that way throws an error because the AspNetUserRoles isn't in the EDMX designer when I generate the EF from the database.
How can I get this table to appear so I can continue on with what I need to do?
I have tried updating the EDMX and that doesn't work either.
I just had this question myself more or less... "Where is the AspNetUserRoles table in the model?"
My understanding is that the AspNetUserRoles table is created and consists of two foreign keys, one to the AspNetUsers table for it's Id value, and one to the AspNetRoles table, also for its Id value. When you assign a role to a user, it adds a row into the AspNetUserRoles table so as to give you what is called a "Navigation Property" on the AspNetUsers table. Look at your edmx and find the AspNetUsers table, at the bottom you'll see a Navigation Property of "AspNetRoles" and this collection is available to you in code on an AspNetUser object.
As a user can belong to many roles, this Navigation Property is a collection that can be assigned to a List something like this:
AspNetUser selectedUser = dbContext.AspNetUsers.FirstOrDefault(u => u.UserName == "foo");
if (selectedUser == null) return;
List<AspNetRole> selectedUsersRoles = selectedUser.AspNetRoles.ToList();
For the original poster's I would return the List and work with that...
public override List<AspNetRoles> GetRolesForUser(string username)
{
DTE = new DatabaseTestEntities();
AspNetUser selectedUser = DTE.AspNetUsers.FirstOrDefault(u => u.UserName == username);
if (selectedUser == null) return null; //User not found - return null
return List<AspNetRole> selectedUsersRoles = selectedUser.AspNetRoles.ToList();
}
This basically means you don't "need" the AspNetUserRoles table explicitly. You should be able to work with a user's roles as noted above. I'm not sure if it's recommended or not, but I would not directly insert into the AspNetUserRoles table either. You should just add a role to the user object and let the UserRoles table update automatically.

Clear related items followed by parent entity update - Many to Many EntityFramework with AutoMapper

I am trying to remove all references followed by adding them back from a list of disconnected objects.
using(var scope = new TransactionScope())
{
_autoIncidentService.AddNewCompanyVehicles(
autoIncidentModel.CompanyVehiclesInvolved.Where(v => v.Id == 0));
_autoIncidentService.ClearCollections(autoIncidentModel.Id);
_autoIncidentService.Update(autoIncidentModel);
scope.Complete();
}
return Json(ResponseView);
The ClearCollections removes items references. The GetAutoIncident includes the collection.
public void ClearCollections(int id)
{
var autoIncident = GetAutoIncident(id);
foreach (var vehicle in autoIncident.CompanyVehiclesInvolved.ToArray())
{
autoIncident.CompanyVehiclesInvolved.Remove(vehicle);
}
db.SaveChanges();
}
When I try to update the entity right after the ClearCollections method it fails.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
I am using a singleton to get the DbContext so there shouldn't be any situation where the context is different. It is being stored in the HttpContext.Current.Items.
The update method is as follows:
public override void Update(AutoIncidentModel model)
{
var data = GetData(model.Id);
Mapper.CreateMap<AutoIncidentModel, AutoIncident>()
.ForMember(m => m.CompanyVehiclesInvolved, opt => opt.ResolveUsing(m =>
{
var ids = m.CompanyVehiclesInvolved.Select(v => v.Id);
return db.Vehicles.Where(v => ids.Contains(v.Id)).ToList();
}));
Mapper.Map(model, data);
db.SaveChanges();
}
Obviously, I am missing something important here. Do the entites from my ResolveUsing method need to somehow be associated with the parent entity or is automapper overwriting the property (CompanyVehiclesInvolved) and causing a problem?

Entity Framework with mapping table adding duplicate entries

I have two tables customer and products with many to many relationship which I handle with mapping table called customerproductmap. I then created entity framework model from this database.
This is what I have for onmodelcreation for the customer:
modelBuilder.Entity<customer>()
.HasMany(e => e.products)
.WithMany(e => e.customers)
.Map(m => m.ToTable("customerproductMap").MapLeftKey("customerId").MapRightKey("productId"));
When I add new data using Context as follows, I see that duplicate entries are being added to customer. The json data I get from the client has parent object with multiple customers. Each customer can have multiple products mapped to it, but these products might have been already added by the previous customer. For e.g. customer1-> products1 and product2 and customer2->Product1. This results in addition of Product1 twice and then these are mapped in the mapping table.
What I want is Product1 is added only once and then mapped to customer1 and customer2 since it’s the same Product. How can I achieve this?
using (var context = new DbEntity())
{
context.ParentObject.Add(GetEntityFromClientData((clientdata)));
try
{
context.SaveChanges();
}
catch (DbEntityValidationException e)
{
.. processing of exception message
}
}
I am new to entity framework, so it might be a simple thing I am missing.
EDIT:
Here is what GetEntityFromClientData does (the relevant parts). You see that I am mapping product1 and product2 to customer1 and product1 to customer2. Then add the region to the database. If I dont then how will I map customer2 to product1.
var customers = new List<Customer>();
foreach (var clientCustomer in clientData.Customers)
{
var q = from o in clientCustomer.Products
select new Product()
{
..basic initialization,
};
var customer = new Customer()
{
Name = clientCustomer.Name,
Products = q.ToArray(),
CustomerId = clientCustomer.Id,
};
customers.Add(customer);
}
var customerLocation = new CustomerLocation ()
{
city = clientData.City,
Customers= customers.ToArray(),
};
var customerLocations = new CustomerLocation []
{
customerLocation
};
var region = new Region()
{
CustomerLocations = customerLocations,
};
return region;

Entity Framework Saving many to many changes

I have a Users 1-* UserGroupLinks *-1 UserGroups Table structure and have created the following method:
public static string SaveUser(User user, List<UserGroup> newUserGroups)
{
using (var dbContext = new DCSEntities())
{
var existingUserGroups = user.UserGroups.ToList<UserGroup>();
existingUserGroups.ForEach(d => user.UserGroups.Remove(d));
newUserGroups.ForEach(a => user.UserGroups.Add(a));
dbContext.ObjectStateManager.ChangeObjectState(user, EntityState.Modified);
dbContext.SaveChanges();
return user.UserCode;
}
}
I would like to remove all the usergrouplinks for that user, and then add the new list of usergroups. When I run this method I get a violation of primary key on the UserGroups object/UsergroupLink table, indicating that my attempt at removing the existing usergrouplinks has failed. How can I resolve this error?
So I've changed the original code to confirm a suspicion.
public static string SaveUser(User user, List<UserGroup> newUserGroups)
{
using (var dbContext = new DCSEntities())
{
User dbUser = dbContext.Users.Where(u => u.UserCode == user.UserCode).Include(ug => ug.UserGroups).Include(s => s.Status).FirstOrDefault();
var existingUserGroups = dbUser.UserGroups.ToList<UserGroup>();
existingUserGroups.ForEach(d => dbUserUserGroups.Remove(d));
newUserGroups.ForEach(a => dbContext.UserGroups.Attach(a));
newUserGroups.ForEach(a => dbUser.UserGroups.Add(a));
dbContext.ObjectStateManager.ChangeObjectState(dbUser, EntityState.Modified);
dbContext.SaveChanges();
return dbUser.UserCode;
}
}
What I can confirm is that this code works when and only when adding new groups for that user. As soon as you try and add an existing group, it gives the primary key violation. It is almost as if the line
existingUserGroups.ForEach(d => dbUserUserGroups.Remove(d));
Is not taking effect.
My solution below is not elegant and therefore I have not marked it as an answer.
The idea to use 2 different dbcontexts actually worked, but as per my comment above, I don't think it is an elegant or the correct solution:
public static string SaveUser(User user, List<UserGroup> newUserGroups)
{
using (var dbContext = new DCSEntities())
{
User dbUser = dbContext.Users.Where(u => u.UserCode == user.UserCode).Include(ug => ug.UserGroups).Include(s => s.Status).FirstOrDefault();
var existingUserGroups = dbUser.UserGroups.ToList<UserGroup>();
existingUserGroups.ForEach(d => dbContext.UserGroups.Detach(d));
existingUserGroups.ForEach(d => dbUser.UserGroups.Remove(d));
dbContext.ObjectStateManager.ChangeObjectState(dbUser, EntityState.Modified);
dbContext.SaveChanges();
}
using (var dbContext = new DCSEntities())
{
User dbUser = dbContext.Users.Where(u => u.UserCode == user.UserCode).Include(ug => ug.UserGroups).Include(s => s.Status).FirstOrDefault();
newUserGroups.ForEach(a => dbContext.UserGroups.Attach(a));
newUserGroups.ForEach(a => dbUser.UserGroups.Add(a));
dbContext.ObjectStateManager.ChangeObjectState(dbUser, EntityState.Modified);
dbContext.SaveChanges();
return dbUser.UserCode;
}
}
I even went as far as to copy this solution verbatim and still got the primary key violation issue entity framework update many to many relationship: virtual or not
If anyone can explain why I need to do this with 2 different contexts I would greatly appreciate it. Thanx