I have a database context with lazy loading disabled. I am using eager loading to load all of my entities. I cannot update many to many relationships.
Here's the repository.
public class GenericRepository<TEntity> : IGenericRepository<TEntity>
where TEntity : class
{
... other code here...
public virtual void Update(TEntity t)
{
Set.Attach(t);
Context.Entry(t).State = EntityState.Modified;
}
...other code here...
}
Here's the User model.
public partial class User
{
public User()
{
this.Locks = new HashSet<Lock>();
this.BusinessModels = new HashSet<BusinessModel>();
}
public int UserId { get; set; }
public string Username { get; set; }
public string Name { get; set; }
public string Phone { get; set; }
public string JobTitle { get; set; }
public string RecoveryEmail { get; set; }
public Nullable<double> Zoom { get; set; }
public virtual ICollection<Lock> Locks { get; set; }
public virtual ICollection<BusinessModel> BusinessModels { get; set; }
}
If I modify the business models collection, it does not save the business models collection although I have attached the entire entity.
Worker.UserRepository.Update(user);
I'm not sure what is going on. I don't want to break my generic repository/unit of work pattern just to update many-to-many relationships.
Edit 2: I've got this working...but it is extremely different from the pattern that I'm going for. Having hard implementations means I will need to create a method for each type that has a many to many relationship. I am investigating now to see if I can make this a generic method.
Edit 3: So the previous implementation I had did not work like I thought it would. But now, I have a slightly working implementation. If someone would please help me so I can move on from this, I will love you forever.
public virtual void Update(TEntity updated,
IEnumerable<object> set,
string navigationProperty,
Expression<Func<TEntity, bool>> filter,
Type propertyType)
{
// Find the existing item
var existing = Context.Set<TEntity>().Include(navigationProperty).FirstOrDefault(filter);
// Iterate through every item in the many-to-many relationship
foreach (var o in set)
{
// Attach it if its unattached
if (Context.Entry(o).State == EntityState.Detached)
// Exception "an object with the same key already exists"
// This is due to the include statement up above. That statement
// is necessary in order to edit the entity's navigation
// property.
Context.Set(propertyType).Attach(o);
}
// Set the new value on the navigation property.
Context.Entry(existing).Collection(navigationProperty).CurrentValue = set;
// Set new primitive property values.
Context.Entry(existing).CurrentValues.SetValues(updated);
Context.Entry(existing).State = EntityState.Modified;
}
I then call it like this:
Worker.UserRepository.Update(user, user.BusinessModels, "BusinessModels", i => i.UserId == user.UserId, typeof (BusinessModel));
Extremely messy, but it lets me update many-to-many relationships with generics. My big problem is the exception when I go to attach new values that already exist. They're already loaded because of the include statement.
This works:
This doesn't:
After many painful hours, I have finally found a way to update many-to-many relationships with a completely generic repository. This will allow me to create (and save) many different types of entities without creating boilerplate code for each one.
This method assumes that:
Your entity already exists
Your many to many relationship is stored in a table with a composite key
You are using eager loading to load your relationships into context
You are using a unit-of-work/generic repository pattern to save your entities.
Here's the Update generic method.
public virtual void Update(Expression<Func<TEntity, bool>> filter,
IEnumerable<object> updatedSet, // Updated many-to-many relationships
IEnumerable<object> availableSet, // Lookup collection
string propertyName) // The name of the navigation property
{
// Get the generic type of the set
var type = updatedSet.GetType().GetGenericArguments()[0];
// Get the previous entity from the database based on repository type
var previous = Context
.Set<TEntity>()
.Include(propertyName)
.FirstOrDefault(filter);
/* Create a container that will hold the values of
* the generic many-to-many relationships we are updating.
*/
var values = CreateList(type);
/* For each object in the updated set find the existing
* entity in the database. This is to avoid Entity Framework
* from creating new objects or throwing an
* error because the object is already attached.
*/
foreach (var entry in updatedSet
.Select(obj => (int)obj
.GetType()
.GetProperty("Id")
.GetValue(obj, null))
.Select(value => Context.Set(type).Find(value)))
{
values.Add(entry);
}
/* Get the collection where the previous many to many relationships
* are stored and assign the new ones.
*/
Context.Entry(previous).Collection(propertyName).CurrentValue = values;
}
Here's a helper method I found online which allows me to create generic lists based on whatever type I give it.
public IList CreateList(Type type)
{
var genericList = typeof(List<>).MakeGenericType(type);
return (IList)Activator.CreateInstance(genericList);
}
And from now on, this is what calls to update many-to-many relationships look like:
Worker.UserRepository.Update(u => u.UserId == user.UserId,
user.BusinessModels, // Many-to-many relationship to update
Worker.BusinessModelRepository.Get(), // Full set
"BusinessModels"); // Property name
Of course, in the end you will need to somewhere call:
Context.SaveChanges();
I hope this helps anyone who never truly found how to use many-to-many relationships with generic repositories and unit-of-work classes in Entity Framework.
#dimgl Your solution worked for me. What I've done in addition was to replace the hard-coded type and name of the primaryKey with dynamically retrieved ones:
ObjectContext objectContext = ((IObjectContextAdapter)context).ObjectContext;
ObjectSet<TEntity> set = objectContext.CreateObjectSet<TEntity>();
IEnumerable<string> keyNames = set.EntitySet.ElementType.KeyMembers.Select(k => k.Name);
var keyName = keyNames.FirstOrDefault();
var keyType = typeof(TEntity).GetProperty(keyName).PropertyType
foreach (var entry in updatedSet
.Select(obj =>
Convert.ChangeType(obj.GetType()
.GetProperty(keyName)
.GetValue(obj, null), keyType))
.Select(value => context.Set<TEntity>().Find(value)))
{
values.Add(entry);
}
Like this your code won't depend on the Entity key's name and type.
Related
I am working on an ASP.Net MVC application and I have a "Report" object that has related enumerables such as schedules and comments. Using AutoMapper, it has been easy to convert from a report entity to a View Model and back, but I have issues when I try to save the Report object (mapped to an existing entity from a view model) back to the database.
More specifically, I can't seem to concisely update existing entities, insert new entities, and delete old entities using automapper. For instance whenever I map schedules from the view model to a report entity, it deletes the existing schedules and then creates new ones (with incremented indexes). This is my code:
public class ScheduleViewModel
{
public int ScheduleID { get; set; }
public int ReportID { get; set; }
public int Month { get; set; }
public int Day { get; set; }
public string Description { get; set; }
}
public class ReportViewModel
{
public int ReportID { get; set; }
public List<ScheduleViewModel> Schedules { get; set; }
public void Save()
{
dbContext db = new dbContext();
Report original = db.Reports.SingleOrDefault(o => o.ReportID == ReportID);
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<ReportViewModel, Report>();
});
config.AssertConfigurationIsValid();
var mapper = config.CreateMapper();
mapper.Map(this, original);
db.SaveChanges();
}
}
My Report object has a relational key (and a "Schedules" navigational property), so everything is mapped successfully from my view model to the "original" Report. New schedules have a ScheduleID of 0, since they haven't been assigned, and they get added to the database using the auto-increment, which is what I want. The existing schedules maintain their ScheduleID when mapped to the "original" report object, but then recieve incremented IDs once SaveChanges is called.
As I understand it, I'm attaching new schedules to the context whether or not the view model ID properties match the primary key in the database (in this case it is a composite of ReportID and ScheduleID). Is there a clean way, using some sort of ForMember(report => report.Schedules), expression that makes Entity Framework understand to not destroy my existing entities if a View Model object can map to an existing Key?
I am looking for something that functions similar to the code below, but since I will have many enumerable properties attached to my report objects, I don't want to maintain these sections for each:
foreach (Schedule schedule in db.Schedules.Where(s => s.ReportID == this.ReportID))
{
ScheduleViewModel svm = this.Schedules.FirstOrDefault(s => s.ScheduleID == schedule.ScheduleID);
//Update Existing
if (svm != null)
db.Entry(schedule).CurrentValues.SetValues(svm);
//Delete Missing
else
db.Entry(schedule).State = System.Data.Entity.EntityState.Deleted;
}
//Insert New
foreach(ScheduleViewModel svm in this.Schedules.Where(s => s.ScheduleID == 0))
{
svm.ReportID = ReportID;
Schedule schedule = new Schedule() {};
db.Schedules.Add(schedule);
db.Entry(schedule).CurrentValues.SetValues(svm);
}
Apparently this is not currently possible with AutoMapper. Instead of mapping individual entities, AutoMapper destroys the existing entity collection and creates a new one with the same properties. I'm sure that works fine for some applications, but with Entity Framework's change tracking it is telling the database to delete the existing records and insert new ones, with new IDs. Unfortunately it seems collections have to be mapped individually/manually using the method I posted in my original question. Rather than repeating that for every collection, though, I wrote a generic handler that will map a model collection to an entity collection using a specified key -- without destroying the entities:
public void UpdateEntitySet<T>(IEnumerable<object> models, IEnumerable<T> entities, string key) where T : class
{
Dictionary<object, T> entityDictionary = new Dictionary<object, T>();
foreach(T entity in entities)
{
var entityKey = entity.GetType().GetProperty(key).GetValue(entity);
entityDictionary.Add(entityKey, entity);
}
if (models != null)
{
foreach (object model in models)
{
var modelKey = model.GetType().GetProperty(key).GetValue(model);
var existingEntity = entityDictionary.SingleOrDefault(d => Object.Equals(d.Key, modelKey)).Value;
if (existingEntity == null)
{
var newEntity = db.Set<T>().Create();
db.Set<T>().Add(newEntity);
db.Entry(newEntity).CurrentValues.SetValues(model);
}
else
{
db.Entry(existingEntity).CurrentValues.SetValues(model);
entityDictionary.Remove(entityDictionary.Single(d => Object.Equals(d.Key, modelKey)).Key);
}
}
}
for (int i = 0; i < entityDictionary.Count; i++)
db.Entry(entityDictionary.ElementAt(i).Value).State = System.Data.Entity.EntityState.Deleted;
}
If you have a composite key, you'll have to modify the code a little, otherwise you can ignore the mapping with AutoMapper and then use the method above like so:
IEnumerable<Schedule> ScheduleEntities = db.Set<Schedule>().Where(s => s.ReportID == ReportID);
UpdateEntitySet<Schedule>(ScheduleViewModels, ScheduleEntities, "ScheduleID");
I have the following entities:
public class ModuleCriteria
{
public int ModuleCriteriaId { get; set; }
public string Criteria { get; set; }
public List<ModuleCriteriaLookup> ModuleCriteriaLookups { get; set;
}
}
public class ModuleCriteriaLookup
{
public int ModuleCriteriaLookupId { get; set; }
public int ModuleCriteriaId { get; set; } // ** (Foreign Key) **
public int SiteId { get; set; }
public string CategoryId { get; set; }
public ModuleCriteria ModuleCriteria { get; set; }
}
I have the following EF configuration in my Context class (edited for brevity):
protected override void OnModelCreating( DbModelBuilder modelBuilder )
{
base.OnModelCreating( modelBuilder );
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ModuleCriteriaLookup>().HasRequired( mc => mc.ModuleCriteria );
// I tried adding the below line but it made no difference.
//modelBuilder.Entity<ModuleCriteria>().HasMany( mc => mc.ModuleCriteriaLookups );
}
...and I have the following DbSet properties defined in my Context class:
public DbSet<ModuleCriteria> ModuleCriteria { get; set; }
public DbSet<ModuleCriteriaLookup> ModuleCriteriaLookup { get; set; }
I have a CriteriaRepository class, which has a Save method, for persisting changes for my ModuleCriteria entities:
public void Save( ModuleCriteria moduleCriteria )
{
using ( var ctx = new MyAppContext() )
{
ctx.Entry( moduleCriteria ).State = moduleCriteria.ModuleCriteriaId == 0 ? EntityState.Added : EntityState.Modified;
ctx.SaveChanges();
}
}
A ModuleCriteria object can exist without a ModuleCriteriaLookup object, but ModuleCriteriaLookup object has to relate to an existing ModuleCriteria object (related on ModuleCriteriaId).
You can have multiple ModuleCriteraLookup objects all relating to the same one ModuleCriteria object.
The behaviour that I would like, and expect with EF, is:
1) If I create a new ModuleCriteria object (without any ModuleCriteriaLookups), call the Save method in my repository, I would expect to see new ModuleCriteria record in the db with no related ModuleCriteriaLookup records in the db.
2) If I create a new ModuleCriteria object and assign a List<ModuleCriteriaLookup> to it, call the Save method in my repository, I would expect to see new ModuleCriteria record in the db and x new ModuleCriteriaLookup rows in the db which relate to that particular ModuleCriteria.
3) If I add/edit/remove any of the ModuleCriteriaLookup objects that related to one of my ModuleCriteria objects, then call the Save method in my repository, I would expect to see any of the ModuleCriteria's deleted ModuleCriteriaLookup objects to get removed from the db, any new ones added and any edited ones simply to get updated.
So all I ever need worry about is that whatever the ModuleCriteria.ModuleCriteriaLookups property contains for a given ModuleCriteria, that's what will be reflected in the 2 tables in my DB by simply calling the Save method for the ModuleCriteria object in my repository.
Unfortunately at the moment, if I'm adding a new ModuleCriteria object with associated List<ModuleCriteriaLookup> it adds both ModuleCriteria and x ModuleCriteriaLookup rows in the db nicely. But when I want to edit or delete entries in the ModuleCriteria.ModuleCriteriaLookups property, this is not being reflected in the db. Nothing is happening with the ModuleCriteriaLookups rows.
I'm not sure where exactly the problem is, whether its whether the EF mapping configuration, or something to do with how the repository works?
The problem is located in the repository. The DbContext needs to be aware of the existence of entities. So when editing and/or deleting entities the entities need to be fetched from the database first.
This description clearly states:
The .Entry property returns objects from the context that are being
tracked by the context.
Because you directly use this properties right after creating the context, the context isn't tracking these entities and is therefore not aware that something has changed. And thereby is unable to generate the correct SQL statements.
There are several ways to deal with this, depending on the rest of your design.
One way to delete it would be:
public void DeleteModuleCriteriaLookup(ModuleCriteriaLookup[] lookups)
{
using (var ctx = new MyAppContext())
{
var moduleCriteriaId = lookups.First().ModuleCriteriaId;
var moduleCritria = (
from criteria in ctx.ModuleCriteria
where criteria.ModuleCriteriaId == moduleCriteriaId
select criteria
).Single();
var lookupIdsToDelete = lookups.Select(l => l.ModuleCriteriaLookupId);
var lookupsToDelete = (
from lookup in moduleCritria.ModuleCriteriaLookups
where lookupIdsToDelete.Contains(lookup.ModuleCriteriaLookupId)
select lookup
).ToArray();
foreach (var lookup in lookupsToDelete)
{
moduleCritria.ModuleCriteriaLookups.Remove(lookup);
}
ctx.SaveChanges();
}
}
I'm trying to figure out how to smoothly do a partial update (basically a HTTP PATCH) of an entity, using Entity Framework 6.0, but I'm stumped at the number of examples out there that don't seem to work for me (even those that aren't obviously for another version of EF).
What I'd like to accomplish:
The entity is updated without having to load it first; i.e. there's only one trip to the database
Only the properties that I touch are updated - others are left as is
The closest I've gotten is neatly described by this answer to a very similar question, and illustrated by the following code:
public async Task UpdateMyEntity(int id, int? updatedProperty, string otherProperty)
{
using (var context = new MyDbContext())
{
var entity = new MyEntity { Id = id };
context.MyEntities.Attach(entity);
if (updatedProperty != null) { entity.Property = updatedProperty.Value; }
if (!string.IsNullOrEmpty(otherProperty) { entity.OtherProperty = otherProperty; }
await context.SaveChangesAsync();
}
}
Now, this works for simple entities, but I'm getting entity validation errors because I have a couple of required properties and relations that are not updated and therefore not present in the attached entity. As noted, I'd just like to ignore those.
I've debugged and verified that context.Entry(entity).Property(e => e.Property).IsModified changes to true when that line is run, and that all the properties I never touch still return false for similar checks, so I thought EF would be able to handle this.
Is it possible to resolve this under the two constraints above? How?
Update:
With LSU.Net's answer I understand somewhat what I have to do, but it doesn't work fully. The logic fails for referential properties.
Consider the following domain model:
public class MyEntity
{
public int Id { get; set; }
public int Property { get; set; }
[Required]
public string OtherProperty { get; set; }
[Required]
public OtherEntity Related { get; set; }
}
public class OtherEntity
{
public int Id { get; set; }
public string SomeProperty { get; set; }
}
Now, if I try to update a MyEntity, I do the following:
var entity = new MyEntity { Id = 123 }; // an entity with this id exists in db
context.MyEntities.Attach(entity);
if (updatedProperty != null) { entity.Property = updatedProperty.Value; }
await context.SaveChangesAsync();
In my custom validation method, overridden as in the answer below, the validation error on the required property OtherProperty is correctly removed, since it is not modified. However, I still get a validation error on the Related property, because entityEntry.Member("Related") is DbReferenceEntry, not DbPropertyEntry, and thus the validation error is not marked as a false error.
I tried adding a separate, analogous clause for handling reference properties, but the entityEntry doesn't seem to mark those as changed; with relation = member as DbReferenceEntry, relation doesn't have anything to indicate that the relationship is changed.
What can I check against for false errors in this case? Are there any other cases I need to handle specially (one-to-many relationships, for example)?
Entity Framework validation with partial updates
#Shimmy has written some code here to omit the validation logic for unmodified properties. That may work for you.
protected override DbEntityValidationResult ValidateEntity(
DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
var falseErrors = result.ValidationErrors
.Where(error =>
{
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;
}
I have the following two model objects which have a many-to-many relationship:
public class StaffMember
{
public Guid StaffMemberKey {get; set;}
// lots of other properties that aren't relevant
public ICollection<Case> Cases {get; set;}
}
public class Case
{
public int CaseKey {get; set;}
// lots of other properties that aren't relevant
public ICollection<StaffMember> Staff {get; set;}
}
The mapping for the many-to-many relationship is handled in the configuration for the Case entity:
public class CaseMapping : EntityTypeConfiguration<Case>
{
public CaseMapping()
{
// other property and relationship mappings
// Many-to-Many mapping with Staff Members
HasMany(c => c.Staff)
.WithMany(staffMember => staffMember.Cases)
.Map(m =>
{
m.ToTable("Cases_StaffMembers", "dbo");
m.MapLeftKey("CaseKey");
m.MapRightKey("StaffMemberKey");
});
}
}
Everything is working great in terms of being able to query against this relationship, add, delete, etc. However, when trying to explicitly load and filter staff members for a case, as described here, no data is being loaded in to the appropriate collection of related entities.
Here is an example of what I'm attempting do:
var staffMemberKey = Guid.Parse("...");
var caseKey = 5;
using (var context = new CodeFirstContext())
{
var selectedCase = context.Cases.Find(caseKey);
context.Entry(selectedCase).Collection(c => c.Staff).Query().Where(sm => sm.StaffMemberKey == staffMemberKey).Load();
}
I would expect that selectedCase.Staff would contain the staff member that was loaded, but it remains null. If I call ToList() instead of Load when querying for the related data, the resulting list does contain the correct staff member entity. If I simply call context.Entry(selectedCase).Collection(c => c.Staff).Load();, then the data is loaded as expected. Is there something I'm missing? What gives?
As a final note, I have lazy loading and proxy creation disabled for my context, in case that makes any difference in this scenario.
When you call Query(), it returns an IQueryable that gives the entities that would be in that property -- it is not designed to be used to update the property. It is basically a "shortcut" for:
ctx.Staff.Where(staff => staff.Case.Id == caseKey);
Load() will load entities into your context, as if you had called ToList() but without returning anything. It works on any IQueryable, and does not capture anything related to the Entry().
I have an Entity Framework POCO with the following structure.
public class Entity
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
I've created a Data Transfer Object for this entity to be used by my views.
public class EntityDto
{
public int Id { get; set; }
public string Name { get; set; }
}
Now, I have the following mapping code in my Global.asax file.
Mapper.CreateMap<Entity, EntityDto>();
Mapper.CreateMap<EntityDto, Entity>(); // not sure whether I need this as well?
Everything is working fine, I pass the DTO to my views OK and I can create a new instance of Entity from my EntityDto model. The problem arises when I try to edit my Entity; I'm aware this is down to AutoMapper losing the Entity Key that EF creates to track changes to the object, but having read through a few sources there doesn't seem to be a definitive solution. Here is the action I'm using to edit my entity.
public ActionResult EditEntity(EntityDto model)
{
var entity = context.Entities.Single(e => e.Id == model.Id);
entity = Mapper.Map<EntityDto, Entity>(model); // this loses the Entity Key stuff
context.SaveChanges();
return View(model);
}
Now, what do I do to solve this? Can I:
Somehow tell AutoMapper to .Ignore() the Entity Key properties?
Get AutoMapper to copy out the Entity Key properties?
.Attach() my mapped Entity and set the state to modified?
Any help always appreciated.
Try passing entity as a second parameter to your mapping.
entity = Mapper.Map<EntityDto, Entity>(model, entity);
Otherwise, your entity instance is overwritten with a new instance, and you lose the entity created in the first line.
.Attach() my mapped Entity and set the state to modified?
public ActionResult EditEntity(EntityDto model)
{
var entity = Mapper.Map<Entity>(model);
context.Set<Entity>().Attach(entity); // (or context.Entity.Attach(entity);)
context.Entry<Entity>(entity).State = System.Data.EntityState.Modified;
context.SaveChanges();
return View(model);
}
Where is your context instantiated? You should do that in your EditEntity action imo.
public ActionResult EditEntity(EntityDto model)
{
using(var context = new MyContext())
{
var entity = Mapper.Map<Entity>(model);
context.Set<Entity>().Attach(entity); // (or context.Entity.Attach(entity);)
context.Entry<Entity>(entity).State = System.Data.EntityState.Modified;
context.SaveChanges();
return View(model);
}
}
An alternative answer that doesn't require Automapper for the DTO to Entity conversion is using a DbEntry:
var oldEntity = DbSet.FirstOrDefault(x => x.Id == updatedEntity.Id);
var oldEntry = Context.Entry(oldEntity);
oldEntry.CurrentValues.SetValues(updatedEntity);
You don't need any attach/state checking because you are getting the old entity first so it has change tracking attached to it. Also, the CurrentValues.SetValues can accept a different type, in this example updatedEntity is the DTO. Set Values documentation is explained as such:
Sets the values of this dictionary by reading values out of the given object. The given object can be of any type. Any property on the object with a name that matches a property name in the dictionary and can be read will be read. Other properties will be ignored. This allows, for example, copying of properties from simple Data Transfer Objects (DTOs).
So seems like it already can perform in an automapper-esque way.