When deleting a model (aggregate root) from the repository, all associated aggregates must be deleted too.
I am struggling to implement this in my Entity Framework 6 implementation of the repository pattern
In my example, I want to delete a Customer from the CustomerRepository. All the customer's Order objects should also be deleted.
Repository (stripped down):
public interface IRepository<T> where T : DomainEntity
{
void Remove(T item);
}
public class EntityFrameworkRepository<T> : IRepository<T> where T : DomainEntity
{
private readonly DbSet<T> dbSet;
public DbContext context;
public EntityFrameworkRepository(IUnitOfWork unitOfWork)
{
context = entityFrameworkUnitOfWork.context;
dbSet = dbSet = context.Set<T>();
}
public virtual void Remove(T item)
{
DbEntityEntry dbEntityEntry = context.Entry(item);
if (dbEntityEntry.State == EntityState.Detached)
{
dbSet.Attach(item);
}
dbSet.Remove(item);
}
}
public class EntityFrameworkUnitOfWork : IUnitOfWork
{
public readonly DbContext context;
public EntityFrameworkUnitOfWork()
{
this.context = new ReleaseContext();
}
public void Commit()
{
context.SaveChanges();
}
}
ICustomerRepository and CustomerRepository (EF implementation):
public interface ICustomerRepository : IRepository<Customer>
{
IEnumerable<Customer> GetAllActive();
}
public class CustomerRepository : EntityFrameworkRepository<Customer>, ICustomerRepository
{
public CustomerRepository(IUnitOfWork unitOfWork)
: base(unitOfWork)
{ }
public override void Remove(Order item)
{
item.Orders.Clear();
base.Remove(item);
}
}
Client-code:
customerRepository.Remove(customer);
unitOfWork.Commit();
Exception thrown:
System.InvalidOperationException: The operation failed: 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.
I would except calling item.Orders.Clear() to indicate to EF that the associations must be deleted.
The error suggests your clear method hasn't removed child entities.
Are you are aware of the fluent api addition cascade on delete ?
HasRequired(t => t.Parent).WithOptional().WillCascadeOnDelete(true);
So if you delete a root object, all dependents can be removed by Db.
Although that option is not always available...
Since you are using IRepository...
did you consider using some pattern like
public int DeleteWhere(Expression<Func<TPoco, bool>> predicate) {
var delList = Context.Set<TPoco>().Where(predicate);
foreach (var poco in delList) {
SetEntityState(poco, EntityState.Deleted);
}
return delList.Count;
}
var custId = 1;
var repOrder = new Respository<Order>();
var delCnt = repOrder.DeleteWhere(t => t.CustomerId == custId);
MyContext.SaveChanges()
There is a good practice: don't delete anything :)
Mark it as "deleted" instead.
Because WHY? Show us a real business requirement to physically delete stuff?
Not only it slows things down (usually DBs lock a lot while deleting), causes fragmentation, etc., but in most of the cases it is absurd! No business would allow you do physically delete a customer and a list of orders!
Business does not delete anything. In the real business no one will go an find all the papers related to a particular customer and dispose of them in a shredder. Unless they did something illegal and FBI is knocking at the door :)
Talk to your business experts who know a little about computers (these are the real business experts). They will tell you what happens to customers when they stop being customers (perhaps they are "archived", perhaps something else, or perhaps just nothing) and then model it.
It is us, programmers, usually invent the concept of "deleting" stuff.
Besides, analysing historical information can be really helpful some time in the future!
There are only two options when physical delete can be necessary:
To save disk space (which is not a problem anymore when disk space is as cheap as dirt)
To have some legal obligation to physically delete data when customer wants it to be deleted (which is a very rare requirement and usually is met in a certain domains).
For #1, again, space is not a problem these days so implementing delete can cost more than benefit from it.
For #2 you want to be explicit anyway and you would probably manage your data storage differently. For example you may want to have a DB per client then so you can just drop the DB and all the backups to comply to the regulation (yes, you must remove backups in order to legally say that you don't hold the deleted data anymore)
So which case is yours? Why you want to delete, what are you real business requirements?
Related
I have my entity defined like this:
public class Entity : BaseModel // Has the already ID defined
{
private int? companyId;
public Company? Company { get; set; }
public int? CompanyId {
get => this.companyId == 0 ? null : this.companyId; // I tried this for debugging purposes to force this value to "null" -> made no difference
set => this.companyId = value;
}
}
public class Company : BaseModel // Has the already ID defined
{
public IEnumerable<Entity> Entities { get; set; } = new List<Entity>();
}
Anyway, if I set the CompanyId to null, my DB throws an exception with the message: "FOREIGN KEY constraint failed". If the CompanyId is set to, e.g. 123, the relationship is resolved accordingly.
I mean, it makes sense, that EF cannot find null in my DB, but how do I want to set an optional value otherwise? I am using code first annotations only, hence my OnModelCreating of my context is completely empty.
How are you loading the entities in the first place? Are you loading an Entity by ID and trying to dis-associate it from a company, or have you loaded a company with it's entities and trying to remove one association?
Normally when working with relations where you have navigation properties, you want to de-associate them (or delete them) via the navigation properties, not the FK properties. For instance if loading a company and wanting to de-associate one of the entities you should eager-load the entities then remove the desired one from the collection:
var company = _context.Companies.Include(c => c.Entitites).Single(c => c.Id == companyId);
var entityToRemove = company.Entities.SingleOrDefault(e => e.Id == entityId);
if(entityToRemove != null)
company.Entities.Remove(entityToRemove);
_context.SaveChanges();
Provided that the relationship between Company and Entity is set up properly as an optional HasMany then provided these proxies are loaded, EF should work out to set the entityToRemove's FK to null.
If you want to do it from the Entity side:
var entityToRemove = _context.Entities.Include(e => e.Company).Single(e => e.Id == entityId);
entityToRemove.Company = null;
_context.SaveChanges();
That too should de-associate the entities. If these don't work then it's possible that your mapping is set up for a required relationship, though I am pulling this from memory so I might need to fire up an example to verify. :) You also should be checking for any code that might set that CompanyId to 0 when attempting to remove one, whether that might be happening due to some mapping or deserialization. Weird behaviour like that can occur when entities are passed around in a detached state or deserialized into controller methods. (which should be avoided)
Update: Code like this can be very dangerous and lead to unexpected problems like what you are encountering:
public virtual async Task<bool> Update(TModel entity)
{
Context.Update(entity);
await Context.SaveChangesAsync();
return true;
}
Update() is typically used for detached entities, and it will automatically treat all values in the entity as Modified. If model was already an entity tracked by the Context (and the context is set up for change tracking) then it is pretty much unnecessary. However, something in the calling chain or wherever has constructed the model (i.e. Entity) has set the nullable FK to 0 instead of #null. This could have been deserialized from a Form etc. in a view and sent to a Controller as an integer value based on a default for a removed selection. Ideally entity classes should not be used for this form of data transfer from view to controller or the like, instead using a POCO view model or DTO. To correct the behaviour as your code currently is, you could try the following:
public async Task<bool> UpdateEntity(Entity entity)
{
var dbEntity = Context.Set<Entity>().Include(x => x.Customer).Single(x => x.Id == entityId);
if (!Object.ReferenceEquals(entity, dbEntity))
{ // entity is a detached representation so copy values across to dbEntity.
// TODO: copy values from entity to dbEntity
if(!entity.CustomerId.HasValue || entity.CustomerId.Value == 0)
dbEntity.Customer = null;
}
await Context.SaveChangesAsync();
return true;
}
In this case we load the entity from the DbContext. If this method was called with an entity tracked by the DbContext, the dbEntity would be the same reference as entity. In this case with change tracking the Customer/CustomerId reference should have been removed. We don't need to set entity state or call Update. SaveChanges should persist the change. If instead the entity was a detached copy deserialized, (likely the case based on that 0 value) the reference would be different. In this case, the allowed values in the modified entity should be copied across to dbEntity, then we can inspect the CustomerId in that detached entity for #null or 0, and if so, remove the Customer reference from dbEntity before saving.
The caveats here are:
This won't work as a pure Generic implementation. To update an "Entity" class we need knowledge of these relationships like Customer so this data service, repository, or what-have-you implementation needs to be concrete and non-generic. It can extend a Generic base class for common functionality but we cannot rely on a purely Generic solution. (Generic methods work where implementation is identical across supported classes.)
This also means removing that attempt at trying to handle Zero in the Entity class. It should just be:
public class Entity : BaseModel
{
public Company? Company { get; set; }
[ForeignKey("Company")]
public int? CompanyId { get; set; }
// ...
}
Marking Foreign Keys explicitly is a good practice to avoid surprises when you eventually find yourself needing to break conventions that EF accommodates in simple scenarios.
I'm using EF5 code first.
We have a method
LogHistoryTracking(DbEntityEntry entity)
to log changes when SaveChanges is called.
At SaveChanges, we get the changed entities and pass into LogHistoryTracking
var changedEntities = ChangeTracker.Entries().ToList();
But when I access
changedEntity.OriginalValues.PropertyNames
there is no properties for foreign keys object (only foreign key Id - but how can we get the data when there is only id here?).
I also tried to google for a solution, but this issue might be not so popular.
There is this article, but it does not work.
Appreciate any help. Thanks.
If you want to have your entity properties to be accessible you must 'Include' them prior to accessing them. Like in the following example which gets the orders of the first cutomer :
var orders = context.Customers
.Include("Orders")
.First().Orders;
In this example if you do not call .Include("Orders") you will not have Customer.Orders. The same goes if you have foreign key and forget to include the navigation property of the foreign key. This is because the key (the ID) is part of the object and the navigation property is not.
Let us see one real world example :
public class Employee : Entity
{
public virtual int CompanyUserId { get; set; }
public virtual CompanyUser CompanyUser { get; set; }
//... cut out for brevity
}
If you get the employees like this :
var employees = context.Employees;
You will not be able to access employees[0].CompanyUser after
context.SaveChanges() because of lazy loading. The connection is disposed after context.SaveChanges(), so no more data fetching.
But if you call :
var employees = context.Employees
.Include("CompanyUser")
.ToArray();
You will be able to access employees[0].CompanyUser.SomeProperty right away before context.SaveChanges regardless lazy loading because ToArray() will execute the query and fetch the entities with the "includes".
If you call :
var employees = context.Employees
.Include("CompanyUser");
Then you will have employee[0].CompanyUser.SomeProperty even after context.SaveChanges() with Lazy Loading because you have told EF to include "CompanyUser" property before executing the query. On execution EF will include the named property.
UPDATE
Intercepting DbContext can be done in at least two different ways.
First - override SaveChanges() or SaveChangesAsync because it is virtual:
public class MyDbContext : DbContext
{
public event Action<MyDbContext> SavingChanges = _ => { };
public override int SaveChanges()
{
this.SavingChanges(this);
return base.SaveChanges();
}
}
Second way without direct override is by hiding the DbContext inside interface like this one (this is from real project) :
public interface IUnitOfWork : IDisposable
{
void Commit();
}
Third way (somewhat different) is by intercepting the Db calls.
Fourth way exists but it depends on what IoC you use. If you use Castle Windsor you can use interceptors. I suppose that with every IoC there is its own way of intercepting this.
It's quite self-explainatory.
I have a class that contains another
Let's call them Subject and Classroom
public class Subject
{
public Classroom Class {get; set;}
}
I'm using stateless facades, wich means my DbContext is disposed right after recovering the objects and is created to store the new ones.
Shouldn't it know that Classroom isn't a new object since it's ID is already in the DB?
Using the debugger I can track to the point right before I call the SaveChanges method and Classroom.id is the one I have on the database.
What's the problem? EF adds a new Classroom with the exact properties as the previous one, but with a new PK.
What am I doing wrong here?
This is the code used for the general CRUD operations (They are in my DbContext) Both update and delete work just fine:
public void Update(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Modified;
this.SaveChanges();
}
public void Insert(DbSet MySet, object Obj)
{
MySet.Add(Obj);
this.SaveChanges();
}
public void Delete(DbSet MySet, object Obj)
{
MySet.Attach(Obj);
var Entry = this.Entry(Obj);
Entry.State = EntityState.Deleted;
this.SaveChanges();
}
Without seeing you're actual code on how you're either updating or creating your Subject entity, it's hard to tell. However, you're probably not attaching the Classroom so EF is assuming that the entity is new, when it's really not.
using (Model m = new Model())
{
m.Subject.Add(subject);
m.Classrooms.Attach(subject.Class);
m.SaveChanges();
}
Even though the PK is the same, without attaching to the Context, EF has no way of figuring out what you're intention is. Attaching the entity explicitly tells your context what you want.
Sorry if duplicated.
Is it possible or recommended for business layer to using objects instead of ids?
SELECT c
FROM Child AS c
WHERE c.parent = :parent
public List<Child> list(final Parent parent) {
// does parent must be managed?
// how can I know that?
// have parent even been persisted?
return em.createNamedQuery(...).
setParameter("parent", parent);
}
This is how I work with.
SELECT c
FROM Child AS c
WHERE c.parent.id = :parent_id
public List<Child> list(final Parent parent) {
// wait! parent.id could be null!
// it may haven't been persisted yet!
return list(parent.getId());
}
public List<Child> list(final long parentId) {
return em.createNamedQuery(...).
setParameter("parent_id", parentId);
}
UPDATED QUESTION --------------------------------------
Do any JAX-RS or JAX-WS classes which each can be injected with #EJB can be said in the same JTA?
Here come the very original problem that I always curious about.
Let's say we have two EJBs.
#Stateless
class ParentBean {
public Parent find(...) {
}
}
#Stateless
class ChildBean {
public List<Child> list(final Parent parent) {
}
public List<Child> list(final long parentId) {
}
}
What is a proper way to do with any EJB clients?
#Stateless // <<-- This is mandatory for being injected with #EJB, right?
#Path("/parents/{parent_id: \\d+}/children")
class ChildsResource {
#GET
#Path
public Response list(#PathParam("parent_id") final long parentId) {
// do i just have to stick to this approach?
final List<Child> children1 = childBean.list(parentId);
// is this parent managed?
// is it ok to pass to other EJB?
final Parent parent = parentBean.find(parentId);
// is this gonna work?
final List<Child> children2 = childBean.list(parent);
...
}
#EJB
private ParentBean parentBean;
#EJB
private ChildBean childBean;
}
Following is presented as an answer only to question "Is it possible or recommended for business layer to using objects instead of ids?", because I unfortunately do not fully understand second question "Do any JAX-RS or JAX-WS classes which each can be injected with #EJB can be said in the same JTA?".
It is possible. In most cases also recommended. Whole purpose of ORM is that we can operate to objects and their relationships and not to their presentation in database.
Id of entity (especially in the case of surrogate id) is often concept that is only interesting when we are near storage itself. When only persistence provided itself needs to access id, it makes often sense to design methods to access id as protected. When we do so, less noise is published to the users of entity.
There is also valid exceptions as usual. It can be for example found that moving whole entity over the wire is too resource consuming and having list of ids instead of list of entities is preferable. Such a design decision should not be done before problem actually exists.
If parent has not been persisted yet, then the query won't work, and executing it doesn't make much sense. It's your responsibility to avoid executing it if the parent hasn't been persisted. But I would not make it a responsibility of the find method itself. Just make it clear in the documentation of the method that the parent passed as argument must have an ID, or at least be persistent. No need to make the sameverification as the entity manager.
If it has been persisted, but the flush hasn't happened yet, the entity manager must flush before executing the query, precisely to make the query find the children of the new parent.
At least with Hibernate, you may execute the query with a detached parent. If the ID is there, the query will use it and execute the query.
I'm wondering why there is no Detach method on the DbContext object like there is for ObjectContext. I can only assume this omission was intentional, but I have a hard time figuring out why. I need to be able to detach and re-attach entities (for putting in the cache in an ASP.NET project, for example). However, since I can't detach an entity, when I try to attach an entity that was associated with a previous context, I get the "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" exception.
What's the guidance here? Am I missing something?
For people that might stumble upon this question, as of CTP5 you now need to write
((IObjectContextAdapter)context).ObjectContext
in order to get to ObjectContext.
DbContext uses an ObjectContext internally and EF team make this available as a protected property just in case you ever need to drop down to the lower level API and sounds like this is the case here, so you can use or expose the required functionality from a derived DbContext:
public class YourContext : DbContext
{
public void Detach(object entity)
{
ObjectContext.Detach(entity);
}
}
Then you can call this method from your controller to detach an entity.
Alternatively, you can change it to even have a richer API:
public class YourContext : DbContext
{
public void ChangeObjectState(object entity, EntityState entityState)
{
ObjectContext.ObjectStateManager.ChangeObjectState(entity, entityState);
}
}
Here is how DbContext looks like from metadata:
public class DbContext : IDisposable
{
protected System.Data.Objects.ObjectContext ObjectContext { get; }
...
}
EF:CF 4.1 RC1 and EF:CF 4.1 RTW have the same explicitly implemented IObjectContextAdapter:
public static class DbContextExtensions
{
public static void Detach(this System.Data.Entity.DbContext context, object entity)
{
((System.Data.Entity.Infrastructure.IObjectContextAdapter)context).ObjectContext.Detach(entity);
}
}
Microsoft decided "Detach is too advanced technology and should be hidden". IMHO the man who invented this should be shot - because if you add brand new entity, it is otherwise difficult to just remove it without commiting changes to db (you can manipulate with DbEntityEntry but that's another story).
Edit 4 years later:
With EF6 (i somehow skipped EF5 :) ) you dont need detach() anymore, becouse removing freshly added entry does not generate delete from [table] where [Id] = 0 as in EF4 - you can just call mySet.Remove(myFreshlyCreatedAndAddedEntity) and everything will be allright.
I usually extend the base class(inherits from the DbContext) with the property:
public class MyDbContext : DbContext
{
public ObjectContext ThisObjectContext
{
get
{
return ((IObjectContextAdapter)this).ObjectContext;
}
}
}
later you can use this property for variety of useful stuff ... like Detach :)