I have a table with properties like:
Id Name ParentId
ParentId is a foreign key to primary column Id. Now lets say I have a few rows like: (Only showing ParentId from rows)
NULL
/ \
1 2
/ \
3 4
Now, lets say we want to copy row object whose ParentId is NULL with it's all sub objects.
var row = db.FirstOrDefault(x=> x.Id == 1);
var new_row = new Table1();
var subrows = row.Table1.ToArray();
foreach(var row in subrows)
{
db.Entry(row).State = System.Data.Entity.EntityState.Detached;
}
new_row.Table1 = subrows;
db.Table.Add(new_row);
db.saveChanges();
Result: New inserted structure like:
NULL
/ \
1 2
I am assuming that only one sublevel is being copied. How to copy/insert all sublevels?
EDIT: Since detach was helping create a copy till one level, this is what I tried:
private void RecursiveDetach(Table1 parent)
{
var subrows = parent.Table1.ToArray();
foreach (var row in subrows)
{
if(row.Table1.Count() > 0)
{
RecursiveDetach(row);
}
db.Entry(row).State = System.Data.Entity.EntityState.Detached;
}
}
However, now I am getting an error:
Collection was modified; enumeration operation may not execute.
I've had to do this before. I've done it purely in code, recursively copying objects and sanitizing unique IDs where needed, but the cleanest approach I've ever built is serializing the object to XML, then de-serializing into a new object. The approach is less efficient, but fantastically flexible and easy to implement.
//Save object to XML file. Returns filename.
public string SaveObjectAsXML(int id)
{
//however you get your EF context and disable proxy creation
var db = GetContext();
bool currentProxySetting = db.Configuration.ProxyCreationEnabled;
db.Configuration.ProxyCreationEnabled = false;
//get the data
var item = db.GetItem(id); //retrieval be unique to your setup, but I have
//a more generic solution if you need it. Make
//sure you have all the sub items included
//in your object or they won't be saved.
db.Configuration.ProxyCreationEnabled = currentProxySetting;
//if no item is found, do whatever needs to be done
if (item == null)
{
return string.Empty;
}
//I actually write my data to a file so I can save states if needed, but you could
//modify the method to just spit out the XML instead
Directory.CreateDirectory(DATA_PATH); //make sure path exists to prevent write errors
string path = $"{DATA_PATH}{id}{DATA_EXT}";
var bf = new BinaryFormatter();
using (FileStream fs = new FileStream(path, FileMode.Create))
{
bf.Serialize(fs, repair);
}
return path;
}
//Load object from XML file. Returns ID.
public int LoadXMLData(string path)
{
//make sure the file exists
if (!File.Exists(path))
{
throw new Exception("File not found.");
}
//load data from file
try
{
using (FileStream fs = new FileStream(path, FileMode.Open))
{
var item = (YourItemType)new BinaryFormatter().Deserialize(fs);
db.YourItemTypes.Add(item);
db.SaveChanges();
return item.Id;
}
}
catch (Exception ex) {
//Exceptions here are common when copying between databases where differences in config entries result in mis-matches
throw;
}
}
The use is simple.
//save object
var savedObjectFilename = SaveObjectAsXML(myObjID);
//loading the item will create a copy
var newID = LoadXMLData(savedObjectFilename);
Best of luck!
Here's a second, totally different answer: recursively detach your whole object instead of just the parent object. The following is written as an extension method to your context object:
/// <summary>
/// Recursively detaches item and sub-items from EF. Assumes that all sub-objects are properties (not fields).
/// </summary>
/// <param name="item">The item to detach</param>
/// <param name="recursionDepth">Number of levels to go before stopping. object.Property is 1, object.Property.SubProperty is 2, and so on.</param>
public static void DetachAll(this DbContext db, object item, int recursionDepth = 3)
{
//Exit if no remaining recursion depth
if (recursionDepth <= 0) return;
//detach this object
db.Entry(item).State = EntityState.Detached;
//get reflection data for all the properties we mean to detach
Type t = item.GetType();
var properties = t.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(p => p.GetSetMethod()?.IsPublic == true) //get only properties we can set
.Where(p => p.PropertyType.IsClass) //only classes can be EF objects
.Where(p => p.PropertyType != typeof(string)) //oh, strings. What a pain.
.Where(p => p.GetValue(item) != null); //only get set properties
//if we're recursing, we'll check here to make sure we should keep going
if (properties.Count() == 0) return;
foreach (var p in properties)
{
//handle generics
if (p.PropertyType.IsGenericType)
{
//assume its Enumerable. More logic can be built here if that's not true.
IEnumerable collection = (IEnumerable)p.GetValue(item);
foreach (var obj in collection)
{
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
else
{
var obj = p.GetValue(item);
db.Entry(obj).State = EntityState.Detached;
DetachAll(db, obj, recursionDepth - 1);
}
}
}
The biggest thing to look out for will be config-type properties--object that represent data not directly related to the object. These might create conflicts, so better to make sure your object doesn't include them.
Note:
This approach requires that all sub-objects you wish to copy be populated in advance, avoiding lazy loading. To ensure this, I use the following extension for my EF queries:
//Given a custom context object such that CustomContext inherits from DbContext AND contains an arbitrary number of DbSet collections
//which represent the data in the database (i.e. DbSet<MyObject>), this method fetches a queryable collection of object type T which
//will preload sub-objects specified by the array of expressions (includeExpressions) in the form o => o.SubObject.
public static IQueryable<T> GetQueryable<T>(this CustomContext context, params Expression<Func<T, object>>[] includeExpressions) where T : class
{
//look through the context for a dbset of the specified type
var property = typeof(CustomContext).GetProperties().Where(p => p.PropertyType.IsGenericType &&
p.PropertyType.GetGenericArguments()[0] == typeof(T)).FirstOrDefault();
//if the property wasn't found, we don't have the queryable object. Throw exception
if (property == null) throw new Exception("No queryable context object found for Type " + typeof(T).Name);
//create a result of that type, then assign it to the dataset
IQueryable<T> source = (IQueryable<T>)property.GetValue(context);
//return
return includeExpressions.Aggregate(source, (current, expression) => current.Include(expression));
}
This method assumes that you have a custom context object that inherits from DbContext and contains DbSet<> collections of your objects. It will find the appropriate one DbSet<T> and return a queryable collection that will pre-load specified sub-classes in your object. These are specified as an array of expressions. For example:
//example for object type 'Order'
var includes = new Expression<Func<Order, object>>[] {
o => o.SalesItems.Select(p => p.Discounts), //load the 'SalesItems' collection AND the `Discounts` collection for each SalesItem
o => o.Config.PriceList, //load the Config object AND the PriceList sub-object
o => o.Tenders, //load the 'Tenders' collection
o => o.Customer //load the 'Customer' object
};
To retrieve my queryable collection, I now call it as such:
var queryableOrders = context.GetQueryable(includes);
Again, the purpose here is to create a queryable object that will eagerly load only the sub-objects (and sub-sub-objects) that you actually want.
To get a specific item, use this like any other queryable source:
var order = context.GetQueryable(includes).FirstOrDefault(o => o.OrderNumber == myOrderNumber);
Note that you can also provide the include expression inline; however, you will need to specify the generic:
//you can provide includes inline if you just have a couple
var order = context.GetQueryable<Order>(o => o.Tenders, o => o.SalesItems).FirstOrDefault(o => o.OrderNumber == myOrderNumber);
Related
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.
I currently working on an dynamic upload module. The idea is to only define the file and the data contract for each new file. Currently I'm using reflection with 2 foreach, this is some heavy code to do this. As you can see in the code I have my object containing the csv file and 2 other lists. These two lists contains all the properties of the object where I would like to do data validation on.
var myCustomObjects = CsvSettings(new CsvReader(readFile, config)).GetRecords<MyCustomObject>();
var decimalProprties = GetPropertyNames<MyCustomObject>(typeof(decimal)).ToList();
var dateProprties = GetPropertyNames<MyCustomObject>(typeof(DateTime)).ToList();
foreach (var myCustomObject in myCustomObjects)
{
foreach (var dateProperty in dateProprties)
{
var value = myCustomObject.GetType().GetProperty(dateProperty).GetValue(myCustomObject, null);
Console.WriteLine(value); //code to check and report the value
}
Console.WriteLine(myCustomObject.Een + "|" + myCustomObject.Twee + "|" + myCustomObject.Drie);
}
How can I do this with an expression or even another way to have so less heavy code?
The code seems fine as-is. You could perhaps simplify it a little by using a method that returns Key/Value pairs for all public properties of a certain type, like so (error handling elided for brevity):
public static IEnumerable<KeyValuePair<string, T>> PropertiesOfType<T>(object myObject)
{
var properties =
from property in myObject.GetType().GetProperties()
where property.PropertyType == typeof(T) && property.CanRead
select new KeyValuePair<string, T>(property.Name, (T)property.GetValue(myObject));
return properties;
}
Then you can avoid the additional call to GetProperty() in your inner loop:
foreach (var myCustomObject in myCustomObjects)
{
foreach (var dateProperty in PropertiesOfType<DateTime>(myCustomObject))
{
Console.WriteLine(dateProperty.Value); // code to check and report the value.
}
}
Also note that you don't seem to need the .ToList() calls.
I am trying to implement a deep clone feature and already asked some question:
IsPrimitive doesn't include nullable primitive values
Array, List, IEnumerable, CustomList class cast to one and iterate threw them
https://stackoverflow.com/questions/20978951
I am modifing the following code:
https://stackoverflow.com/a/11308879/2598770
And currently I am facing the problem, that my original list also gets modified with "copied" objects instead of only the clone object, as recommended by the 2nd earlier question (Array, List, IEnumerable, CustomList class cast to one and iterate threw them). I have change the following code:
//Code directly from source
var cloneObject = CloneMethod.Invoke(originalObject, null);
if (typeToReflect.IsArray)
{
var arrayType = typeToReflect.GetElementType();
if (IsPrimitive(arrayType) == false)
{
Array clonedArray = (Array)cloneObject;
clonedArray.ForEach((array, indices) => array.SetValue(InternalCopy(clonedArray.GetValue(indices), visited), indices));
}
}
To a version that also handles, IList and not just arrays:
var cloneObject = CloneMethod.Invoke(originalObject, null);
if (cloneObject is IList)
{
if (typeToReflect.GetGenericArguments().Any())
{
var arrayType = typeToReflect.GenericTypeArguments[0];
if (IsPrimitive(arrayType) == false)
{
var clonedArray = (IList)cloneObject;
if (clonedArray.IsReadOnly == false)
for (var i = 0; i < clonedArray.Count; i++)
{
var originalListEntry = clonedArray[i];
var clonedListEntry = InternalCopy(originalListEntry, visited);
clonedArray[i] = clonedListEntry;
}
}
}
}
but on the line clonedArray[i] = clonedListEntry;, it doesnt just change the clonedArray but also the originalObject.
How can I prevent this, so that the clonedListEntry only gets set on the clonedArray?
You need to create a new container instead of just getting a reference to cloneObject. For example:
var existingList = (IList)cloneObject;
var clonedArray = Array.CreateInstance(arrayType, existingList.Count);
You can then go on to populate clonedArray. In the existing code the clonedArray is just a reference to your original list (it's not even guaranteed to be an array!), so any changes made to it get reflected to the original as well.
I know the title wasn't the greatest, but here is what I actually want to accomplish.
I have a details view that represents Entity1 and its associations. I am capturing the property names and values in a key/value pair. I am currently using reflection to set the Entity properties to the corresponding values for non associations. I doubt this is the most efficient way, but I have been unable to find a better way using Expression Trees. So now I need to set the associations of Entity1 to their corresponding entities based on the primary key of those entity associations, call them Entity2-4.
When iterating the Properties of Entity1, I don't know how to construct a dynamic query to Entity2-4 and set Entity1.association to the corresponding entity. Here is the code I have so far:
foreach (string k in e.Values.Keys)
{
if (e.Values[k] != null && !String.IsNullOrEmpty(e.Values[k].ToString()))
{
System.Type objectType = Entity1.GetType();
PropertyInfo[] p = objectType.GetProperties();
foreach (PropertyInfo pi in p)
{
// set Entity1.Property for non associations (works just fine)
if (pi.Name == k)
{
System.Type t = pi.PropertyType;
pi.SetProperty(e.Values[k].ToString(), Entity1);
break;
}
// when i see pi.Name contain Reference, I know I'm working on an association
else if (pi.Name.Contains("Reference"))
{
// k is in the form of Entity.Property
var name = pi.Name.Left("Reference");
var keys = k.Split('.');
var ent = keys[0];
var prop = keys[1];
if (name == ent)
{
// here I need to obtain an instance from the db
// ie generate my dynamic query to the Entity with the name
// contained within the var "ent"
// I tried using reflection and could instantiate the entity
// but it did me no good as I needed the entity from the db
var entityInstance = some dynamic query;
// here I need to set the association of Entity1 to entityInstance from above
// normally I would use reflection, but I'm not sure that would work
// since EntityReference is the actual property returned by reflection
Entity1.SetAssocation(prop, Entity2);
break;
}
}
}
}
}
EDIT
I basically need construct the entity and its association entities so that I can submit them to the data context. Entity 2 through 4 exist in the db, I need to query the db to obtain the instances to that I may associate them to the new Entity1 I am creating and going to submit.
My basic model:
Entity1
Entity1.ID
Entity1.Prop1
Entity1.Prop2
Entity1.Prop3
Entity1.Entity2
Entity1.Entity3
Entity1.Entity4
Entity2
Entity2.ID
Entity2.Name
Entity3
Entity3.ID
Entity3.Name
Entity4
Entity4.ID
Entity4.Name
We've had an in-depth discussion about how to get the metadata out of the DbContext. Here are a few links to get you started. And I'll add some specific comments.
How I can read EF DbContext metadata programmatically?
A short summary (but you should check in there for more):
using (var db = new MyDbContext())
{
var objectContext = ((IObjectContextAdapter)db).ObjectContext;
var container = objectContext.MetadataWorkspace.GetEntityContainer(objectContext.DefaultContainerName, DataSpace.CSpace);
var dependents = ((EntitySet)(set)).ForeignKeyDependents;
var principals = ((EntitySet)(set)).ForeignKeyPrincipals;
var navigationProperties = ((EntityType)(set.ElementType)).NavigationProperties;
// and e.g. for many-to-many (there is more for other types)
ManyToManyReferences = navigationProperties.Where(np =>
np.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
np.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
.Select(np => Extensions.CreateLambdaExpression<TEntity>(np.Name))
.ToList();
}
#Goran Obradovic did a great job to wrap up what I started into a set of reusable queries (my due credits to him:).
I have worked out all other sorts of information that's in there. This is just for the DataSpace.CSpace (which is the most useful to you), but there is also DataSpace.SSpace etc. - which are more for creating SQL queries etc. I'll put most links at the bottom.
Specifics:
In your case following might be helpful:
(note: I'm not entirely sure what you're after, but I'm trying to guess here what's the direction that you're heading)
db.Set<Worker>().Find(1);
Is the generic method for accessing DbSet for a specific entity.
You could also construct it from a Type if you need it to be fully dynamic, e.g....
(I always wanted to do this:)
MethodInfo setMethod = typeof(DbContext).GetMethod("Set", new Type[]{});
MethodInfo genericSetMethod = setMethod.MakeGenericMethod(new Type[] { typeof(YourEntity) });
var set = genericSetMethod.Invoke(db, new object[] {});
Put your entity - or your Type instead of typeof(YourEntity).
You could then proceed and query that for e.g. Find(id) - for that entity - to get concrete values etc.
That's as dynamic as it gets - I'm not sure if that's what you want - but I'm just throwing things out here in case you need it.
That should get you started at least I hope.
links:
(all are posts of mine - some may be more or less relevant but might help)
How I can read EF DbContext metadata programmatically?
How check by unit test that properties mark as computed in ORM model?
Get Model schema to programmatically create database using a provider that doesn't support CreateDatabase
Programmatic data transformation in EF5 Code First migration
So, I wasn't able to perform this dynamically. But here is my working solution. Can anyone advise on how to perform this dynamically?
foreach (string k in e.Values.Keys)
{
if (e.Values[k] != null && !String.IsNullOrEmpty(e.Values[k].ToString()))
{
System.Type objectType = roster.GetType();
PropertyInfo[] p = objectType.GetProperties();
foreach (PropertyInfo pi in p)
{
if (pi.Name == k)
{
System.Type t = pi.PropertyType;
pi.SetProperty(e.Values[k].ToString(), roster);
break;
}
else if (pi.Name.Contains("Reference"))
{
var name = pi.Name.Left("Reference");
var keys = k.Split('.');
var entityName = keys[0];
var prop = keys[1];
if (name == entityName )
{
var val = e.Values[k].ToString();
switch (pi.Name)
{
case "Entity2Reference":
Entity1.Entity2Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, val);
break;
case "Entity3Reference":
Entity1.Entity3Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, val);
break;
case "Entity4Reference":
Entity1.Entity4Reference.EntityKey = new EntityKey("MyEntities." + entityName + "s", prop, Int64.Parse(val));
break;
}
}
}
}
}
}
I have a collection ProductSearchResults, below method intends to find a specific product in that collection and update it. I end up updating the object that points to the element of the collection instead of the actual element it self though(i think)
Can you please show me how to do this properly so that I update the actual product in the collection
Thanks
public void UpdateProductInfo(ProductInfo product)
{
var productToUpdate = this.ProductSearchResults.Where(p => p.ID == product.ID);
if (productUpdate.Count() > 0)
{
var toUpdate = productToUpdate.First<ProductInfo>();
toUpdate = product;
}
}
IN actual fact all you are doing is changing the reference to the local variable toUpdate to point at the passed-in argument product.
Lets take a step backwards, when you do:
var toUpdate = productToUpdate.First<ProductInfo>();
you have a reference to an item from your collection (ProductSearchResults). You can now happily update its properties, ala:
toUpdate.ProductName = product.ProductName;
toUpdate.Price = product.Price;
//etc..
however, you cannot update the itemn in the collection to point to a different/new item in the way you were attempting to. You could remove that item from the collection, and add your new one if that is indeed what you require:
public void UpdateProductInfo(ProductInfo product)
{
var productToUpdate = this.ProductSearchResults.Where(p => p.ID == product.ID);
if (productUpdate.Count() > 0)
{
var toUpdate = productToUpdate.First<ProductInfo>();
this.ProductSearchResults.Remove(toUpdate);
this.ProductSearchResults.Add(product);
}
}
Hope that helps.
var productToUpdate = this.ProductSearchResults.FirstOrDefault(p => p.ID == product.ID);
if (productUpdate != null)
{
productUpdate.Property = product.Property;
...continue for other properties
}
In C#, writing variable = expression; assigns a new value to the variable. It does not affect the value that was previously referenced by that variable.
In your example, you have a value called product, which is a ProductInfo provided by the caller, and a value you get from your list, which you stored in toUpdate, that you wish to update. This would involve calling member functions (or assigning to properties) of toUpdate based on values in product.
However, I suspect you actually want to update the product information of a product found in a database or other kind of storage that is accessed by ProductSearchResults. The storage engine you use (or your data access layer) probably provides you with a function to save a ProductInfo associated with a certain identifier, so all you need to do is call that function.
Do you want to replace the product in the collection or update some properties on it? Assuming the latter, here's an example:
public void UpdateProductInfo(ProductInfo product)
{
var productToUpdate = this.ProductSearchResults.FirstOrDefault(p => p.ID == product.ID).;
if (productToUpdate == null)
{
// throw exception?
}
else
{
productToUpdate.Price = productInfo.Price; // for example
}
}