I need some more understanding of how Entity Framework works. I have implemented code which is running ok and doing the job.
But I need to know that it is the good way or not.
I have a table with 8 columns, say
Table1
Column1 (pk)
Column2, Column3, Column4, Column5, Column6, Column7, Column8
Now when I click a button, I need to insert (if new) or update (for existing record) first 6 columns.
And on the same button click event, as part of the same process, I will call a stored procedure (with a parameter of primary key id, Column1) that will fetch these 6 columns values in the stored procedure itself and will do some calculations based on six column values and return two new values which I need to update in Column7, Column8.
So, process will be:
New record: insert (six columns data), calculations (call stored procedure), update (last 2 column)
Existing record: update (six columns data), calculations (call stored procedure), update(last 2 column)
Now, for insert, I use
_dbContext.Table1.Add(entity);
_dbContext.SaveChanges();
For existing record update (of first 6 columns), I use
//code - Entity property values are updated with new ones
_dbContext.Table1.Attach(entity);
_dbContext.Entry(entity).State = EntityState.Modified;
_dbContext.SaveChanges();
For last update, Column7, Column8, I use
var entity = GetById(Id);
if (entity != null)
{
entity.Column7 = value1;
_dbContext.Entry(entity).Property(t => t.Column7).IsModified = true;
entity.Column8 = value2;
_dbContext.Entry(entity).Property(t => t.Column8).IsModified = true;
_dbContext.SaveChanges();
}
I am not sure why I need not to attach entity for last update. Is it due to I have called GetById method for same table just above that?
Without attaching entity how it updates columns? (if I attach entity it gives error saying already being tracked)
Also, I have to call GetById multiple times to get record for both updates (in existing record scenario). Any other solution for that?
for that you can use MapToModel method
here is example
//code - Entity property values are updated with new ones
var newEntity= new Entity
{
Id = p.Id, // the Id you want to update
Column1= "" // put value for column/s that you need to update
};
newEntity.MapToModel(oldEntity);
_dbContext.SaveChanges();
IMHO I don't think you need to specify IsModified for each property if you fetch through the Context.
EF Context will automatically track Entities unless you change the default behavior
EF knows which entities are changed after loading from the database,hence it will be only updated.
Assuming GetById is fetching the data from data-store.
var entity = GetById(Id);
if (entity != null)
{
entity.Column7 = value1;
entity.Column8 = value2;
_dbContext.SaveChanges();
}
Detect Changes
Similar Question
Related
I am using EF Core v2.2 for inserting and updating rows into a table.
While I am doing that, I am looping based on a list and trying to insert into the table. For the 1st row insert the code works as expected.
However from the second item in the list the row doesn't get inserted into the table.
When I debugged, I found out that after the _rmsContext.save() the 2nd inserts the primary key value for the table is generated as -9223372036854774802.
Here is my code
foreach (string account in accntList)
{
var crossBowAccounts = _cbContext.SomeTableRepository
.FindBy(ca => ca.AccountId == account &&
ca.ClientId == model.ClientId)
.FirstOrDefault();
AccountHistory accountHistory = new AccountHistory()
{
AccountId = account,
ClientId = model.ClientId,
UserId = userId,
ActionName = crossBowAccounts == null ? "Add" : "Edit"
};
_rmsContext.AccountHistoryRepository.Add(accountHistory);
_rmsContext.Save();
// Some more insert/update code for a table in a different context .
_rmsContext.Save();
}
Here model is being passed as an input to the method.
This is what I observed during debugging:
This is my table schema
Looks like you are hitting this issue here: https://github.com/dotnet/efcore/issues/6147
EF Core generates temporary IDs which are then replaced with the real IDs when SaveChanges is called
It's not exactly a bug and it has been changed again in 3.1.
So until the item is inserted, a negative and/or random number makes sense. You need to see why it's not inserted (the negative id is not the reason).
it is not bug, id is generated randomly before call SaveChanges method. If you do debug after SaveChanges, Id will be correct.
i make table for property name that changed and value before and value after
How i can use Change Tracking to store changed in this table?
You can track the operation, the changed columns and the new values by using Change Tracking. However getting the old Value out of Change Tracking is not possible. SQL Server 2016 offers the new feature "Change data capture", which gives you the needed Information about the old value before the update/delete happened ( see https://msdn.microsoft.com/en-us/library/bb933994.aspx ).
If you don't have access to a SQL Server 2016, here is how you can configure Change Tracking:
Activate at Database
ALTER DATABASE <YourDatabase> f.e. DeviceDatabase
SET CHANGE_TRACKING = ON
(CHANGE_RETENTION = 2 DAYS, AUTO_CLEANUP = ON)
Activate Change Tracking for your needed tables
ALTER TABLE <YourTable> f.e. Devices
ENABLE CHANGE_TRACKING
WITH (TRACK_COLUMNS_UPDATED = ON)
Setup a DB Job, which will copy change-information into your custom table every minute,hour,day (what you need)
DECLARE #minversion bigint;
SET #minversion = (SELECT MinVersion = CHANGE_TRACKING_MIN_VALID_VERSION(OBJECT_ID('Devices')) )
SELECT SYS_CHANGE_COLUMNS, e.Id FROM
CHANGETABLE(CHANGES Devices, #minversion) AS C
LEFT OUTER JOIN Devices AS e
ON e.Id = c.Id;
To Get the latest Value of the Changed Column you can try this (but beware of multiple updates of the same row. you only get the latest value).
CHANGE_TRACKING_IS_COLUMN_IN_MASK
(COLUMNPROPERTY(OBJECT_ID('Devices'), 'Id', 'ColumnId')
,c.sys_change_columns)
This will return 1 if Column changed, 0 if not. You can add this for every column of your table and join on value = 1 and then add the value to your query.
Finally, I would just recommend to use Stored Procedures to Update/Insert/Delete on your Tables. In those you can easily insert all information you want to store about the change in your custom table.
If you have SQL Server 2016 tho, try what I mentioned above, eventually.
Actually if you override the SaveChanges() method in your data context class you can access ChangeTracker. This gives you all the entities currently tracked by the context and their EntityState (if they are added/modified/deleted/unchanged etc).
Here you can get the DbEntityEntry class and from that get the entitys current values and/or its previous values if the entity is in the modified state.
public override int SaveChanges()
{
var allTrackedEntities = this.ChangeTracker.Entries().ToList();
return base.SaveChanges();
}
I currently use this method to do some basic auditing of who is doing that to what entity.
I currently have a table as defined as follows:
CREATE TABLE (
ID int identity(1,1) primary key not null,
field_one nvarchar(max) not null
)
I am using Entity framework 5 to insert records into this table. This aspect works and I am inserting records correctly.
field_one is a value based on the current id. For example if I have records where the max ID is 5, the next record would be inserted as (6,ABC6), and then (7, ABC7) and so on.
What I am running into is when the table is empty. The identity is at 7, and so the next record should be (8,ABC8). I Need to get the id of the auto_increment prior to the insert in order for me to apply the calculations I need when the table is empty.
How would I be able to do this since I know that the id is generated after the insert and the property is in my entity gets updated after.
You cannot get the correct identity before you perform SaveChanges. This is due to the fact that it is set by Entity Framework on insert.
However, there is a workaround to your problem.
Consider this:
// wrap in a transaction so that the entire operation
// either succeeds or fails as a whole
using(var scope = new TransactionScope())
using(var context = new DbContext()) {
var item = new Item();
context.Items.Add(item);
context.SaveChanges();
// item.ID now contains the identifier
item.field_one = string.Format("abc{0}", item.ID);
// perform another save.
context.SaveChanges();
// commit transaction
scope.Complete();
}
Yes, there are two calls to the database but there's no other way unless you are ready to go deeper than Entity Framework.
I have 2 entities, let's say, Trip and Activity. The relationship between them is many to many so a join table is created automatically by EF.
Entity Trip attributes:
-Id (PK) Generated by database
-Name
-Description
-Property1
-Property2
-Property3
Entity Activity attributes (this entity contains fixed records -read only-, no records are inserted here on performing inserts):
-Id (PK) Generated by database
-Name
-Description
-Cost
Join table contains 2 columns, that is, the IDs of the above entities, that are primary and foreign keys at the same time.
I have no problems inserting entries which automatically EF creates join table TripActivities and add entries successfully to it. Also entries are added successfully to entity Trip and it leaves unchanged entity Activity.
My problem is on updating entries, for example, - suppose user can modify information related to a trip from the GUI - so I take all the info from this GUI and I perform the following steps to update the existing trip:
Trip trip = Context.Trips.Find(id); // Search for the appropriate trip to update from Id
trip.Name = ObtainNameFromGUI();
trip.Description = ObtainDescriptionFromGUI();
trip.Property1 = ObtainProperty1FromGUI();
trip.Property2 = ObtainProperty2FromGUI();
trip.Property3 = ObtainProperty3FromGUI();
trip.Activities = new List<Activity>();
// From the GUI user selects from a checkbox list the activities associated to the trip
// So we read its Ids and from those ids we fetch from database the activities to obtain
// the info related to each activity selected in the GUI. This is all done inside the
// below method.
List<Activity> activities = this.ObtainActivitiesSelectedFromGUI();
// If no activites selected (=null) I want EF automatically deletes the entries in the
// joined table for this trip. And of course, if there are activities selected, EF
// should update the respectives entries in the joined table for this trip with the new
// ones.
if (activites != null)
{
activities.ForEach(a =>
{
trip.Activities.Add(a);
});
}
context.Trips.Add(trip);
context.SaveChanges();<br><br>
By doing this I want EF updates all the entities related (except Activity as it has fixed entries, must be kept unchanged), that is, Trip and the joined table automatically but it does not work: a new trip is created and more entries in the joined table (The only thing that is working is that entity Activity is kept unchanged as I want).
How to achieve this? I have spent a lot of hours trying to do this but without success...
Thanks in advance.
EDIT:
I have removed line:
context.Trips.Add(trip);
Now the results are:
-Entity Trip is correctly updated, no new records added which is Ok.
-Entity Activity is kept unchanged which is Ok.
-Join table: The old records for current trip being updated are not updated, instead new records are inserted for the current trip which is not correct.
I have used a different approach for similar scenario that I faced, which works well with Detached Entities. What I ended up was finding out which entities were added and which ones deleted by comparing GUI(detached entity) values to the database values. Here is the sample code that I have used. The entities in play are RelayConfig and StandardContact which have many to many relationship
public void Update(RelayConfig relayConfig, List<StandardContact> exposedContacts) {
RelayConfig dbRelayConfig = context.RelayConfigs.Include(r => r.StandardContacts)
.Where(r => r.Id == relayConfig.Id).SingleOrDefault();
context.Entry<RelayConfig> (dbRelayConfig).CurrentValues.SetValues(relayConfig);
List<StandardContact> addedExposedContacts =
exposedContacts.Where(c1 => !dbRelayConfig.StandardContacts.Any(c2 => c1.Id == c2.Id)).ToList();
List<StandardContact> deletedExposedContacts =
dbRelayConfig.StandardContacts.Where(c1 => !exposedContacts.Any(c2 => c2.Id == c1.Id)).ToList();
StandardContact dbExposedContact = null;
addedExposedContacts.ForEach(exposedContact => {
dbExposedContact = context.StandardContacts.SingleOrDefault(sc => sc.Id == exposedContact.Id);
dbRelayConfig.StandardContacts.Add(dbExposedContact);
});
deletedExposedContacts.ForEach(exposedContact => { dbRelayConfig.StandardContacts.Remove(exposedContact);});
You will use something like this. Assuming that you will get the related objects from the UI and just you are going to update the same in the database, some thing like the following will work.
context.Products.Attach(product);
context.ObjectStateManager.ChangeObjectState(product, System.Data.EntityState.Modified);
context.ObjectStateManager.ChangeObjectState(product.ProductDescription, System.Data.EntityState.Modified);
context.ObjectStateManager.ChangeObjectState(product.ProductModel, System.Data.EntityState.Modified);
context.SaveChanges();
As you may see here, we are setting the EntityState as Modified which hints EF to perform update for the related tables too.
Please post back your queries or any issues that you may encounter in this implementation.
I have a WPF DataGrid bound to some entities (Entity Framework 4+).
User then edits the DataGrid and presses SAVE. Data is then saved back to MS SQL Server 2008 using SaveChanges(). Well... now, I would like to have AUTOMATIC timestamp which stores the time of the last change and updates itself automatically.
Guys, is it possible? How?
Thank you, James
The easiest solution to achieve this is on the database level:
Create a new column in the table for storing your DateTime value.
Create database trigger to set the column for each insert or update to the table.
Map the new column as property in your entity
Set StoreGeneratedPattern for the new property to Computed so the value generated in the database is correctly updated to your attached entity after each insert or update.
If you don't like the trigger you must do it manually in overriden SaveChanges where you find all entities which will be inserted or updated and set the column:
public override int SaveChanges(SaveOptions options)
{
var entities = ObjectStateManger.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
.Select(e => e.Entity)
.OfType<YourEntityType>();
DateTime now = DateTime.Now;
foreach(var entity in entities)
{
entity.Updated = now;
}
return base.SaveChanges(options);
}