Duplicating Entity Framework entities - c#

I have this EF query:
var records = mydata.Where(a => some condition).ToList();
Records is a list of Record objects. Record is a database table that has a one to many relationship called "relation".
The EF object represents that member variable as Collection<Relation>.
After the where above, I see that "relation" member variable contains a collection of 18 entities.
I need to duplicate those Record objects, so that I am detaching them first, this way:
var detached = this.DetachEntities(records, db, "RecordId");
Where this.DetachEntities is defined this way:
private T DetachEntity<T>(T entity, Repositories.GestionActivosEntities db, string keyName) where T : class
{
db.Entry(entity).State = EntityState.Detached;
if (entity.GetType().GetProperty(keyName) != null)
{
entity.GetType().GetProperty(keyName).SetValue(entity, 0);
}
return entity;
}
private List<T> DetachEntities<T>(List<T> entities, Repositories.GestionActivosEntities db, string keyName) where T : class
{
foreach (var entity in entities)
{
this.DetachEntity(entity, db, keyName);
}
return entities;
}
After I detached the entities, I am doing:
db.Record.AddRange(detached);
The problem, is that, after I detached the list, the relation is not copied also, resulting that "relation" member variable contains no elements.
How can I keep the relation elements after detaching?
EDIT:
This shows the debugging session:
The breakpoint is placed in the line where the query is done. The cursor shows the line after the query was executed.
This screenshot shows the child records ValorCampo and Workflow
This screenshot shows the entity after detaching it. Note the child records are not present in the detached object
As I have told, the problem only is when detaching. Child records are not kept. Database rows remain untouched.

I had the same problem, unfortunately navigation properties are lost after detaching an Item or when entity state is changed to detached.
what you can do is clone the entity
one way to do this is : Context.Entry(your_entity).CurrentValues.ToObject();
however this will not clone the navigation properties either
if you fully want to clone an object among with navigation properties
the easiest way for me to achieve it was using automapper library for c#
Below is a sample usage:
var config = new MapperConfiguration(cfg => cfg.CreateMap<originalObject, T>());
var mapper = new Mapper(config);
// or
var mapper = config.CreateMapper();
T clonedObject = mapper.Map<T>(originalObject);
after you clone and detach the original object, you can add
db.Record.AddRange(clonedObject );
and below is a generic extension to do it
public static object Map<T>(this T source)
{
var fullName = source.GetType().FullName;
var sourceType = source.GetType();
var baseType = ObjectContext.GetObjectType(source.GetType());
var config = new MapperConfiguration(cfg =>
cfg.CreateMap(sourceType, baseType));
var mapper = config.CreateMapper();
var entity = mapper.Map(source, sourceType, baseType);
return entity;
}
where you can call it like
var clonedObject = originalObject.Map();
Hope this helps!

Related

Clone dynamic object using Entity Framework Repository Pattern

