Getting the Id of the newly added entity without calling SaveChanges? - entity-framework

Is there a way to get the Id of the newly added entity without calling SaveChanges?
For e.g. in the following example, if I comment out the call to the context.SaveChangesAsync() as shown below, the value of FooId inserted into the newBar record will be zero.
var newFoo = new Foo() { ...};
context.Foos.Add( );
// await context.SaveChangesAsync();
new Bar() { FooId = newFoo.Id, ... }
context.Bars.Add( );
await context.SaveChangesAsync();
transaction.Commit();
I am using Entity Framework 6.2.0 targeting .NET Framework 4.6.1.

Related

Execute SP using Entity Framework and .net Core

I created a web api project using .net core and entity framework.
This uses a stored procedure which returns back most of the properties of a database table defined by entity framework.
The entity framwrok does not bring back all the columns of the table. And I get an error when I call the api complaining it cannot find the missing columns when I execute the stored procedure using ,
_context.Set<TableFromSql>().FromSql("execute dbo.spr_GetValue").ToList();
I created another model class which defines the properties brought back from the SP( called NewClass).
_context.Set<NewClass>().FromSql("execute dbo.spr_GetValue").ToList();
This works, but just wanted to check if there is a convention that the SP should only return the model classes from the database.
The SQL query must return data for all properties of the entity or query type
For this limitation, it is caused when mapping the sql query result to Model. It loop through the properties in model and try to retrive the values from query result. If the model properties are not exist in query result, it will throw error.
If you want to return required columns instead of all columns, one options is to define the returned model by Query.
For your demo code, you may define this in OnModelCreating.
builder.Query<TableFromSql>();
Note, for this way, you need to make sure all properties in TableFromSql exist in execute dbo.spr_GetValue.
For another way, you may implement your own FromSql which will add condition to check whether the properties are exist in query result.
public static class DbContextExtensions
{
public static List<T> RawSqlQuery<T>(this DbContext context,string query)
{
using (var command = context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
context.Database.OpenConnection();
using (var result = command.ExecuteReader())
{
var entities = new List<T>();
return DataReaderMapToList<T>(result);
}
}
}
public static List<T> DataReaderMapToList<T>(IDataReader dr)
{
List<T> list = new List<T>();
T obj = default(T);
while (dr.Read())
{
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties())
{
if (ColumnExists(dr, prop.Name))
{
if (!object.Equals(dr[prop.Name], DBNull.Value))
{
prop.SetValue(obj, dr[prop.Name], null);
}
}
}
list.Add(obj);
}
return list;
}
public static bool ColumnExists(IDataReader reader, string columnName)
{
return reader.GetSchemaTable()
.Rows
.OfType<DataRow>()
.Any(row => row["ColumnName"].ToString() == columnName);
}
}
Use above code like :
var result = _context.RawSqlQuery<ToDoItemVM>("execute [dbo].[get_TodoItem]");

Entity Framework - Update entity but not child entity

In my EF 6 MVC app, I have an entity Seller which has a 1:1 relationship to SellerShippingPolicies. When I update the seller entity, EF is also attempting to update the SellerShippingPolicies entity, and I don't want this to happen.
I have the following method that updates a Seller entity:
public Entities.Seller Save(Entities.Seller seller)
{
// Instantiate a helper method
HelperMethods helper = new HelperMethods(this.UnitOfWork);
// Map the domain entity to an EF entity
var sellerRecord = Mapper.Map<Seller>(seller);
// Attempt to prevent the updating of the SellerShippingPolicies entity
helper.GetDbContext().Entry(sellerRecord.SellerShippingPolicies).State = EntityState.Detached;
// Save the entity
sellerRecord = helper.SaveItem<Seller>(sellerRecord);
}
Here is the SaveItem method that gets called:
public T SaveItem(T entity)
{
var row = this._dbSet.Find(GetPrimaryKeyValue(entity));
if ( row == null )
return AddItem(entity);
else
return UpdateItem(entity);
}
And the Update method that eventually gets called:
public T UpdateItem(T entity)
{
// Retrieve the current copy of the entity to be updated.
var currentEntity = GetItem(GetPrimaryKeyValue(entity));
// Copy the contents of the modified entity on top of the copy we just retrieved. This way EF will save everything correctly.
currentEntity = Copy.ShallowCopy<T>(entity, currentEntity);
this._dbContext.SaveChanges();
return currentEntity;
}
Not sure it's necessary, but here is the method for ShallowCopy and GetItem.
public static T ShallowCopy<T>(object source, T target)
{
foreach (PropertyInfo pi in typeof(T).GetProperties())
{
var property = source.GetType().GetProperty(pi.Name);
if (property == null)
continue;
if (property.GetSetMethod() != null)
property.SetValue(target, pi.GetValue(source, null), null);
}
return target;
}
public T GetItem(object primaryKeyValue)
{
return this._dbSet.Find(primaryKeyValue);
}
All these methods share the same context object.
You can see that I'm attempting to prevent the updating of the SellerShippingPolicies entity by setting its state to detached. This does not work. I've also tried setting the state to Unchanged. That doesn't work either. In both cases, EF attempts to update the SellerShippingPolicies entity. What am I missing?

