How can I create a copy an existent entity(dbset) but changing the primary key?
For example, I have the code below:
foreach(var item in db.PedidoMassPro_Detail.Where(o => o.Order_Number.Equals(orderNumber)))
{
PedidoMassPro_Detail newItem = item;
newItem.Order_Number = "TESTE111";
db.PedidoMassPro_Detail.Add(newItem);
db.SaveChanges();
}
I get the error: New transaction is not allowed because there are other threads running in the session.
There is a lot of columns, so that is the reason why I not setting each column, I will need a copy and the only thing that I need to change is the Primary Key (Order_Number).
get a list of untracked objects from EF by specifying that you want them AsNoTracking, if it is tracked, EF will know that entity exists in the db already, and instead of adding it as new entity, it will try to update.
db.PedidoMassPro_Detail.Where(o => o.Order_Number.Equals(orderNumber).AsNoTracking()
use toList to materialize objects, I think this is the reason you are getting the current error, while iterating a list, you are trying to change the list, so make sure first you get a list of items matching the criteria.
var orderSet = db.PedidoMassPro_Detail.Where(o => o.Order_Number.Equals(orderNumber).AsNoTracking().ToList();
use single save to serialize changes to db
foreach(var item in orderSet )
{
item.Order_Number = "TESTE111";
//handle primary key, is it Guid, auto identity?
db.PedidoMassPro_Detail.Add(item);
}
db.SaveChanges();
Related
I have the following
class Human
int id
string gender
List<PhysicalAttribute> attributes
class PhysicalAttributes
int id
string description
string type
List<Human> humans
When I add the first human with attributes, the tables are created and the data is populated properly.
The problem is when I add the next humans with similar attributes. Let's say I have attributes
type:"body"
description:"slim"
for both the first and second human. When I create new and add the second human, another entry with the same type and description is added to the PhysicalAttributes table.
Is there some way of doing this so that the existing entry will be used?
Do I have to do a lookup on the PhysicalAttributes table first to see that the entry has been created?
Is there some way of doing this so that the existing entry will be used?
Yes. Make (type,description) the Entity Key for PhysicalAttributes. You seem to have introduced a meaningless ID key property that allows multiple PhysicalAttributes to have the same type and description.
It is also possible to not fix the model, and fetch the existing PhysicalAttributes from the database to discover if any of the ones you need already exist. But that's just a tedious workaround for having the wrong entity key structure.
If you are loading from JSON it's going to be inconventient to attach existing entities, and instead you can do the following.
Override SaveChanges and fetch all the PhyscialAttribute values from the database. Then for the PhysicalAttribute entities that already exist in the database, Detach them from the DbContext, and EF won't attempt to insert them.
After you call PhysicalAttributes.Load(), you will have two entries in the ChangeTracker for every PhysicalAttribute on a new Entity that already exists in the database.
EG
public override int SaveChanges()
{
PhysicalAttributes.Load();
var entryGroup = from entry in ChangeTracker.Entries<PhysicalAttribute>()
group entry by new { entry.Entity.Description, entry.Entity.Type } into byKey
where byKey.Any( e => e.State == EntityState.Unchanged)
select byKey;
foreach (var eg in entryGroup)
{
foreach (var e in eg )
{
if (e.State == EntityState.Added)
{
e.State = EntityState.Detached;
}
}
}
return base.SaveChanges();
}
I'm trying to delete multiple rows from a table. But it gives the following error after the first iteration. I can see primary key Id as 0 on all the xTestType object. that might be the issue. Why is it always giving Id 0.
foreach (var temp in oldxDetails.TestTypes)
{
if (deleteTestTypes.Contains(input.Id))
{
var xTestType = new xTestType
{
xId = xId,
TestTypeMasterId = temp.Id
};
await _xTestRepository.DeleteAsync(xTestType);
}
}
Exception:
The instance of entity type 'xTestType' cannot be tracked because another instance of this type with the same key is already being tracked. When adding new entities, for most key types a unique temporary key value will be created if no key is set (i.e. if the key property is assigned the default value for its type). If you are explicitly setting key values for new entities, ensure they do not collide with existing entities or temporary values generated for other new entities. When attaching existing entities, ensure that only one entity instance with a given key value is attached to the context.
When you fetch data from database, and iterate over it, like:
var dataObject = await dbContext.Table.where(x=>x.UserId == model.UserId).TolistAsync();
foreach(var item in dataObject)
{
}
do not create another object, pass the fetched object directly to Delete or use it to update, because DbContext is tracking the objects it has fetched, not the ones you create. for example:
//Wrong code
var dataObject = await dbContext.Table.where(x=>x.UserId == model.UserId).TolistAsync();
foreach(var item in dataObject)
{
var x=new DataObject()
{
x=item.Id
};
dbContext.Table.Remove(x);
}
you must pass the originally fetched instance to Remove() method, see:
var dataObject = await dbContext.Table.where(x=>x.UserId == model.UserId).TolistAsync();
foreach(var item in dataObject)
{
dbContext.Table.Remove(item);
}
The issue exists because the entity framework is tracking xTestType when you fetched all of them. There are two approaches to handle the situation.
Approach 1:
DbContext.Entry(xTestTypeOld).State = EntityState.Deleted; // where xTestTypeOldis record from which you are taking xId
Approach 2 :
DbContext.Entry(xTestTypeOld).State = EntityState.Detached;
DbContext.Entry(xTestType).State = EntityState.Deleted;
I would say the first approach is the best one.
C# rookie. Below is my code, been trying for hours now to get this to update some fields in my DB and tried many different implementations without luck.
// Select all fields to update
using (var db = new Entities())
{
// dbFields are trusted values
var query = db.tblRecords
.Where("id == " + f.id)
.Select("new(" + string.Join(",", dbFields.Keys) + ")");
foreach (var item in query)
{
foreach (PropertyInfo property in query.ElementType.GetProperties())
{
if (dbFields.ContainsKey(property.Name))
{
// Set the value to view in debugger - should be dynamic cast eventually
var value = Convert.ToInt16(dbFields[property.Name]);
property.SetValue(item, value);
// Something like this throws error 'Object does not match target type'
// property.SetValue(query, item);
}
}
}
db.SaveChanges();
}
The above code when run does not result in any changes to the DB. Obviously this code needs a bit of cleanup but i'm trying to get the basic functionality working. I believe what I might need to do is to somehow reapply 'item' back into 'query' but I've had no luck getting that to work no matter what implementation I try i'm always receiving 'Object does not match target type'.
This semi similar issue reaffirms that but isn't very clear to me since i'm using a Dynamic LINQ query and cannot just reference the property names directly. https://stackoverflow.com/a/25898203/3333134
Entity Framework will perform updates for you on entities, not on custom results. Your tblRecords holds many entities, and this is what you want to manipulate if you want Entity Framework to help. Remove your projection (the call to Select) and the query will return the objects directly (with too many columns, yes, but we'll cover that later).
The dynamic update is performed the same way any other dynamic assignment in C# would be, since you got a normal object to work with. Entity Framework will track the changes you make and, upon calling SaveChanges, will generate and execute the corresponding SQL queries.
However, if you want to optimize and stop selecting and creating all the values in memory in the first place, even those that aren't needed, you could also perform the update from memory. If you create an object of the right type by yourself and assign the right ID, you can then use the Attach() method to add it to the current context. From that point on, any changes will be recorded by Entity Framework, and when you call SaveChanges, everything should be sent to the database :
// Select all fields to update
using (var db = new Entities())
{
// Assuming the entity contained in tblRecords is named "ObjRecord"
// Also assuming that the entity has a key named "id"
var objToUpdate = new ObjRecord { id = f.id };
// Any changes made to the object so far won't be considered by EF
// Attach the object to the context
db.tblRecords.Attach(objToUpdate);
// EF now tracks the object, any new changes will be applied
foreach (PropertyInfo property in typeof(ObjRecord).GetProperties())
{
if (dbFields.ContainsKey(property.Name))
{
// Set the value to view in debugger - should be dynamic cast eventually
var value = Convert.ToInt16(dbFields[property.Name]);
property.SetValue(objToUpdate, value);
}
}
// Will only perform an UPDATE query, no SELECT at all
db.SaveChanges();
}
When you do a SELECT NEW ... it selects only specific fields and won't track updates for you. I think if you change your query to be this it will work:
var query = db.tblRecords.Where(x=>x.id == id);
OK I can delete a single item in EF6 like this:
public void DeleteUserGroup(MY_GROUPS ug)
{
using (var context = new MYConn())
{
var entry = context.Entry(ug);
if (entry.State == EntityState.Detached)
{
context.MY_GROUPS.Attach(ug);
}
context.MY_GROUPS.Remove(ug);
context.SaveChanges();
}
}
If this method changed from passing a single instance of MY_GROUPS to a List<MY_GROUPS> how would I handle the delete?
Would there be a more efficient way then just doing a foreach and setting the state one at a time?
UPDATE:
I am already using a similar method as above utilizing the RemoveRange method.
However I am getting an error:
The object cannot be deleted because it was not found in the
ObjectStateManager.
I'm looking for the best way to attach a list of objects to the context so that I can delete them.
To be able to remove records, you need to make sure your ObjectContext is tracking them. Right now you have detached objects, and your context has no knowledge of them so it's impossible to delete them. One way to remove them is to do like you say, Attach all your objects to the context, then delete them. The other way is to fetch the records from the database so you can remove them:
//Find all groups in database with an Id that is in your group collection 'ug'
var groups = context.My_Groups.Where(g => ug.Any(u => u.Id == g.Id));
context.My_Groups.RemoveRange(groups);
context.SaveChanges();
However, note that even while using RemoveRange, a delete command will be send to the database per item you want to remove. The only difference between RemoveRange and Remove is that the first will only call DetectChanges once, which can really improve performance.
Iterate over your collection and set Deleted state for each
groups.ForEach(group => ctx.Entry(group).State = EntityState.Deleted);
ctx.SaveChanges();
You can use RemoveRange:
context.MY_GROUPS.RemoveRange(context.MY_GROUPS.Where(x => x.columnName== "Foo"));
You can also use ForEach like this:
context.MY_GROUPS.Where(x => x.columnName == "Foo").ToList().ForEach(context.DeleteObject);
context.SaveChanges();
You could also use ObjectContext.ExecuteStoreCommand Method as an another approach for this purpose.
I found this it worked for me. I did it in a loop before calling save changes. I wanted it to create just the delete sql command and it did.
http://www.entityframeworktutorial.net/entityframework6/delete-disconnected-entity-in-entity-framework.aspx
// disconnected entity to be deleted
var student = new Student(){ StudentId = 1 };
using (var context = new SchoolDBEntities())
{
context.Entry(student).State = System.Data.Entity.EntityState.Deleted;
context.SaveChanges();
}
EntityFramework 6+ has made this a bit easier with .RemoveRange().
public void DeleteUserGroup(List<MY_GROUPS> ug)
{
using (var context = new MYConn())
{
context.MY_GROUPS.RemoveRange(ug);
context.SaveChanges();
}
}
By using the solution provided by Alexander Deck where brands is an IEnumerable:
context.Brands.RemoveRange(context.Brands.Where(cb => brands.Any(b => b.Id == cb.Id)));
I got the following error:
Unable to create a constant value of type 'SomeCompany.SomeApp.DB.Entities.Brand'. Only primitive types or enumeration types are supported in this context.
And it was solved by converting the Brands DBSet to IEnumerable with the AsEnumerable() extension method:
context.Brands.RemoveRange(context.Brands.AsEnumerable().Where(cb => brands.Any(b => b.Id == cb.Id)));
That did the trick for me.
I have already read many posts about the entity framework problem with many to many and its beeing a pain in the neck again.
Colaborador has an ICollection of Time and
Time has an Icollection of Colaborador
Some people say that it´s necessary to attach the child entity before Add in the context(didn´t work for me, Pk error).
I am using simple injector and my context is per request.
My associative table is mapped like this:
HasMany<Time>(c => c.Times)
.WithMany(t => t.Colaboradores)
.Map(ct =>
{
ct.MapLeftKey("ColaboradorId");
ct.MapRightKey("TimeId");
ct.ToTable("Receptor");
});
It creates the associative table in the database.
When i try to insert a Colaborador(entity), i add in its list some Times(Teams), add to DbContext and then SaveChanges().
When i do this, it creates a new Colaborador, insert correctly in the associative table(the ids) but also duplicate the Time.
var colaborador = Mapper.Map<ColaboradorViewModel, Colaborador>(colaboradorVm);
List<TimeViewModel> timesVm = new List<TimeViewModel>();
colaboradorVm.TimesSelecionados.ForEach(t => timesVm.Add(_serviceTime.BuscarPorId(t)));
colaborador.Times = Mapper.Map<ICollection<TimeViewModel>, ICollection<Time>>(timesVm);
The function BuscarPorId does the Find method and returns a Time.
I have figured out that if i call the Add command, the entity will mark the child´s state as Added as well, but if i attempt to attach the Time or change it´s state to Unchanged, i get a primary key error...
foreach (var item in colaborador.Times)
{
lpcContext.Set<Time>().Attach(item);
//lpcContext.Entry(item).State = EntityState.Unchanged;
}
Is there any way of tell to entity framework to not insert a specific child? So only the main and associative table are populated?
Mapper.Map creates new Time objects which are not attached to your context, so you must attach them as Unmodified, but attaching them causes another error due to duplicate PK because your context is already tracking the original copies of the Time entities. Using the Find method will retrieve these tracked and locally cached entities.
Find and use the entity already attached to your context:
Instead of:
colaborador.Times = Mapper.Map<ICollection<TimeViewModel>, ICollection<Time>>(timesVm);
Use:
var times = new List<Time>();
var dbSet = lpcContext.Set<Time>();
foreach( var t in timesVm )
{
var time = dbSet.Find( t.Id );
if( null == time )
{
time = Mapper.Map<TimeViewModel, Time>( t );
}
times.Add( time );
}
collaborador.Times = times;