I'd like to be able to clone an object and its children.
In a previous project (EF6.0) an implementation like this worked fine;
Job job = db.Jobs.AsNoTracking()
.Include("Columns")
.Include("Exclusions")
.Include("Batches")
.Include("OtherColumns")
.First(x => x.Id == (int)jobId);
db.Jobs.Add(job);
db.SaveChanges();
A new job was added to the database, as well as new children.
Now in a new project (EFCore3.1.3) I'm using a Repository pattern and trying to create a clone function;
public async Task<int> CloneByIdAsync(int id, string childrenObjectsToInclude)
{
var query = _dbContext.Set<T>().AsNoTracking().AsQueryable(); // Get as no tracking so all objects can be saved as new (no ids)
string[] includes = childrenObjectsToInclude.Split(';');
foreach (string include in includes)
query = query.AsNoTracking().Include(include);
T thing = query.AsNoTracking().First(x => x.Id == id); // We have to get the thing we want
await _dbContext.Set<T>().AddAsync(thing); // Add to the collection
return await _dbContext.SaveChangesAsync(); // Save the changes - should generate all new ids and items
}
You can see I've tried to use AsNoTracking() in a couple of places, but this doesn't seem to work. It retrieves the object and it's children just fine, but when trying to add to the database it's trying to insert the object with the same Id and so I get a SQL Exception.
Am I doing something obviously wrong?
After trying a few things I eventually conceded that you need to set the object's Id to 0 in EF Core for it to be treated as a new object. I used something like the below, and manually detached all the child entities that I wanted on the cloned object. With their Id's all set to 0 when save changes occurs a new set of child objects will also be created correctly linked to the new parent object.
var project = await _dbContext.Projects
.Include("FloorAreaTypes")
.Include("FloorAreaTypes.FloorAreaCategories")
.SingleAsync(x => x.Id == id);
var fts = project.FloorAreaTypes.ToList();
var fcs = fts.SelectMany(ft => ft.FloorAreaCategories).ToList();
DetachEntity(project);
DetachEntities(fts);
DetachEntities(fcs);
await _dbContext.AddAsync(project);
await _dbContext.AddRangeAsync(fts);
await _dbContext.AddRangeAsync(fcs);
await _dbContext.SaveChangesAsync();
private T DetachEntity<T>(T entity) where T : class
{
_dbContext.Entry(entity).State = EntityState.Detached;
if (entity.GetType().GetProperty("Id") != null)
{
entity.GetType().GetProperty("Id").SetValue(entity, 0); // Id's must be set to 0 for a new object, even with no tracking
}
return entity;
}
private List<T> DetachEntities<T>(List<T> entities) where T : class
{
foreach (var entity in entities)
{
this.DetachEntity(entity);
}
return entities;
}
With some more time I'm sure this could be refactored to use Reflection with recursive functions for child objects based on a set of Include strings.

EntityFramework: Eager loading with excludes instead of includes?