Clear related items followed by parent entity update - Many to Many EntityFramework with AutoMapper

I am trying to remove all references followed by adding them back from a list of disconnected objects.
using(var scope = new TransactionScope())
{
_autoIncidentService.AddNewCompanyVehicles(
autoIncidentModel.CompanyVehiclesInvolved.Where(v => v.Id == 0));
_autoIncidentService.ClearCollections(autoIncidentModel.Id);
_autoIncidentService.Update(autoIncidentModel);
scope.Complete();
}
return Json(ResponseView);
The ClearCollections removes items references. The GetAutoIncident includes the collection.
public void ClearCollections(int id)
{
var autoIncident = GetAutoIncident(id);
foreach (var vehicle in autoIncident.CompanyVehiclesInvolved.ToArray())
{
autoIncident.CompanyVehiclesInvolved.Remove(vehicle);
}
db.SaveChanges();
}
When I try to update the entity right after the ClearCollections method it fails.
The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.
I am using a singleton to get the DbContext so there shouldn't be any situation where the context is different. It is being stored in the HttpContext.Current.Items.
The update method is as follows:
public override void Update(AutoIncidentModel model)
{
var data = GetData(model.Id);
Mapper.CreateMap<AutoIncidentModel, AutoIncident>()
.ForMember(m => m.CompanyVehiclesInvolved, opt => opt.ResolveUsing(m =>
{
var ids = m.CompanyVehiclesInvolved.Select(v => v.Id);
return db.Vehicles.Where(v => ids.Contains(v.Id)).ToList();
}));
Mapper.Map(model, data);
db.SaveChanges();
}
Obviously, I am missing something important here. Do the entites from my ResolveUsing method need to somehow be associated with the parent entity or is automapper overwriting the property (CompanyVehiclesInvolved) and causing a problem?

How to update not every fields of an object using Entity Framework and EntityState.Modified

I need to update all fields except property1 and property2 for the given entity object.
Having this code:
[HttpPost]
public ActionResult Add(object obj)
{
if (ModelState.IsValid)
{
context.Entry(obj).State = System.Data.EntityState.Modified;
context.SaveChanges();
}
return View(obj);
}
How to change it to add an exception to obj.property1 and obj.property2 for not being updated with this code?
Let's assume that you have a collection of the properties to be excluded:
var excluded = new[] { "property1", "property2" };
With EF5 on .NET 4.5 you can do this:
var entry = context.Entry(obj);
entry.State = EntityState.Modified;
foreach (var name in excluded)
{
entry.Property(name).IsModified = false;
}
This uses a new feature of EF5 on .NET 4.5 which allows a property to be set as not modified even after it has been previously set to modified.
When using EF 4.3.1 or EF5 on .NET 4 you can do this instead:
var entry = context.Entry(obj);
foreach (var name in entry.CurrentValues.PropertyNames.Except(excluded))
{
entry.Property(name).IsModified = true;
}
You can't define such an exception. You can however mark single properties as modified:
context.Entry(obj).Property(o => o.Property3).IsModified = true;
context.Entry(obj).Property(o => o.Property4).IsModified = true;
// etc.
Note that setting IsModified to false is not supported once you have marked the state of the whole entity to Modified.
For your purpose I would actually prefer to load the entity from the database and then update it using normal change tracking:
var objInDB = context.Objects.Single(o => o.Id == obj.Id);
obj.Property1 = objInDB.Property1;
obj.Property2 = objInDB.Property2;
context.Entry(objInDB).CurrentValues.SetValues(obj);
context.SaveChanges();
Note that only changed properties will be saved by default by Automatic Detect changes.
See EF 6 and EF Core articles
This question was already nicely answered, but I wanted to provide an extension method for anyone who would like to use it.
This code was developed for EF 4.3.1
//You will need to import/use these namespaces
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
//Update an entity object's specified columns, comma separated
//This method assumes you already have a context open/initialized
public static void Update<T>(this DbContext context, T entityObject, params string[] properties) where T : class
{
context.Set<T>().Attach(entityObject);
var entry = context.Entry(entityObject);
foreach(string name in properties)
entry.Property(name).IsModified = true;
context.SaveChanges();
}
Usage Example
using (FooEntities context = new FooEntities())
{
FooEntity ef = new FooEntity();
//For argument's sake say this entity has 4 columns:
// FooID (PK), BarID (FK), Name, Age, CreatedBy, CreatedOn
//Mock changes
ef.FooID = 1;
ef.Name = "Billy";
ef.Age = 85;
context.Update<FooEntity>(ef, "Name", "Age"); //I only want to update Name and Age
}
This is an update that works for .net CORE and maybe can help someone who needs a generic solucion and wants to exclude some properties base on different conditions.
I'm using reflection to iterate through the properties and update base on its property value, in this case, as example, i'm excluding the null properties.
public virtual TEntity Update(TEntity entity)
{
dbSet.Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
var entry = dbContext.Entry(entity);
Type type = typeof(TEntity);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(entity, null) == null)
{
entry.Property(property.Name).IsModified = false;
}
}
dbContext.SaveChanges();
return entity;
}
The answers above (most of them) use DbContext. For those who is using ObjectContext these solutions arent accessible.
Here is solution for ObjectContext strictly (EF5 .NET 4.5):
ctx.AddObject("ENTITYNAME", item);
ctx.ObjectStateManager.ChangeObjectState(item, EntityState.Modified);
var entry = ctx.ObjectStateManager.GetObjectStateEntry(item);
entry.RejectPropertyChanges("PROPERTY_TO_EXCLUDE");

