I want to log new and modified records. This code works just fine for Modified Records.
But with Added records, there is an issue. Since it is new to the Database, there is not yet a primary key for it. So there is no way to log which record was added.
However, if I try to log the records after the save, the EntityState is no longer Added. So I don't know what was added.
The only solution I have been able to come up with is to save a list of the new records, and then after the save, then Log the changes. But that seems like a workaround.
Is there some way to resolve this?
private List<Event> LogChanges(EntityEntry entityEntry, Enums.TableNames tableName)
{
List<Event> result = new List<Event>();
var databaseValues = entityEntry.GetDatabaseValues();
foreach (var property in entityEntry.CurrentValues.Properties.Where(a=> a.Name !="TenantId"))
{
string original = databaseValues[property]?.ToString();
string current = entityEntry.CurrentValues[property]?.ToString();
if(!object.Equals(original,current))
{
result.Add(
new Event()
{
AppUserId = this._appUserProvider.CurrentAppUserId,
EventDate = DateTimeOffset.UtcNow,
EventTypeId = (int)Enums.EventTypes.Modified,
TenantId = databaseValues.GetValue<int>("TenantId"),
RecordId = databaseValues.GetValue<int>("Id"),
ColumnName = property.Name,
OriginalValue = original,
NewValue = current,
TableId = (int)tableName
});
}
}
return result;
}
This library adds triggers to EntityFrameworkCore. Using the Triggers it provides is a much cleaner way to accomplish the above.
Related
I have an object with several really large string properties. In addition, it has a simple timestamp property.
What I trying to achieve is to update only timestamp property without getting the whole huge object to the server.
Eventually, I would like to use EF and to do in the most performant way something equivalent to this:
update [...]
set [...] = [...]
where [...]
Using the following, you can update a single column:
var yourEntity = new YourEntity() { Id = id, DateProp = dateTime };
using (var db = new MyEfContextName())
{
db.YourEntities.Attach(yourEntity);
db.Entry(yourEntity).Property(x => x.DateProp).IsModified = true;
db.SaveChanges();
}
OK, I managed to handle this. The solution is the same as proposed by Seany84, with the only addition of disabling validation, in order to overcome issue with required fields. Basically, I had to add the following line just before 'SaveChanges():
db.Configuration.ValidateOnSaveEnabled = false;
So, the complete solution is:
var yourEntity = new YourEntity() { Id = id, DateProp = dateTime };
using (var db = new MyEfContextName())
{
db.YourEntities.Attach(yourEntity);
db.Entry(yourEntity).Property(x => x.DateProp).IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
db.SaveChanges();
}
I'm having to write a app that effectively copies data from one databaseA.table to databaseB.table but there are a few fields in databaseB that aren't in databaseA.
I've come up with basic code below. The insert works and the update doesn't trow an error, however, the update doesn't actually update any records.
I've confirmed that the bcEmployee object in the update has the new values from databaseA like it should. The employee object is the record from databaseA.
Am I missing something to make this update?
BC_employee bcEmployee = new BC_employee();
bcEmployee.emp_id = employee.emp_id;
bcEmployee.emp_firstname = employee.emp_firstname;
bcEmployee.emp_lastname = employee.emp_lastname;
using (BCcontext ctx = new BCcontext())
{
var existBCemployee = ctx.employee.Find(employee.emp_id);
if (existBCemployee == null) //Insert
{
//Set default values that aren't in the original database
bcEmployee.emp_paystat = null;
bcEmployee.password = null;
bcEmployee.enroll_date = null;
ctx.employee.Add(bcEmployee);
}
else
{
ctx.Entry(existBCemployee).CurrentValues.SetValues(bcEmployee);
}
ctx.SaveChanges();
}
During the execution of the following piece of code, I get the message
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.
And there are loads of occurrences of that in here, but I didn't find a solution to my problem.
This only happens to me because I'm running _db.SaveChanges() two times inside one transaction (_db is my database context object), and I HAVE to do so because I need the generated ID that it gives me in order to proceed with the code.
If anyone can help me, I'd appreciate it. Also, if you know a way to proceed without the two _db.SaveChanges() or with a different way of approaching the transaction, I welcome you to show it.
using (var trans = _db.Database.BeginTransaction())
{
try
{
var f = cbxFornecedor.SelectedItem as Fornecedor;
var c = new Compra
{
CompraId = compra.CompraId,
DataCompra = dtpDataCompra.Value,
ListaProdutos = new List<ListaProdutos>(),
Fornecedor = f,
Referencia = tbxReferencia.Text,
Situacao = rbtEntregue.Checked
};
_db.Compras.Add(c);
_db.SaveChanges();
foreach (var cada in _itens)
c.ListaProdutos.Add(new ListaProdutos
{
Compra = c,
CompraId = c.CompraId,
Produto = cada.ProdutoClasse,
ProdutoId = cada.ProdutoClasse.ProdutoId,
Valor = cada.ValorTotal,
Quantidade = cada.Quantidade,
});
foreach (var cada in c.ListaProdutos)
if (_db.ListaProdutos.Find(cada.CompraId, cada.ProdutoId) != null)
_db.Entry(cada).State = EntityState.Modified;
else
_db.ListaProdutos.Add(cada);
_db.Entry(c).State = EntityState.Modified;
_db.SaveChanges();
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
MessageBox.Show(this, ex.Message, #"Erro", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
And, just to be clear, I know that my problem is with that fact that the transaction is there. I tried removing it and it worked. Also, if I take out the second _db.SaveChanges(), it works too.
The exception is only thrown in the second _db.SaveChanges(), and only if it is surrounded by the transaction block.
It is not direct answer to your question, but it may be helpful.
You do not need to specify Compra and CompraId for new ListaProdutos because EF smart enough to figure it out. Also it looks like that you do not need _db.ListaProdutos.Find(cada.CompraId, cada.ProdutoId) != null check because cada.CompraId - is new Id and it can not be in DB at this moment. Also you do not need _db.ListaProdutos.Add(cada); because EF already new that you are adding this records. Can you try next code?
var f = cbxFornecedor.SelectedItem as Fornecedor;
var c = new Compra
{
DataCompra = dtpDataCompra.Value,
ListaProdutos = new List<ListaProdutos>(),
Fornecedor = f,
Referencia = tbxReferencia.Text,
Situacao = rbtEntregue.Checked
};
_db.Compras.Add(c);
foreach (var cada in _itens)
c.ListaProdutos.Add(new ListaProdutos
{
Produto = cada.ProdutoClasse,
ProdutoId = cada.ProdutoClasse.ProdutoId,
Valor = cada.ValorTotal,
Quantidade = cada.Quantidade,
});
_db.SaveChanges();
If I have a main table, lets say orders, and a sub table of items and the items table has a fields for item number BUT it also has a nullable (optional) field for color that applied only to certain items. How would I update the items table, at the same time as the orders table, using Entity Framework?
Here is a code example of what I have so far. Two problems, I'm only entering one of my items and, from what my research indicates, I can't add another field to the items table?
foreach (Guid c in AllItems)
{ Items.OrderItemID = Guid.NewGuid();
ITemsOrderID = order.OrderID;
ITems.ItemID = c;
If (ItemID = ItemThatLetsYouChoseAColorID)
{
Items.ItemColorID = ColorID;
} else {
Items.ItemColorID = null;
}
}
context.Orders.AddObject(Orders);
context.Items.AddObject(Items);
context.SaveChanges();
My Orders table gets a record inserted, and the Items gets ONE record inserted. I'm missing something basic here, I'm afraid. BTW, this is Entity Framework 4.0, which. I believe, does not require the use of EntityKey.
You're adding an object to the Items collection only one time after the scope of your foreach.
Have you tested something like:
foreach (Guid c in AllItems)
{
var Item = new Item();
Item.OrderItemID = Guid.NewGuid();
Item.OrderID = order.OrderID;
Item.ItemID = c;
If (ItemID = ItemThatLetsYouChoseAColorID)
{
Item.ItemColorID = ColorID;
}
else
{
Item.ItemColorID = null;
}
context.Items.AddObject(Items);
}
context.Orders.AddObject(order);
context.SaveChanges();
And I'm not sure to understand what you mean by
I can't add another field to the items table
You should be more precise about what you actually expect. Insert a row, add a column in the table...? What is a "field"?
Here is the working code. I had the new Item outside the foreach item loop, so was overwriting the value. Also, I need to add each one to the context. I had a hard time with this, hope it helps someone else:
<-fill the order object->
foreach (Guid i in Items)
{
**Items item = new Items();**
item.ItemID = Guid.NewGuid();
item.OrderID = order.OrderID;
if (i == ItemWithColorGuid)
{
foreach (Guid c in Colors)
{
**Items color = new Items();**
color.ItemsID = Guid.NewGuid();
color.OrderID = order.orderID;
color.itemID = g;
color.colorID = c;
context.item.AddObject(color);
}
}
else
{
item.ItemID = i;
item.ColorID = null;
context.item.AddObject(item);
}
}
context.orders.AddObject(order);
context.SaveChanges();
I'm currently inserting/updating fields like this (if there's a better way, please say so - we're always learning)
public void UpdateChallengeAnswers(List<ChallengeAnswerInfo> model, Decimal field_id, Decimal loggedUserId)
{
JK_ChallengeAnswers o;
foreach (ChallengeAnswerInfo a in model)
{
o = this.FindChallengeAnswerById(a.ChallengeAnswerId);
if (o == null) o = new JK_ChallengeAnswers();
o.answer = FilterString(a.Answer);
o.correct = a.Correct;
o.link_text = "";
o.link_url = "";
o.position = FilterInt(a.Position);
o.updated_user = loggedUserId;
o.updated_date = DateTime.UtcNow;
if (o.challenge_id == 0)
{
// New record
o.challenge_id = field_id; // FK
o.created_user = loggedUserId;
o.created_date = DateTime.UtcNow;
db.JK_ChallengeAnswers.AddObject(o);
}
else
{
// Update record
this.Save();
}
}
this.Save(); // Commit changes
}
As you can see there is 2 times this.Save() (witch invokes db.SaveChanges();)
when Adding we place the new object into a Place Holder with the AddObject method, in other words, the new object is not committed right away and we can place as many objects we want.
But when it's an update, I need to Save first before moving on to the next object, is there a method that I can use in order to, let's say:
if (o.challenge_id == 0)
{
// New record
o.challenge_id = field_id;
o.created_user = loggedUserId;
o.created_date = DateTime.UtcNow;
db.JK_ChallengeAnswers.AddObject(o);
}
else
{
// Update record
db.JK_ChallengeAnswers.RetainObject(o);
}
}
this.Save(); // Only save once when all objects are ready to commit
}
So if there are 5 updates, I don't need to save into the database 5 times, but only once at the end.
Thank you.
Well if you have an object which is attached to the graph, if you modify values of this object, then the entity is marked as Modified.
If you simply do .AddObject, then the entity is marked as Added.
Nothing has happened yet - only staging of the graph.
Then, when you execute SaveChanges(), EF will translate the entries in the OSM to relevant store queries.
Your code looks a bit strange. Have you debugged through (and ran a SQL trace) to see what is actually getting executed? Because i can't see why you need that first .Save, because inline with my above points, since your modifying the entities in the first few lines of the method, an UPDATE statement will most likely always get executed, regardless of the ID.
I suggest you refactor your code to handle new/modified in seperate method. (ideally via a Repository)
Taken from Employee Info Starter Kit, you can consider the code snippet as below:
public void UpdateEmployee(Employee updatedEmployee)
{
//attaching and making ready for parsistance
if (updatedEmployee.EntityState == EntityState.Detached)
_DatabaseContext.Employees.Attach(updatedEmployee);
_DatabaseContext.ObjectStateManager.ChangeObjectState(updatedEmployee, System.Data.EntityState.Modified);
_DatabaseContext.SaveChanges();
}