My data model has a lot of nested entities and I would like to eagerly load the whole object tree ... except for a view entities that will be explicitly loaded on demand.
Using include paths I have to specify many paths and every time I add a new entity I will have to adapt those include paths. I currently use following method of my repository to load all entities of a type:
public virtual IQueryable<TEntity> All(string commaSeperatedIncludePropertyPaths = "")
{
IQueryable<TEntity> initialQuery = Context.Set<TEntity>();
string[] includePaths = commaSeperatedIncludePropertyPaths.Split(new[] { ','}, StringSplitOptions.RemoveEmptyEntries);
return includePaths.Aggregate(initialQuery, (currentQuery, includeProperty) => currentQuery.Include(includeProperty));
}
The passed include paths already fill a whole screen.
Therefore I would prefer that the EntityFramework automatically loads all navigation properties eagerly, except for the ones I specify with exclude paths:
public virtual IQueryable<TEntity> All(string commaSeperatedExcludePropertyPaths = "")
{
//... how to implement?
}
The exclude paths would help to avoid circular dependencies and to filter out the few entities I don't want to load eagerly. Specifying excludes instead of includes would reduce boilerplate code for me.
Is this possible with EF 6.1.3 or planned for EF 7? If not, what are reasons against that option?
Did anyone already try to read entity meta data and apply it for "auto eager loading" and failed?
Related (old) questions and articles:
Overview on options for loading navigation properties:
https://msdn.microsoft.com/en-us/magazine/hh205756.aspx
Auto eager load
Entity framework auto eager load
Entity Framework - Is there a way to automatically eager-load child entities without Include()?
Entity framework linq query Include() multiple children entities
Type save includes
Entity Framework .Include() with compile time checking?
Below is a first draft for a solution. I still have to find out if it's practicable ... and I'll consider to rework the loading approach (as Lanorkin suggested), too. Thank you for your comments.
Edit
It turned out that, while excludes might make sense when developing an application ...doing many changes to the domain model..., excludes are not more elegant than includes for a "real world example" that I just considered.
a) I went through my entities and counted the number of included and excluded navigation properties. The average number of excluded properties was not significantly smaller then the number of included properties.
b) If I do consider a distinct navigation property "foos" for the exclusions, I will be forced to consider exclusions for the sub entities of type Foo ... if I do not want to use its properties at all.
On the other hand, using inclusions, I just need to specify the navigation property "foos" and do not need to specify anything else for the sub entities.
Therefore, while excludes might save some specs for one level, they dent to require more specs for the next level ... (when excluding some intermediate entities and not only entities that are located at the leaves of the loaded object tree).
c) Furthermore, the includes/excludes might not only depend on the type of the entity but also on the path that is used to access it. Then an exclude needs to be specified like "exclude properties xy when loading the entity for one purpose and exclude properties z when loading the entity for another purpose".
=> As a result of this considerations I will go on using inclusions.
I implemented type save inclusions that are based on inclusion dictionaries instead of strings:
private static readonly Inclusions<Person> _personInclusionsWithCompanyParent = new Inclusions<Person>(typeof(Company))
{
{e => e.Company, false},
{e => e.Roles, true}
};
I have a method that creates the query from a list of inclusions. That method also checks if all existing navigation properties are considered in the dictionaries. If I add a new entity and forget to specify corresponding inclusions, an exception will be thrown.
Nevertheless, here is an experimental solution for using excludes instead of includes:
private const int MAX_EXPANSION_DEPTH = 10;
private DbContext Context { get; set; } //set during construction of my repository
public virtual IQueryable<TEntity> AllExcluding(string excludeProperties = "")
{
var propertiesToExclude = excludeProperties.Split(new[]
{
','
},
StringSplitOptions.RemoveEmptyEntries);
IQueryable<TEntity> initialQuery = Context.Set<TEntity>();
var elementType = initialQuery.ElementType;
var navigationPropertyPaths = new HashSet<string>();
var navigationPropertyNames = GetNavigationPropertyNames(elementType);
foreach (var propertyName in navigationPropertyNames)
{
if (!propertiesToExclude.Contains(propertyName))
{
ExtendNavigationPropertyPaths(navigationPropertyPaths, elementType, propertyName, propertyName, propertiesToExclude, 0);
}
}
return navigationPropertyPaths.Aggregate(initialQuery, (current, includeProperty) => current.Include(includeProperty));
}
private void ExtendNavigationPropertyPaths(ISet<string> navigationPropertyPaths,
Type parentType,
string propertyName,
string propertyPath,
ICollection<string> propertiesToExclude,
int expansionDepth)
{
if (expansionDepth > MAX_EXPANSION_DEPTH)
{
return;
}
var propertyInfo = parentType.GetProperty(propertyName);
var propertyType = propertyInfo.PropertyType;
var isEnumerable = typeof(IEnumerable).IsAssignableFrom(propertyType);
if (isEnumerable)
{
propertyType = propertyType.GenericTypeArguments[0];
}
var subNavigationPropertyNames = GetNavigationPropertyNames(propertyType);
var noSubNavigationPropertiesExist = !subNavigationPropertyNames.Any();
if (noSubNavigationPropertiesExist)
{
navigationPropertyPaths.Add(propertyPath);
return;
}
foreach (var subPropertyName in subNavigationPropertyNames)
{
if (propertiesToExclude.Contains(subPropertyName))
{
navigationPropertyPaths.Add(propertyPath);
continue;
}
var subPropertyPath = propertyPath + '.' + subPropertyName;
ExtendNavigationPropertyPaths(navigationPropertyPaths,
propertyType,
subPropertyName,
subPropertyPath,
propertiesToExclude,
expansionDepth + 1);
}
}
private ICollection<string> GetNavigationPropertyNames(Type elementType)
{
var objectContext = ((IObjectContextAdapter)Context).ObjectContext;
var entityContainer = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
var entitySet = entityContainer.EntitySets.FirstOrDefault(item => item.ElementType.Name.Equals(elementType.Name));
if (entitySet == null)
{
return new List<string>();
}
var entityType = entitySet.ElementType;
return entityType.NavigationProperties.Select(np => np.Name)
.ToList();
}

Tracking Original Values of Entity in Multi-Layered/Tier Environment

