How to remove category without sub category ?
Category Model :
public class Category
{
public virtual int Id{ get; set; }
public virtual string Name { get; set; }
public virtual Category Parent { get; set; }
public virtual int? ParentId { get; set; }
}
datas :
Id ParentId Name
1 null Hot
2 1 Soup
3 1 Coffee
4 3 Decaf Coffee
5 null Cold
6 5 Iced Tea
i need to remove Category with Id=1 but The following error occurs :
The DELETE statement conflicted with the SAME TABLE REFERENCE
constraint "FK_dbo.Categories_dbo.Categories_ParentId". The conflict
occurred in database "ProjectDatabase", table "dbo.Categories", column
'ParentId'.
The statement has been terminated.
my delete code :
public void Delete(int categoryId)
{
var category = _categories.First(d => d.Id == categoryId);
_categories.Remove(category);
}
CategoryConfig :
public class CategoryConfig : EntityTypeConfiguration<Category>
{
public CategoryConfig()
{
ToTable("Categories");
HasOptional(x => x.Parent)
.WithMany()
.HasForeignKey(x => x.ParentId)
.WillCascadeOnDelete(false);
}
}
Well, according to the documentation, if a foreign key on the dependent entity is nullable, Code First does not set cascade delete on the relationship (which you are doing explicitly), and when the principal is deleted the foreign key will be set to null.
I don't know why is not doing that in your case, probably is because you are working with an unidirectional relationship and you don't have the collection of children in your parent Category, so EF couldn't set the FK properties to null in the subcategories, but you can try the following:
var category = _categories.First(d => d.Id == categoryId);
var children=_categories.Where(d=>d.ParentId==categoryId);
foreach(var c in children)
c.ParentId=null;
_categories.Remove(category);
You can't delete the category with Id=1 because there are subcategories referenced to it (Soup and Coffee). In your delete method you'll have to either change the ParentId of these items first to another existing element (or null) or delete these items before deleting the category with Id=1.
Related
Suppose I have two classes that have a many-to-one relationship
public class ParentObject{
public int Id { get; set; }
public List<ChildObject> Children { get; set; }
...
}
public class ChildObject{
public int Id { get; set; }
public ParentObject Parent { get; set; }
}
When I add the migration and update the database, the result is two tables:
ParentObject
Id : int
...
ChildObject
Id : int
ParentObjectId : int
...
Now I want to search for all of the children of a particular parent. In SQL, I can do this without having to join in the parent table simply by querying on the ChildObject.ParentObjectId column.
SELECT * FROM [ChildObject] c WHERE c.ParentObjectId = parentId
But in EF Core, the only way I've found to do this is to use .Include(c => c.Parent) and then .FirstOrDefault(c => c.Parent.Id == parentId). This creates a join to the ParentObject table.
Is there a way to query the ChildObject table without the .Include()?
Simply use the navigation property to access the parent PK:
var children = db.Children
.Where(c => c.Parent.Id == parentId)
.ToList();
If you include some other Parent entity property in Where, OrderBy, Select (or the Parent itself inside Select or Include), of course EF Core will create a join. But if you access only the PK of the related entity, then EF Core is smart enough the use the FK instead and avoid the join.
Please note that the returned Child objects won't have Parent property populated (will be null) except the context is not already tracking a Parent entity instance with that PK.
I have two classes with a many-to-many relationship in a ASP.NET EF application. I'm trying to find all Listings that have any Categories which is posted from a view. The categories are checkboxes on the view form.
These are the classes with navigation properties simplified for example:
public class Listing
{
public int ID { get; set; }
public ICollection<Category> Categories { get; set; }
...
}
public class Category
{
public int ID { get; set; }
public ICollection<Listing> Listings { get; set; }
...
}
// this is the join table created by EF code first for reference
public class CategoryListings
{
public int Category_ID { get; set; }
public int Listing_ID { get; set; }
}
This is the query I am trying to use in my MVC Controller but it doesn't work and I don't really know what else to try:
if (model.Categories !=null && model.Categories.Any(d => d.Enabled))
{
List<Listing> itemsSelected = null;
foreach (var category in model.Categories.Where(d => d.Enabled))
{
var itemsTemp = items.Select(x => x.Categories.Where(d => d.ID == category.ID));
foreach (var item1 in itemsTemp)
{
itemsSelected.Add((Listing)item1); //casting error here
}
}
items = itemsSelected;
}
In SQL, I would write this using a subquery (the subquery represents the multiple categories that can be searched for):
select l.id, cl.Category_ID
from
listings as l inner join CategoryListings as cl
on l.id=cl.Listing_ID
inner join Categories as c on c.ID = cl.Category_ID
where c.id in (select id from Categories where id =1 or id=3)
How do I write that SQL query in EF using navigators or lambda? The subquery in the SQL will change each search and can be any id or IDs.
You forgot to tell us what objects are in your collection items. I think they are Listings. Your case doesn't work, because itemsTemp is a collection of Categories, and every item1 is a Category, which of course can't be cast to a Listing.
Advice: to debug casting problems, replace the word var
with the type you actually expect. The compiler will warn you about
incorrect types. Also use proper identifiers in your lambda expressions.
This makes them easier to read
IQueryable<???> items = ... // collection of Listings?
List<Listing> itemsSelected = null;
IQueryable<Category> enabledCategories = model.Categories.Where(category => category.Enabled));
foreach (Category category in enabledCategories)
{
IEnumerable<Category> itemsTemp = items
.Select(item => item.Categories
.Where(tmpCategory => tmpCategory.ID == category.ID));
foreach (Category item1 in itemsTemp)
{
// can't cast a Category to a Listing
We'll come back to this code later.
If I look at your SQL it seems that you want the following:
I have a DbContext with (at least) Listings and Categories.
I want all Listings with their Categories that have category Id 1 or 3
It's good to see that you followed the entity framework code-first conventions, however you forgot to declare your collections virtual:
In entity framework the columns in a table are represented by
non-virtual properties. The virtual properties represent the relations
between the table.
With a slight change your many-to-many relation can be detected automatically by entity framework. Note the virtual before the ICollection
class Listing
{
public int ID { get; set; }
// every Listing has zero or more categories (many-to-many)
public virtual ICollection<Category> Categories { get; set; }
...
}
class Category
{
public int ID { get; set; }
// every Category is used by zero or more Listings (many-to-many)
public ICollection<Listing> Listings { get; set; }
...
public bool Enabled {get; set;}
}
And the DbContext
public MyDbContext : DbContext
{
public DbSet<Listing> Listings {get; set;}
public DbSet<Category> Categories {get; set;}
}
Although a relational database implements a many-to-many relationship with a junction table, you don't need to declare it in your DbContext. Entity framework detects that you want to design a many-to-many and creates the junction table for you.
But how can I perform my joins without access to the junction table?
Answer: Don't do joins, use the ICollections!
Entity Framework knows which inner joins are needed and will do the joins for you.
Back to your SQL code:
Give me all (or some) properties of all Listings that have at least one Category with Id equal to 1 or 3
var result = myDbcontext.Listings
.Select(listing => new
{ // select only the properties you plan to use
Id = listing.Id,
Name = listing.Name,
...
Categories = listing.Categories
// you don't want all categories, you only want categories with id 1 or 3
.Where(category => category.Id == 1 || category.Id == 3)
.Select(category => new
{
// again select only the properties you plan to use
Id = category.Id,
Enabled = category.Enabled,
...
})
.ToList(),
})
// this will also give you the Listings without such Categories,
// you only want Listings that have any Categories left
.Where(listing => listing.Categories.Any());
One of the slower parts of database queries is the transfer of the selected data from the DBMS to your local process. Hence it is wise to only transfer the properties you actually plan to use. For example, you won't need the foreign keys of one-to-many relationships, you know it equals the Id value of the one part in the one-to-many.
Back to your code
It seems to me, that your items are Listings. In that case your code wants all Listings that have at least one enabled Category
var result = myDbContext.Listings
.Where(listing => ...) // only if you don't want all listings
.Select(listing => new
{
Id = listing.Id,
Name = list.Name,
Categories = listing.Categories
.Where(category => category.Enabled) // keep only the enabled categories
.Select(category => new
{
Id = category.Id,
Name = category.Name,
...
})
.ToList(),
})
// this will give you also the Listings that have only disabled categories,
// so listings that have any categories left. If you don't want them:
.Where(listing => listing.Categories.Any());
Do you have a relation between Listing/Category and CategoryListings?
Here is example for EF 6: http://www.entityframeworktutorial.net/code-first/configure-many-to-many-relationship-in-code-first.aspx
If you have it the query will be simple, something like that:
CategoryListing.Where(cl => new List<int>{1, 3}.Contains(cl.CategoryRefId))
.Select(x => new {x.ListingRefId, x.CategoryRefId});
If you need all properties of Listing or Category, Include() extension will help.
I have the following two classes:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Child> Children { get; set; }
public Parent()
{
Children = new List<Child>();
}
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
public int? ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
And then the Fluent setting in the data context:
modelBuilder.Entity<Child>()
.HasKey(c => new { c.Id, c.ParentId })
.HasOptional(c => c.Parent)
.WithMany(p => p.Children)
.WillCascadeOnDelete(true);
I'm doing this so that I actually delete the child object when I say parent.Children.Remove(aChild);, not just set its ParentId null.
The problem is, I'm getting the error "Violation of PRIMARY KEY constraint 'PK_dbo.Child'. Cannot insert duplicate key in object 'dbo.Child'. The duplicate key value is (0, 2)." when I create a fresh parent with children, and then db.SaveChanges():
Parent p = new Parent { Name = "Quarterbacks" };
p.Children.Add(new Child { Name = "Brady" });
p.Children.Add(new Child { Name = "P. Manning" });
p.Children.Add(new Child { Name = "Kaepernick" });
p.Children.Add(new Child { Name = "Wilson" });
p.Children.Add(new Child { Name = "Rodgers" });
db.Parents.Add(p);
db.SaveChanges();
I thought integer primary keys are auto-generated on insertion. What should I do? Should I change the keys to strings and create GUID keys in C# for this to work?
I believe that for composite keys no part of the key is marked as an identity by default, even not for integer keys (which would be the case for a simple, non-composite key). You probably must add the Identity option explicitly to the Child entity's Id property:
modelBuilder.Entity<Child>()
.Property(c => c.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
I'm a bit worried that your second part of the Child's key is nullable. Perhaps EF does allow that, but the database not necessarily: I think, with SQL Server for example nullable key parts are forbidden. Maybe, other databases can deal with that. You're apparently aiming for an identifying relationship (the one that deletes the child from the database when it is removed from the parent) which however needs a required (not an optional) relationship between parent and child, as far as I know.
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 have a situation where one of my table is self-mapped to itself. The primary key of one row (Parent) can be used as a foreign key to other row (Child) and this foreign key column contains null for such rows that have no parent. Something like this:
table: Settings_LocationType
++++++++++++++++++++++++++++++++++++++++++++++++
LocationID | Name | ParentLocationId
++++++++++++++++++++++++++++++++++++++++++++++++
1 Parent 1 null
2 Child 1 1
3 Child 2 1
4 Parent 2 null
Model: LocationType
public class LocationType
{
public virtual long LocationTypeId { get; set; }
public virtual string Name { get; set; }
public virtual LocationType ParentLocationType { get; set; }
public virtual IList<LocationType> LocationTypes { get; set; }
public LocationType()
{
LocationTypes = new List<LocationType>();
}
}
Mapping: LocationTypeMap
public class LocationTypeMap : ClassMap<LocationType>
{
public LocationTypeMap()
{
Table("Setting_LocationType");
Id(x => x.LocationTypeId).Column("LocationId").GeneratedBy.Sequence("location_type_seq");
Map(x => x.ShortName, "Name").Length(15).Not.Nullable();
References<LocationType>(x => x.ParentLocationType).LazyLoad().Nullable();
HasMany<LocationType>(x => x.LocationTypes).AsBag().KeyColumn("ParentLocationId").KeyNullable().LazyLoad().Inverse().Cascade.SaveUpdate();
}
}
Now I am having a problem in retrieving those rows which contain NULL (or say aren't child) in PatentLocationType field. I tried passing null like this repo.Get("ParentLocationType.LocationTypeId", null); but it didnt work but threw object reference is not set to an instance error.
Have you tried:
repo.Get ("ParentLocationType", null)
OK, I solved it using Expression.IsNull instead of Expression.Eq when querying for such LocationType