I have the following two classes:
[Table("Products")]
public class Product
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public virtual ICollection<Process> Processes { get; set; }
public Product()
{
this.Processes = new HashSet<Process>();
}
}
and
[Table("Processes")]
public class Process
{
public int Id { get; set; }
public string Name { get; set; }
public string MachineName { get; set; }
//list of all products
public virtual ICollection<Product> Products { get; set; }
public Process()
{
this.Products = new HashSet<Product>();
}
}
As you can see, One product can have multiple processes and one process can be bound to multiple products. (e.g. product Chair consists of the following processes: Cutting, Drilling, Screwing, etc...
Under my OnModelCreating method in my DataContext I have specified Many to Many relationships as follows:
modelBuilder.Entity<Process>()
.HasMany<Product>(s => s.Products)
.WithMany(c => c.Processes)
.Map(cs =>
{
cs.MapLeftKey("Process_ProcessId");
cs.MapRightKey("Product_ProductId");
cs.ToTable("ProcessProducts");
});
Which creates a new table named ProcessProducts where many to many relationships are being stored.
My Problem now is, that when I remove e.g. Product from my database, I would like to automatically remove all rows from ProcessProducts table, where the particular ProductId has been used. I wanted to configure CascadeDelete under my modelBuilder, but it does not allow me to do so.
Maybe it's the way, how I am removing the item which is wrong?
public void Delete(TEntity entity)
{
if (entity == null) throw new ArgumentNullException("entity");
_context.Set<TEntity>().Attach(entity);
_context.Set<TEntity>().Remove(entity);
_context.SaveChanges();
}
Any help in regards to this matter would be highly appreciated.
Do delete in cascade mode you can use SQL or EF.
Each case need to be configured how deep the cascade will go. So the question is: How deep you want to go:
If you delete a Product should delete de Process(es) connected with it and if this Process(es) has other Product(s) should cascade keep this chain of deletion?
Perhaps by cascade delete, you only meant delete the connection (many-to-many relationship) between Product and Process. If this is the case you only need to do:
public void DeleteProduct(Product entity)
{
if (entity == null) throw new ArgumentNullException("entity");
entity = _context.Set<Product>().First(f => f.Id == entity.Id);
_context.Set<Product>().Remove(entity);
_context.SaveChanges();
}
This should delete your Product and all ProcessProducts registries connected with your Product.
Related
What I want to do: When a user selects a product, populate a data grid with every Product. If that Product / Event combination have an associated EventProduct, fill in other pieces of the data grid with that data. If not, create a new EventProduct and default all properties to 0. On saving the event, if the EventProduct properties have changed or been populated, save that EventProduct to the DB as a new EventProduct.
My current approach:
I have three classes: Event, Product, and EventProduct as defined here (truncated).
public partial class Event
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Event()
{
EventProducts = new HashSet<EventProduct>();
}
[Key]
public int index { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<EventProduct> EventProducts { get; set; }
}
public partial class Product
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Product()
{
EventProducts = new HashSet<EventProduct>();
}
[Key]
public int index { get; set; }
[StringLength(200)]
public string name { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<EventProduct> EventProducts { get; set; }
}
public partial class EventProduct
{
public EventProduct()
{
Event = new Event();
Product = new Product();
quantity_allocated = 0;
quantity_sold = 0;
quantity_sampled = 0;
}
public int index { get; set; }
public int EventID { get; set; }
public int ProductID { get; set; }
public int? quantity_allocated { get; set; }
public int? quantity_sold { get; set; }
public decimal? quantity_sampled { get; set; }
public virtual Event Event { get; set; }
public virtual Product Product { get; set; }
}
I'm populating the table by querying and joining my Products to my EventProducts, and creating a new Associative Object which has a Product and an EventProduct in a one-to-one relationship. I'm setting my itemsource equal to the following:
public static List<ProductEventProduct> GetProductEventProduct(Event e, DatabaseModel dbContext)
{
var query = from product in dbContext.Products
join eventProduct in dbContext.EventProducts
on new { pIndex = product.index, eIndex = e.index }
equals new { pIndex = eventProduct.Product.index, eIndex = eventProduct.Event.index } into temp
from eventProduct in temp.DefaultIfEmpty()
select new ProductEventProduct
{
Product = product,
EventProduct = eventProduct
};
var dataSource = query.ToList();
foreach (ProductEventProduct entry in dataSource)
{
if (entry.EventProduct == null)
{
entry.EventProduct = new EventProduct()
{
EventID = e.index,
ProductID = entry.Product.index,
Product = entry.Product,
Event = e
};
}
}
return dataSource;
}
And when I have a single, manually input (direct into my data source) EventProduct it works as intended, and users can edit the Allocated amount (sold and sampled are locked in this view):
My problem is with saving. Right now I'm iterating through each row of the data grid, and if it's been changed or if the value is not null, create an EventProduct from that and add that EventProduct to my Database Context:
List<Associations.ProductEventProduct> entries = (List<Associations.ProductEventProduct>)EventProductDataGrid.ItemsSource;
IEnumerable<Associations.ProductEventProduct> changedEntries = entries.Where(association =>
association.EventProduct.quantity_allocated != 0 ||
association.EventProduct.quantity_sampled != 0 ||
association.EventProduct.quantity_sold != 0);
foreach (Associations.ProductEventProduct entry in changedEntries)
{
// if there are no event products in the database that have the same product and event, it's new so save it to DB
if (!(dbContext.EventProducts.Any(ep =>
ep.EventID == entry.EventProduct.EventID && ep.ProductID == entry.Product.index)))
{
dbContext.EventProducts.Add(entry.EventProduct); // line where I get the error described below
dbContext.SaveChanges();
}
else // if it is an EventProduct which exists in the database already
{
EventProduct modifyEvent = dbContext.EventProducts.Single(ep => ep.Event.index == entry.EventProduct.Event.index && ep.Product.index == entry.Product.index);
modifyEvent.quantity_allocated = entry.EventProduct.quantity_allocated;
modifyEvent.quantity_sampled = entry.EventProduct.quantity_sampled;
modifyEvent.quantity_sold = entry.EventProduct.quantity_sold;
}
}
dbcontext.SaveChanges();
But when adding my EventProduct to my DBContext, I get the error, "'A referential integrity constraint violation occurred: A primary key property that is a part of referential integrity constraint cannot be changed when the dependent object is Unchanged unless it is being set to the association's principal object. The principal object must be tracked and not marked for deletion.'". Which doesn't make sense to me, since both its references to Product and Event are populated, valid, and correct in my debugger.
I've been stuck on various pieces of this issue for days now and I know that my approach is wrong, any advice would be enormously appreciated.
I imagine your problem is that the EventProduct you are adding to your DbContext refers to an Event or Product (or both) that already exist in the database but are not currently being tracked by the DbContext. When calling dbContext.EventProducts.Add(entry.EventProduct); it has the effect that it's trying to add the entry.EventProduct.Event and entry.EventProduct.Product in the DbContext as if they are new entities.
If you know that entry.EventProduct.Event and entry.EventProduct.Product already exists in the database, then you can add them to the change tracker letting EF know that they already exist and haven't changed:
// Let EF know the entities already exist
dbContext.Set<Event>().Attach(entry.EventProduct.Event);
dbContext.Set<Product>().Attach(entry.EventProduct.Product);
// Now add the EventProduct letting it refer to the existing Event and Product
dbContext.EventProducts.Add(entry.EventProduct);
dbContext.SaveChanges();
Note: as per the documentation the entities you attach will be given the state Unchanged which means if you do have changes to the Event or Product that you want to update in the database you should instead use DbContext.Entry() and set the returned Entry.State to Modified.
I have two entities Users and Roles
public partial class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string SecondName { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
public partial class Role
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public virtual ICollection<User> Users { get; set; }
}
And i have table(RoleUser) in Database contain only userid and role id.
I want to modify the user ,Delete the rows that exist in RoleUser Table and insert new record.
When i used this following code it delete the rows in RoleUser Table and also the roles itself
public void Update(User usr)
{
var existingParent = _context.Users
.Where(p => p.Id == usr.Id)
.Include(p => p.Roles)
.SingleOrDefault();
if (existingParent != null)
{
// Update parent
_context.Entry(existingParent).CurrentValues.SetValues(usr);
// Delete children
foreach (var existingChild in existingParent.Roles.ToList())
{
if (!usr.Roles.Any(c => c.Id == existingChild.Id))
_context.Roles.Remove(existingChild);
}
}
}
The question is how to delete the records that exist in RoleUser Table and insert new records without deleting the entities itself??
This is code for remove
_context.Users.find(userId).Roles.Remove(_context.Roles.find(roleId));
_context.SaveChange();
And this is code for add
_context.Users.find(userId).Roles.Add(_context.Roles.find(roleId));
_context.SaveChange();
It will save userid and roleid to table many-to-many
Hope help you
You need to do this to remove a record only in Many to Many table. Let me know if it works for you.
IObjectContextAdapter contextAdapter = (IObjectContextAdapter)_context;
ObjectStateManager stateManager = contextAdapter.ObjectContext.ObjectStateManager;
stateManager.ChangeRelationshipState(existingParent, existingChild, "Roles", EntityState.Deleted);
_context.SaveChanges();
To add new values:
_context.Entry(existingParent).Collection("Roles").CurrentValue = values;
Where values is your list of data to add (should be IEnumerable or ICollection, so List<Role> is ok).
values must contains object linked to database.
foreach (Role entry in newValues) {
values.Add(_context.Set(typeof(Role)).Find(entry.Id));
}
I have your basic recursive categories that are linked between each other. When I try to delete a category that has children I get your usual error.
What I always did is made a functions to recursively delete all children but I wonder can I just somehow set CASCADE ON DELETE somehow to my POCO class that is using EF so I would not need implement my own deletion mechanics?
Error
The DELETE statement conflicted with the SAME TABLE REFERENCE
constraint "FK_dbo.Categories_dbo.Categories_RootCategoryId". The
conflict occurred in database "Website", table "dbo.Categories",
column 'RootCategoryId'.
Model
public class Category
{
public int Id { get; set; }
public int? RootCategoryId { get; set; }
public virtual Category RootCategory { get; set; }
public virtual ICollection<Category> ChildCategories { get; set; }
}
What I have now
Currently I delete relation before deleting a category. But what if I want to delete all child-categories recursively? Only cascading them will accomplish that.
public ActionResult Delete(int id)
{
var category = _db.Categories.Single(x => x.Id == id);
if (category.RootCategoryId == null)
{
category.ChildCategories.ToList().ForEach(x => x.RootCategoryId = null);
}
else
{
category.ChildCategories.ToList().ForEach(x => x.RootCategoryId = category.RootCategoryId);
}
_db.Categories.Remove(category);
_db.SaveChanges();
return RedirectToAction("Index", "Category");
}
The way I would approach this issue would be to use the OnModelCreating Fluent api.
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
modelBuilder.Entity()
.HasMany(u => u.ProjectAuthorizations)
.WithRequired(a => a.UserProfile)
.WillCascadeOnDelete(true);
}
I'm using Entity Framework 5, and a code first approach.
I have a Product class, which can have zero-or-more ProductColors. The colors are prepopulated in the database using seeding. The color table should not be populated with new items using EF as it is a static list of items that will not grow. Colors are reused in many products.
My model classes:
public class Product
{
public int ID { get; set; }
public string Title { get; set; }
public virtual ICollection<ProductColor> Colors { get; set; }
}
public class ProductColor
{
public int ID { get; set; }
public string Title { get; set; }
}
In my DbMigrationsConfiguration:
protected override void Seed(... context)
{
context.ProductColors.AddOrUpdate(
p => p.ID,
new ProductColor(1, "White"),
new ProductColor(2, "Black"),
new ProductColor(3, "Red"));
}
In my DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>().HasMany(x => x.Colors).WithMany();
}
public DbSet<Product> Products { get; set; }
My Products are created from a viewmodel object, both when they are created for the first time, and also later when they are edited:
Product product = new Product { ID = productViewModel.ID };
product.Colors = new List<ProductColor>();
foreach (int colorId in productViewModel.SelectedColorIds)
{
ProductColor productColor = productColors.Find(m => m.ID == colorId);
product.Colors.Add(productColor);
}
They are saved in the database like this when created:
db.Products.Add(product);
db.SaveChanges();
And like this when they are edited:
db.Entry(product).State = EntityState.Modified;
db.SaveChanges();
EF generates Products, ProductColor and ProductProductColor tables just fine initially. When the products are first created and saved, the colors are properly being saved in the ProductProductColor table.
But when I edit/modify the Product and Colors collection, the colors are not being updated in the database. Seems it doesn't recognize that the Colors collection has been modified. How can I make it so?
Sorry for the lengthy post, but I wanted to include all the elements in case someone needs the full picture.
I managed to find a solution. Instead of create a new Product instance (using the same ID as previously), I fetch the product from the database.
IQueryable<Product> products =
from p in db.Products.Include("Colors")
select p;
Product product = Enumerable.FirstOrDefault(products, p => p.ID == id);
When I then change the Colors collection, it seems the tracking of items is working and serialized correctly into the database.
After some reading to understand why, I see fetching the product from the database creates a Proxy object that keeps track of the collection for me. When creating a Product manually, it is not able to track changes to collections.
I have 2 classes that reference each other. It's a weird situation that our CRM needs.
I have an Organization and EmAddress tables and classes. The Organization inherits from Subscriber, which also has a table. I think this could be my problem, or the fact that I can't set Inverse on these because there is no "HasMany"...
The order of insert/update is ..
INSERT Email
INSERT Org
UPDATE Email to set Email.Subscriber
Email.Subscriber needs to be "NOT NULL", so this doesn't work. How can I change the order, I can't use Inverse because there is no list. Just 2 references.
public class Organization : Subscriber
{
public override string Class { get { return "Organization"; } }
EmAddress PrimaryEmailAddress {get;set;}
}
public class OrganizationMap : SubclassMap<Organization>
{
public OrganizationMap()
{
Table("Organization");
KeyColumn("Organization");
References(x => x.PrimaryEmail,"PrimaryEmailAddress").Cascade.SaveUpdate();
}
}
public EmAddressMap()
{
Id(x => x.Id, "EmAddress");
Map(x => x.EmailAddress, "eMailAddress");
References<Subscriber>(x => x.Subscriber,"Subscriber").LazyLoad().Fetch.Select().Not.Nullable();
/*not.nullable() throw s error. NHibernate INSERTS email, INSERTS org, UPDATES email. */
}
public class EmAddress
{
public virtual Guid Id { get; set; }
public virtual string EmailAddress { get; set; }
public virtual Subscriber Subscriber { get; set; }
}
//Implementation
var session = NHIbernateHelper.GetSession();
using(var tx = session.BeginTransaction())
{
var org = new Organization();
org.PrimaryEmail = new EmAddress(){Subscriber = org};
session.Save(org);
tx.commit();
}
This post might help:
http://ayende.com/blog/3960/nhibernate-mapping-one-to-one
Have only one side use many-to-one (Fluent: "References") and the other side uses one-to-one (Fluent: "HasOne").