Referring - How to get original values of an entity in Entity Framework? - I tried to extract the original value(s) of an entity in EF. But the ObjectStateManager.GetOBjectStateEntry giving the modified value of the entity. What am I missing?
I am using EF 4.0 (POCO Entities) in a multi-layered environment.
public bool Update(IMessage objMessage)
{
object ob = objMessage.GetMaster();
appSancAdvice _entity = ob as appSancAdvice;
using (var context = new BISEntities())
{
context.appSancAdvices.Attach(_entity);
ObjectStateEntry objectState = context.ObjectStateManager.GetObjectStateEntry(_entity);
objectState.ChangeState(System.Data.EntityState.Modified);
// this is giving the modified value of _entity
var originalValues = context.ObjectStateManager.GetObjectStateEntry(_entity).OriginalValues["sancstatus_id"];
int _i = context.SaveChanges();
return (_i > 0) ? true : false;
}
}
The context does not know the original value because you attach the entity. If you want the original values, you must fetch the object from the database. It is not that EF does that automatically when you get OriginalValues from a newly attached object.

Two references to the same domain/entity model

Problem
I want to save the attributes of a model that have changed when a user edits them. Here's what I want to do ...
Retrieve edited view model
Get domain model and map back updated value
Call the update method on repository
Get the "old" domain model and compare values of the fields
Store the changed values (in JSON) into a table
However I am having trouble with step number 4. It seems that the Entity Framework doesn't want to hit the database again to get the model with the old values. It just returns the same entity I have.
Attempted Solutions
I have tried using the Find() and the SingleOrDefault() methods, but they just return the model I currently have.
Example Code
private string ArchiveChanges(T updatedEntity)
{
//Here is the problem!
//oldEntity is the same as updatedEntity
T oldEntity = DbSet.SingleOrDefault(x => x.ID == updatedEntity.ID);
Dictionary<string, object> changed = new Dictionary<string, object>();
foreach (var propertyInfo in typeof(T).GetProperties())
{
var property = typeof(T).GetProperty(propertyInfo.Name);
//Get the old value and the new value from the models
var newValue = property.GetValue(updatedEntity, null);
var oldValue = property.GetValue(oldEntity, null);
//Check to see if the values are equal
if (!object.Equals(newValue, oldValue))
{
//Values have changed ... log it
changed.Add(propertyInfo.Name, newValue);
}
}
var ser = new System.Web.Script.Serialization.JavaScriptSerializer();
return ser.Serialize(changed);
}
public override void Update(T entityToUpdate)
{
//Do something with this
string json = ArchiveChanges(entityToUpdate);
entityToUpdate.AuditInfo.Updated = DateTime.Now;
entityToUpdate.AuditInfo.UpdatedBy = Thread.CurrentPrincipal.Identity.Name;
base.Update(entityToUpdate);
}
The issue is that Entity Framework cache's the objects it reads in the DbSet. So when you request the object the second time, it isn't going to the database because it already has loaded it.
However, the good news is that Entity automatically tracks the original values. See this question for information on how to get them: How to get original values of an entity in Entity Framework?

Adding foreign key relationship with Entity Framework

This problem is similar to my previously asked question. When I query data using the Entity Framework (EF) I always use the MergeOption.NoTracking option because I end up taking my EF generated objects and mapping them to view models which have lovely attributes decorated on the property to enforce validation and so on.
I am trying to add a foreign key relationship using the EF, but anytime I do I am getting the following exception:
The object being attached to the source object is not attached to the same ObjectContext as the source object
Here is my code:
public static void UpdateDownloadFileVersion(DownloadFile downloadFile, int[] selectedVersions) {
using (SupportEntity supportContext = new SupportEntity()) {
supportContext.DownloadFiles.Attach(downloadFile);
var productVersionIdsToAdd = (from v in selectedVersions
where (downloadFile.ProductVersions.Any(pv => pv.Id == v) == false)
select v).ToList();
foreach (var productVersionId in productVersionIdsToAdd) {
var productVersion = new ProductVersion() { Id = productVersionId };
downloadFile.ProductVersions.Attach(productVersion); //Exception happens here.
downloadFile.ProductVersions.Add(productVersion);
}
supportContext.SaveChanges();
}
}
This is where Stub Entities become very very useful...
var productVersion = new ProductVersion() { Id = productVersionId };
supportContext.AttachTo("ProductVersions", productVersion);
Here is a good article
In above case, when attached productVersion is assigned to product versions' entity, productversion entity gets attached to context, with EntityState=Added. Entire graph will be in or out of context.

Categories