I have a many-to-many relationship between Entity A and Entity B. Entity Framework has automatically created a junction table in SQL Server after running the migration. (I don't have this junction table defined anywhere in the code.) For example:
class EntityA
{
// ...
public ICollection<EntityB> Foo { get; set; }
}
class EntityB
{
// ...
public ICollection<EntityA> Bar { get; set; }
}
I need to replace the Foo collection on EntityA using a (detached) list coming in from a client application. I've spent the better part of a day trying to figure this out. Here is what I've tried:
[HttpPut]
public async Task<IActionResult> Update(EntityA someEntity)
{
var entry = context.EntityA.Attach(someEntity);
entry.State = EntityState.Modified;
var collection = entry.Collection(x => x.Foo);
collection.IsModified = true;
await context.SaveChangesAsync();
}
I've also tried changing the CurrentValue property of collection, and obviously I've also tried replacing Foo directly, but nothing seems to work -- the junction table remains empty. How can this child list be entirely replaced without having to Include() / load the entire list into memory for manual tracking / removal?
Ivan (in the comments above) is right. After some trial and error, I ended up writing an extension method that works for my case. Before I get to that, I want to credit this answer for pointing me in the right direction, which I ended up modifying to get it working with auto-generated EF junction tables. First, the extension method:
// assuming your models inherit from a base class or implement an interface
public interface IEntity
{
Guid Id { get; set; } // or int or whatever your ID field is
}
public static class DbExtensions
{
// Updates the many-to-many child collections of an entity (for an auto-generated EF junction table)
public static async Task UpdateJunctionTableAsync<T, Y>(this DbContext baseContext, T entity, Expression<Func<T, IEnumerable<Y>>> property)
where T : class, IEntity
where Y : class, IEntity
{
// scope these calls to a new context -- working off the base context
// tends to cause issues down the line with the change tracking
using var context = new DbContext();
// EF internally compares with DB entities, so we'll do the same
var dbEntity = await context.FindAsync<T>(entity.Id);
var dbEntry = context.Entry(dbEntity);
// access the collection entry that resulted in a junction table
var dbItemsEntry = dbEntry.Collection(property);
// get its associated CLR collection accessor
var accessor = dbItemsEntry.Metadata.GetCollectionAccessor();
// load the entry's items
await dbItemsEntry.LoadAsync();
// build a dictionary to track what needs to be added vs removed
var dbItemsMap = dbItemsEntry.CurrentValue.ToDictionary(e => e.Id);
// get the current items in the entity (not DB)
var items = (IEnumerable<Y>)accessor.GetOrCreate(entity, false);
// add them to the DB as needed
foreach (var item in items)
{
// if this already exists, no need to process it.
if (dbItemsMap.ContainsKey(item.Id))
dbItemsMap.Remove(item.Id);
else
{
// otherwise, add a tracked version of it.
context.Set<Y>().Attach(item);
accessor.Add(dbEntity, item, false);
}
}
// anything still left here has been deleted from the entity
foreach (var oldItem in dbItemsMap.Values)
accessor.Remove(dbEntity, oldItem);
// we have to clear the junction table from the incoming model's collection,
// otherwise EF will try to attach to it again, which will cause errors
// further down the line
var memberSelectorExpression = property.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var propertyInfo = memberSelectorExpression.Member as PropertyInfo;
if (propertyInfo != null)
propertyInfo.SetValue(entity, null, null);
}
await context.SaveChangesAsync();
}
}
Using this is simple:
[HttpPut]
public async Task<IActionResult> UpdateFoo(EntityA model)
{
// update the junction table first
await context.UpdateJunctionTableAsync(model, x => x.Foo);
// then update whatever else you want
// e.g., if we were updating the whole row:
// context.EntityA.Attach(model).State = EntityState.Modified;
// save
await context.SaveChangesAsync();
return Ok();
}
Related
I'm building an Asp.Net core Api project.
I have a Parent entity as Aggregate root. That parent entity has a collection of Owned types.
If instead of collection of Owned types I have collection of Entities, I would simply tell the parent to delete the entity by it's Id like this:
// Parent entity method
public void DeleteChild(int childId)
{
var existing = this._children.FirstOrDefault(x=>x.Id == childId);
...
this._children.Remove(existing);
}
How to delete the owned object in the collection of an aggregate (owned entity does not have an Id property)?
//Parent entity method
public void DeleteChild(?????????) //Don't know what to put here, a surrogate key?
{
...
}
The model is something like this:
public class Parent
{
public int Id {get;set;}
public string Name {get;set;}
public List<Child> Children {get;set;}
... //other non important props
}
public class Child
{
public string Name {get;set;}
... //other non important props
}
And the mapping:
public class ParentEntityConfiguration : IEntityTypeConfiguration<Parent>
{
public void Configure(EntityTypeBuilder<Device> builder)
{
builder.ToTable("Parents");
builder.HasKey(x => x.Id);
builder.OwnsMany<Child>("Children", cfg =>
{
cfg.ToTable("Children");
cfg.WithOwner().HasForeignKey("ParentId");
cfg.HasKey("ParentId", "Name");
cfg.HasIndex("ParentId", "Name").IsUnique();
});
}
}
So there is no "Id" propety in the Child nor the "ParentId" as they are shadowed.
The same goes for modifying the Owned object.
What is the best practice for this?
Thanks
P.S. I'm using EF Core 6.x
Edit: Given the added details in the question.
You are using a composite key for your child which is a combination between the child's name and the parentid. Since child is a owned type it will be automatically included when you retrieve the object of the parent. (If you didn't change the configuration AutoInclude)
So if you get your parent object like:
var parent = dbcontext.Parent.Where(p => p.id = idParent);
//find the one you want to eliminate by name.
var child = parent.children.First(item => item.name == "foo").value;
//remove the child from the list
parent.children.Remove(child);
//save changes
dbcontext.SaveChangesAsync();
================================================================
OLD:
You just have to find the parent first. Then you can iterate through the childrens and delete the ones that you want. Then just call the save changes and they should be removed.
Example:
// Parent entity method
public void DeleteChild(int childId, idParent)
{
var parent= dbcontext.Parent.FirstOrDefault(x=>x.Id == idparent);
for (int i = 0; i < parent.Children.length; i++)
{
if (collection[i].id = childId)
collection[i] = null;
}
dbcontext.SaveChangesAsync();
}
You need to reference the value-object by itself:
public void DeleteChild(Child child)
{
// redacted
}
Is it possible to set a primary key value of your own choice ?
I'm working with data from an API and i'd like the objects to have the same id in my database as they have originally.
For example, i have an object with these attributes:
_context = new ApplicationDbContext();
Object
{
id = 1234,
Name = "Pitbull",
Owner = "Greg"
};
_context.saveChanges(Object);
id is the PK for object in the database. But, if i save this the id is discarded and the database creates it's own value.
Thanks for reading ! :)
Yes there is.
The proper way to do this is to override your three DbContext.SaveChanges methods. In these methods you'll give the primary keys of all added objects an Id.
In the example below this is done in method GenerateIds:
public override int SaveChanges()
{
GenerateIds();
return base.SaveChanges();
}
public override Task<int> SaveChangesAsync()
{
GenerateIds();
return await base.SaveChangesAsync();
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken)
{
GenerateIds();
return await base.SaveChangesAsync(cancellationToken);
}
private void GenerateIds()
{
var addedEntries = this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added)
foreach (var addedEntry in addedEntries)
{
((IId)addedEntry.Entity).Id = this.CreateId();
}
}
CreateId should create a unique Id.
One of the Id generators I use often is nuget package IDgen (by RobIII). It is simple to install and to use. It generates unique System.Int64 identifiers, which have the advantage of being much smaller than a GUID. It works even if you have generators for the same database on multiple servers. The method it uses is the method twitter uses to generate ids for all its servers
Code is a one liner. In your DbContext:
private static IdGen.IdGenerator idGenerator = new IdGen.IdGenerator(0);
private long CreatedId()
{
return idGenerator.CreateId();
}
I am using the repository pattern to provide access to and saving of my aggregates.
The problem is the updating of aggregates which consist of a relationship of entities.
For example, take the Order and OrderItem relationship. The aggregate root is Order which manages its own OrderItem collection. An OrderRepository would thus be responsible for updating the whole aggregate (there would be no OrderItemRepository).
Data persistence is handled using Entity Framework 6.
Update repository method (DbContext.SaveChanges() occurs elsewhere):
public void Update(TDataEntity item)
{
var entry = context.Entry<TDataEntity>(item);
if (entry.State == EntityState.Detached)
{
var set = context.Set<TDataEntity>();
TDataEntity attachedEntity = set.Local.SingleOrDefault(e => e.Id.Equals(item.Id));
if (attachedEntity != null)
{
// If the identity is already attached, rather set the state values
var attachedEntry = context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(item);
}
else
{
entry.State = EntityState.Modified;
}
}
}
In my above example, only the Order entity will be updated, not its associated OrderItem collection.
Would I have to attach all the OrderItem entities? How could I do this generically?
Julie Lerman gives a nice way to deal with how to update an entire aggregate in her book Programming Entity Framework: DbContext.
As she writes:
When a disconnected entity graph arrives on the server side, the
server will not know the state of the entities. You need to provide a
way for the state to be discovered so that the context can be made
aware of each entity’s state.
This technique is called painting the state.
There are mainly two ways to do that:
Iterate through the graph using your knowledge of the model and set the state for each entity
Build a generic approach to track state
The second option is really nice and consists in creating an interface that every entity in your model will implement. Julie uses an IObjectWithState interface that tells the current state of the entity:
public interface IObjectWithState
{
State State { get; set; }
}
public enum State
{
Added,
Unchanged,
Modified,
Deleted
}
First thing you have to do is to automatically set the state to Unchanged for every entity retrieved from the DB, by adding a constructor in your Context class that hooks up an event:
public YourContext()
{
((IObjectContextAdapter)this).ObjectContext
.ObjectMaterialized += (sender, args) =>
{
var entity = args.Entity as IObjectWithState;
if (entity != null)
{
entity.State = State.Unchanged;
}
};
}
Then change your Order and OrderItem classes to implement the IObjectWithState interface and call this ApplyChanges method accepting the root entity as parameter:
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new YourContext())
{
context.Set<TEntity>().Add(root);
CheckForEntitiesWithoutStateInterface(context);
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
private static void CheckForEntitiesWithoutStateInterface(YourContext context)
{
var entitiesWithoutState =
from e in context.ChangeTracker.Entries()
where !(e.Entity is IObjectWithState)
select e;
if (entitiesWithoutState.Any())
{
throw new NotSupportedException("All entities must implement IObjectWithState");
}
}
Last but not least, do not forget to set the right state of your graph entities before calling ApplyChanges ;-) (You could even mix Modified and Deleted states within the same graph.)
Julie proposes to go even further in her book:
you may find yourself wanting to be more granular with the way
modified properties are tracked. Rather than marking the entire entity
as modified, you might want only the properties that have actually
changed to be marked as modified.
In addition to marking an entity as modified, the client is also
responsible for recording which properties have been modified. One way
to do this would be to add a list of modified property names to the
state tracking interface.
But as my answer is already too long, go read her book if you want to know more ;-)
My opinionated (DDD specific) answer would be:
Cut off the EF entities at the data layer.
Ensure your data layer only returns domain entities (not EF entities).
Forget about the lazy-loading and IQueryable() goodness (read: nightmare) of EF.
Consider using a document database.
Don't use generic repositories.
The only way I've found to do what you ask in EF is to first delete or deactivate all order items in the database that are a child of the order, then add or reactivate all order items in the database that are now part of your newly updated order.
So you have done well on update method for your aggregate root, look at this domain model:
public class ProductCategory : EntityBase<Guid>
{
public virtual string Name { get; set; }
}
public class Product : EntityBase<Guid>, IAggregateRoot
{
private readonly IList<ProductCategory> _productCategories = new List<ProductCategory>();
public void AddProductCategory(ProductCategory productCategory)
{
_productCategories.Add(productCategory);
}
}
it was just a product which has a product category, I've just created the ProductRepository as my aggregateroot is product(not product category) but I want to add the product category when I create or update the product in service layer:
public CreateProductResponse CreateProduct(CreateProductRequest request)
{
var response = new CreateProductResponse();
try
{
var productModel = request.ProductViewModel.ConvertToProductModel();
Product product=new Product();
product.AddProductCategory(productModel.ProductCategory);
_productRepository.Add(productModel);
_unitOfWork.Commit();
}
catch (Exception exception)
{
response.Success = false;
}
return response;
}
I just wanted to show you how to create domain methods for entities in domain and use it in service or application layer. as you can see the code below adds the ProductCategory category via productRepository in database:
product.AddProductCategory(productModel.ProductCategory);
now for updating the same entity you can ask for ProductRepository and fetch the entity and make changes on it.
note that for retrieving entity and value object of and aggregate separately you can write query service or readOnlyRepository:
public class BlogTagReadOnlyRepository : ReadOnlyRepository<BlogTag, string>, IBlogTagReadOnlyRepository
{
public IEnumerable<BlogTag> GetAllBlogTagsQuery(string tagName)
{
throw new NotImplementedException();
}
}
hope it helps
Here's the situation in its most simplified form using the EF5 Code-First approach:
public abstract class EntityBase<PK>
{
public PK ID { get; set; }
}
public class Country : EntityBase<string>
{
public string Name { get; set; }
}
public class Address : EntityBase<int>
{
[Required]
public string CountryID { get; set; }
public Country Country { get; set; }
// ... other address properties ...
}
The one-to-many relationship between Address and Country is set up with no cascade-delete like so:
modelBuilder.Entity<Address>()
.HasRequired(a => a.Country)
.WithMany()
.HasForeignKey(a => a.CountryID)
.WillCascadeOnDelete(false);
Finally, I have a generic base repository class with CRUD methods that call SaveChanges on the underlying DbContext to commit data changes atomically. E.g.:
public class EFRepository<T, PK> : IRepository<T, PK> where T : EntityBase<PK>
{
//
// ... other methods ...
//
public virtual void Delete(T instance)
{
// ... trigger validations, write to log, etc...
_dbContext.Set<T>().Remove(instance);
try
{
_dbContext.SaveChanges();
}
catch(Exception ex)
{
// ... handle the error ...
}
}
}
Part 1:
Scenario:
var countryRepo = new EFRepository<Country>();
var country = countryRepo.Save(new Country() { ID="??", Name="Test Country" });
var addressRepo = new EFRepository<Address>();
var address = addressRepo.Save(new Address() { Country=country });
countryRepo.Delete(country);
This should fail due to the existence of a dependent Address. However, afterwards the address ends up with a null in CountryID, which is invalid because Address.CountryID is required, so subsequent SaveChanges calls throw a validation exception unless the address is detached.
I expected that when an object is deleted, EF5 will be smart enough to first check for any cascade-delete constraints like the one above and, failing to find any, then proceed to delete the data. But exactly the opposite seems to be the case.
Is this a normal behaviour or am I doing something wrong?
Part 2:
Following a failed SaveChanges call, some Addresses are now in an invalid state in my DbContext and need to be restored to their original values. Of course, I can always do so explicitly for each entity type (Country, State, Order, etc.) by creating specialized repository classes and overriding Delete, but it smells big time. I'd much rather write some general purpose code to gracefully recover related entities after a failed SaveChanges call.
It would require interrogating DbContext to get all relationships in which an entity (e.g. Country) is the principal, regardless of whether or not its class defines navigational properties to dependent entities.
E.g. Country has no Addresses property, so I need to somehow find in DbContext the definition of the one-to-many relationship between Country and Address and use it to restore all related Addresses to their original values.
Is this possible?
Answering my own question in Part 2:
Here is my approach to checking for related dependents when deleting an entity on the principal end of a many-to-one relationship and where dependents are NOT exposed as a navigation collection in the principal (e.g. class Address has a Country property, but class Country doesn't have an Addresses collection).
DbContext
Add the following method to the context class:
/// <summary>
/// Returns an array of entities tracked by the
/// context that satisfy the filter criteria.
/// </summary>
public DbEntityEntry[] GetTrackedEntities<T>(
Expression<Func<DbEntityEntry<T>, bool>> filterCriteria)
where T : class
{
var result = new List<DbEntityEntry>();
var doesItMatch = filterCriteria.Compile();
foreach (var entry in this.ChangeTracker.Entries<T>())
{
if (doesItMatch(entry))
result.Add(entry);
}
return result.ToArray();
}
Repositories
Create a repository for each class that has some dependencies, override the Delete method and use the new GetTrackedEntities<T> method to get all related dependents and either:
explicitly delete them if they are cascade-deletable in code
detach them from the context if they are cascade-deletable in the DB itself
throw an exception if they are NOT cascade-deletable.
Example of the latter case:
public class EFCountryRepository :
EFReadWriteRepository<Country, string>,
ICountryRepository
{
public override void Delete(Country instance)
{
// Allow the Country to be deleted only if there are no dependent entities
// currently in the context that are NOT cascade-deletable.
if (
// are there any Regions in the context that belong to this Country?
_dbContext.GetTrackedEntities<Region>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
||
// are there any Addresses in the context that belong to this Country?
_dbContext.GetTrackedEntities<Address>(e =>
e.Entity.CountryID == instance.ID ||
e.Entity.Country == instance).Length > 0
)
throw new Exception(String.Format(
"Country '{0}' is in use and cannot be deleted.", instance.ID));
base.Delete(instance);
}
// ... other methods ...
}
Example of a case where cascade-deleting will be done by the DB itself, so all we need to do is detach the dependents from the context:
public class EFOrderRepository :
EFReadWriteRepository<Order, string>,
IOrderRepository
{
public override void Delete(Order instance)
{
foreach (var orderItem in _dbContext.GetTrackedEntities<OrderItem>(e =>
e.Entity.OrderID == instance.ID ||
e.Entity.Order == instance))
{
_dbContext.Entry(orderItem).State = System.Data.EntityState.Detached;
}
base.Delete(instance);
}
// ... other methods ...
}
Hope someone will find this solution helpful.
I'm using Entity Framework 5.0 with DbContext and POCO entities. There's a simple entity containing 3 properties:
public class Record
{
public int Id { get; set; }
public string Title { get; set; }
public bool IsActive { get; set; }
}
The Title field is always unmodified, and the UI simply displays it without providing any input box to modify it. That's why the Title field is set to null when the form is sent to the server.
Here's how I tell EF to perform partial update of the entity (IsActive field only):
public class EFRepository<TEntity>
{
...
public void PartialUpdate(TEntity entity, params Expression<Func<TEntity, object>>[] propsToUpdate)
{
dbSet.Attach(entity);
var entry = _dbContext.Entry(entity);
foreach(var prop in propsToUpdate)
contextEntry.Property(prop).IsModified = true;
}
}
and the call:
repository.PartialUpdate(updatedRecord, r => r.IsActive);
Calling SaveChanges method, I get the DbEntityValidationException, that tells me, Title is required. When I set dbContext.Configuration.ValidateOnSaveEnabled = false, everything is OK.
Is there any way to avoid disabling validation on the whole context and to tell EF not to validate properties that are not being updated?
Thanks in advance.
If you use partial updates or stub entities (both approaches are pretty valid!) you cannot use global EF validation because it doesn't respect your partial changes - it always validates whole entity. With default validation logic you must turn it off by calling mentioned:
dbContext.Configuration.ValidateOnSaveEnabled = false
And validate every updated property separately. This should hopefully do the magic but I didn't try it because I don't use EF validation at all:
foreach(var prop in propsToUpdate) {
var errors = contextEntry.Property(prop).GetValidationErrors();
if (erros.Count == 0) {
contextEntry.Property(prop).IsModified = true;
} else {
...
}
}
If you want to go step further you can try overriding ValidateEntity in your context and reimplement validation in the way that it validates whole entity or only selected properties based on state of the entity and IsModified state of properties - that will allow you using EF validation with partial updates and stub entities.
Validation in EF is IMHO wrong concept - it introduces additional logic into data access layer where the logic doesn't belong to. It is mostly based on the idea that you always work with whole entity or even with whole entity graph if you place required validation rules on navigation properties. Once you violate this approach you will always find that single fixed set of validation rules hardcoded to your entities is not sufficient.
One of things I have in my very long backlog is to investigate how validation affects speed of SaveChanges operation - I used to have my own validation API in EF4 (prior to EF4.1) based on DataAnnotations and their Validator class and I stopped using it quite soon due to very poor performance.
Workaround with using native SQL has same effect as using stub entities or partial updates with turned off validation = your entities are still not validated but in addition your changes are not part of same unit of work.
In reference to Ladislav's answer, I've added this to the DbContext class, and it now removes all the properties that aren't modified.
I know its not completely skipping the validation for those properties but rather just omitting it, but EF validates per entity not property, and rewriting the entire validation process anew was too much of hassle for me.
protected override DbEntityValidationResult ValidateEntity(
DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
var falseErrors = result.ValidationErrors
.Where(error =>
{
if (entityEntry.State != EntityState.Modified) return false;
var member = entityEntry.Member(error.PropertyName);
var property = member as DbPropertyEntry;
if (property != null)
return !property.IsModified;
else
return false;//not false err;
});
foreach (var error in falseErrors.ToArray())
result.ValidationErrors.Remove(error);
return result;
}
This is a remix of previous #Shimmy response and it's a version that I currently use.
What I've added is the clause (entityEntry.State != EntityState.Modified) return false; in the Where:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
var falseErrors = result
.ValidationErrors
.Where(error =>
{
if (entityEntry.State != EntityState.Modified) return false;
var member = entityEntry.Member(error.PropertyName);
var property = member as DbPropertyEntry;
if (property != null) return !property.IsModified;
return false;
});
foreach (var error in falseErrors.ToArray())
{
result.ValidationErrors.Remove(error);
}
return result;
}