Requiring an Id from SaveChanges, whilst saving? Entity Framework

I am inserting a record into the database, which looks like this:
class Transaction
{
int Id;
}
What I want, is when I insert this object, I want to create another record, like this:
class TransactionUpdate
{
int StartingTransactionId;
int EndingTransactionId;
}
What I have so far, is a loop in my SaveChanges on the DbContext, which takes new Transaction objects that will be created and creates TransationUpdate objects and attaches these to the DbContext.
public override int SaveChanges()
{
foreach(var entry in this.ChangeTracker.Entries())
{
if(entry.Entity is Transaction)
{
var update = new TransactionUpdate();
update.StartingTransactionId = ((Transaction)entry.Entity).PreviousTransactionId;
update.EndingTransactionId = ((Transaction)entry.Entity).Id; // This is zero because the entity has not been inserted.
this.TransactionUpdates.Add(update);
}
}
}
The problem is, I cannot properly create a TransactionUpdate because I do not have 'EndingTransactionId', or, the Id of the Transaction I am currently inserting.
How can I solve this problem?
Many Thanks.
SOLVED
I have done what Ladislav suggested and am now creating a list of items to add, along with references to the objects that are required to insert them. Thus:
public override int SaveChanges()
{
var transactionUpdatesToAdd = new List<Tuple<TransactionUpdate, Transaction>>();
foreach (var entry in this.ChangeTracker.Entries<Transaction>())
{
if (entry.State == EntityState.Added)
{
var update = new TransactionUpdate();
update.StartingTransactionId = ((Transaction)entry.Entity).PreviousTransactionId;
transactionUpdatesToAdd.Add(new Tuple<TransactionUpdate, Transaction>(update, entry.Entity));
}
}
using(var scope = new TransactionScope())
{
// Save new Transactions
base.SaveChanges();
// Update TransactionUpdates with new IDs
foreach (var updateData in transactionUpdatesToAdd)
{
updateData.Item1.EndingTransactionId = updateData.Item2.Id;
this.TransactionUpdates.Add(updateData.Item1);
}
// Insert the new TransactionUpdate entities.
return base.SaveChanges();
}
Based on your description I guess you are using autogenerated Id in database. You will not receive this Id befere executing SaveChanges on the context. You have to divide operation into two separate modifications:
public override int SaveChanges()
{
// call base context saving operation to insert all Transactions
base.SaveChanges();
foreach(var entry in this.ChangeTracker.Entries())
{
if(entry.Entity is Transaction)
{
var update = new TransactionUpdate();
update.StartingTransactionId = ((Transaction)entry.Entity).PreviousTransactionId;
update.EndingTransactionId = ((Transaction)entry.Entity).Id;
this.TransactionUpdates.Add(update);
}
}
// save changes again to insert all TransactionUpdates
base.SaveChanges();
}
You should wrap it into TransactionScope to perform whole saving as atomic operation.
If you haven't inserted TransactionId, you have it anyway in your object. Pass your object as parameter to an overloaded method SaveChanges and use it to pass the Id