I try to get the value of entity that stored in DbSet before it was changed by code and before it was saved. However, when I try to get it with LINQ Single statement I get the changed value. I'm using EF7.
Here's the code:
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
ent.FirstName = "New name";
Entity entityBeforeChange = dbSet.Single(x => x.Id == id); //here I want to get entity with old values, if that's important I just need to read it without modifying this instance
Context.SaveChanges();
Hope I was clear enough and can get some help
You can grab the original values of any entity from the ChangeTracker.
var original = Context.ChangeTracker.Entries<Entity>().Single(x => x.Id == id);
var firstName = original.Property<string>("FirstName").OriginalValue;
Here is a code I use from my audit library.
EF7
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
context.ChangeTracker.DetectChanges();
// Find your entry or get all changed entries
var changes = context.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified);
foreach (var objectStateEntry in changes)
{
AuditEntityModified(audit, objectStateEntry, auditState);
}
}
public static void AuditEntityModified(Audit audit, AuditEntry entry, EntityEntry objectStateEntry)
{
foreach (var propertyEntry in objectStateEntry.Metadata.GetProperties())
{
var property = objectStateEntry.Property(propertyEntry.Name);
if (entry.Parent.CurrentOrDefaultConfiguration.IsAudited(entry.ObjectStateEntry, propertyEntry.Name))
{
entry.Properties.Add(new AuditEntryProperty(entry, propertyEntry.Name, property.OriginalValue, property.CurrentValue));
}
}
}
EF6
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
var entry = ((IObjectContextAdapter)ctx).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity);
var currentValues = entry.CurrentValues;
var originalValues = entry.OriginalValues;
AuditEntityModified(originalValues, currentValues);
}
public static void AuditEntityModified(DbDataRecord orginalRecord, DbUpdatableDataRecord currentRecord, string prefix = "")
{
for (var i = 0; i < orginalRecord.FieldCount; i++)
{
var name = orginalRecord.GetName(i);
var originalValue = orginalRecord.GetValue(i);
var currentValue = currentRecord.GetValue(i);
var valueRecord = originalValue as DbDataRecord;
if (valueRecord != null)
{
// Complex Type
AuditEntityModified(valueRecord, currentValue as DbUpdatableDataRecord, string.Concat(prefix, name, "."));
}
else
{
if (!Equals(currentValue, originalValue))
{
// Add modified values
}
}
}
}
Edit:
The complete source code can be found in my GitHub repository: https://github.com/zzzprojects/EntityFramework-Plus
Library Website: http://entityframework-plus.net/
You can Detach an entity from the context. Keep in mind that you'll have to pull it from the context before you update the other, attached entity.
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
Entity entityBeforeChange = dbSet.Single(x => x.Id == id);
Context.Entry(entityBeforeChange).State = EntityState.Detached; // severs the connection to the Context
ent.FirstName = "New name";
Context.SaveChanges();
You could use a new DbContext since the loaded entity is cached in the one you already have.
Entity oldUnchanged;
using (var ctx = new YourDbContext())
{
oldUnchanged = ctx.Set<Entity>().Single(x => x.Id == id);
}
Related
I've got an MVC ASP.Net app using Entity Framework v6.0 with an Employee's table.
We're using a Code First approach with the standard Create (CRUD) method that has a EF lookup for existing employee's and also an EF lookup for employee CreatedBy/ModifiedBy fields. Trying to create mocks for both (EF object stubs) fails.
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create([Bind(Include = "EmployeeID,TeamID,EmployeeADID,FirstName,MiddleName,LastName,EmailAddress,IsAdministrator,IsDeleted")] Employee employee)
{
if (ModelState.IsValid)
{
//Only unique employee IDs
var existingEmployee = db.Employees.FirstOrDefault(o => o.EmployeeADID == employee.EmployeeADID);
if(existingEmployee != null)
{
ViewBag.Error = "Employee ID must be unique, this employee (" + existingEmployee.FullName + ") already exists in the system.";
ViewBag.TeamID = new SelectList(db.Teams, "TeamID", "Name", employee.TeamID);
return View(existingEmployee);
}
SetAuditFields(employee);
db.Employees.Add(employee);
db.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.TeamID = new SelectList(db.Teams, "TeamID", "Name", employee.TeamID);
return View(employee);
}
The problem is the SetAuditFields call and I need to mock db.Employees.AsNoTracking AsNoTracking for the Edit operation.
private void SetAuditFields(Employee employee, bool onlyModified = false)
{
char sep = '\\';
string pID = User.Identity.Name.Split(sep)[1].ToUpper();
var users = db.Employees.AsNoTracking().Where(c => c.EmployeeADID == pID || c.EmployeeID == employee.EmployeeID).ToList();
var currentUser = users.FirstOrDefault(u => u.EmployeeADID == pID);
if (onlyModified)
{
//Notice the AsNoTracking, when you set the db.Entry(object).State = EntityState.Modified; this query wont return anything as its in Modified Mode.
//var originalEmployee = db.Employees.AsNoTracking().FirstOrDefault(o => o.EmployeeID == employee.EmployeeID);
var originalEmployee = users.FirstOrDefault(o => o.EmployeeID == employee.EmployeeID);
employee.CreatedByID = originalEmployee.CreatedByID;
employee.CreatedDate = originalEmployee.CreatedDate;
employee.ModifiedByID = currentUser.EmployeeID;
employee.ModifiedDate = DateTime.Now;
}
else
{
employee.CreatedByID = currentUser.EmployeeID;
employee.CreatedDate = DateTime.Now;
employee.ModifiedByID = currentUser.EmployeeID;
employee.ModifiedDate = DateTime.Now;
}
}
So how do I mock db.Employees.AsNoTracking after initially mocking db.Employees?
The THESE TWO LINES COMMENTED OUT in the code below don't work and fail with:
"The member 'IQueryable.Provider' has not been implemented on type 'DbSet1Proxy' which inherits from 'DbSet1'
I also tried the mockContext.SetupSequence but I need to interchange between with AsNoTracking on and off. Surely there must be something I'm missing?
[TestMethod()]
public void Create_Submit_Test()
{
// Arrange
var employeeDoesntExist = new Employee { EmployeeID = 0, FirstName = "DoesntExist" };
var employeeAdmin = new Employee { EmployeeID=140, FirstName = "Bern", MiddleName = "", LastName = "O", EmployeeADID = "123", EmailAddress = "Bernard.O#a.com", TeamID = 1, IsDeleted = false, IsAdministrator = true };
var employeeNew = new Employee { FirstName = "Jez", MiddleName = "", LastName = "T", EmployeeADID = "321", EmailAddress = "Jeremy.Thompson#a.com", TeamID = 1, IsDeleted = false, IsAdministrator = true };
var mockContext = new Mock<ICTEntities>();
var employeeEmptyMock = base.GetQueryableMockDbSet(employeeDoesntExist);
var employeeAdminMock = base.GetQueryableMockDbSet(employeeAdmin);
//THESE TWO LINES COMMENTED OUT
//mockContext.Setup(m => m.Employees).Returns(employeeEmptyMock);
//mockContext.Setup(m => m.Employees.AsNoTracking()).Returns(employeeAdminMock);
mockContext.SetupSequence(x => x.Employees.AsNoTracking())
.Returns(employeeEmptyMock)
.Returns(employeeAdminMock);
//I dont want to save it to the Database, otherwise next time we run this the object will already exist, so I mock the call
mockContext.Setup(d => d.SaveChanges()).Returns(1);
var controller = new EmployeesController(mockContext.Object);
controller.ControllerContext = base.MockAccess().Object;
// Act
RedirectToRouteResult result = controller.Create(employeeNew) as RedirectToRouteResult;
// Assert
Assert.IsNotNull(result);
Assert.AreEqual("Index", result.RouteValues["Action"]);
}
Here is the GetQueryableMockDbSet method:
protected DbSet<T> GetQueryableMockDbSet<T>(params T[] sourceList) where T : class
{
var queryable = sourceList.AsQueryable();
var dbSet = new Mock<DbSet<T>>();
dbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(queryable.Provider);
dbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(queryable.Expression);
dbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(queryable.ElementType);
dbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(() => queryable.GetEnumerator());
return dbSet.Object;
}
I ended up creating a chain of mocks using a counter as per https://stackoverflow.com/a/14368486/495455
int callCounter = 1;
mockContext.Setup(m => m.Employees)
.Returns(() =>
{
if (callCounter == 1)
{
callCounter++;
return employeeToEditMockCU;
}
else
{
return employeeMockCU;
}
});
Mocking using a SetupSequence doesn't work for me after the first mock. The db.Employee becomes null after the first call. So I dont use a SetupSequence:
mockContext.SetupSequence(x => x.Employees)
.Returns(employeeToEditMockCU)
.Returns(employeeMockCU);
Also to get around the AsNoTracking() I ended up fetching the record to update and saving it without using EntityState.Modified:
EF Update using EntityState.Modified
How to update record using Entity Framework 6?
What is the best way to update multiple records in a list to speed up processing?
Currently, I'm updating about 15000 products, each with 3 different price sets and it takes the whole day to complete.
I need to update the prices all at once in code side, then commit those changes to the database in 1 go, instead of fetching each inventory item, updating its values, then attaching it to the context. Every single fetch is causing the delays.
Code
public void UpdatePricesFromInventoryList(IList<Domain.Tables.Inventory> invList)
{
var db = new UniStockContext();
foreach (var inventory in invList)
{
Domain.Tables.Inventory _inventory = db.Inventories
.Where(x => x.InventoryID == inventory.InventoryID)
.FirstOrDefault();
if (inventory.Cost.HasValue)
_inventory.Cost = inventory.Cost.Value;
else
_inventory.Cost = 0;
foreach (var inventoryPrices in inventory.AccInventoryPrices)
{
foreach (var _inventoryPrices in _inventory.AccInventoryPrices)
{
if (_inventoryPrices.AccInventoryPriceID == inventoryPrices.AccInventoryPriceID)
{
_inventoryPrices.ApplyDiscount = inventoryPrices.ApplyDiscount;
_inventoryPrices.ApplyMarkup = inventoryPrices.ApplyMarkup;
if (inventoryPrices.Price.HasValue)
_inventoryPrices.Price = inventoryPrices.Price.Value;
else
_inventoryPrices.Price = _inventory.Cost;
if (inventoryPrices.OldPrice.HasValue)
{
_inventoryPrices.OldPrice = inventoryPrices.OldPrice;
}
}
}
}
db.Inventories.Attach(_inventory);
db.Entry(_inventory).State = System.Data.Entity.EntityState.Modified;
}
db.SaveChanges();
db.Dispose();
}
I've also tried working my code according to this SOQ Entity Framework update/insert multiple entities
and it gave me and error. Here are the details:
Code:
public void UpdatePricesFromInventoryListBulk(IList<Domain.Tables.Inventory> invList)
{
var accounts = new List<Domain.Tables.Inventory>();
var db = new UniStockContext();
db.Configuration.AutoDetectChangesEnabled = false;
foreach (var inventory in invList)
{
accounts.Add(inventory);
if (accounts.Count % 1000 == 0)
{
db.Set<Domain.Tables.Inventory>().AddRange(accounts);
accounts = new List<Domain.Tables.Inventory>();
db.ChangeTracker.DetectChanges();
db.SaveChanges();
db.Dispose();
db = new UniStockContext();
}
}
db.Set<Domain.Tables.Inventory>().AddRange(accounts);
db.ChangeTracker.DetectChanges();
db.SaveChanges();
db.Dispose();
}
Error:
An entity object cannot be referenced by multiple instances of IEntityChangeTracker.
I would suggest changing the following:
Domain.Tables.Inventory _inventory = db.Inventories
.Where(x => x.InventoryID == inventory.InventoryID)
.FirstOrDefault();
To
Domain.Tables.Inventory _inventory = db.Inventories
.Single(x => x.InventoryID == inventory.InventoryID);
I'd still add the db.Configuration.AutoDetectChangesEnabled = false; after getting the context, and also use AsNoTracking:
Turn off EF change tracking for any instance of the context
that is because you are hit the database context at every loop to increase the performance you should get all the Inventories by one hit ,this is your problem try the below code and you will notice the performance :
public void UpdatePricesFromInventoryList(IList<Domain.Tables.Inventory> invList)
{
var db = new UniStockContext();
invIdsArray = invList.select(x => x.InventoryID).ToArray();
IList<Domain.Tables.Inventory> invListFromDbByOneHit = db.Inventories.Where(x => invIdsArray.Contains(x.InventoryID)).Tolist();
foreach (var inventory in invListFromDbByOneHit)
{
//Domain.Tables.Inventory _inventory = db.Inventories
//.Where(x => x.InventoryID == inventory.InventoryID)
//.FirstOrDefault();
if (inventory.Cost.HasValue)
_inventory.Cost = inventory.Cost.Value;
else
_inventory.Cost = 0;
foreach (var inventoryPrices in inventory.AccInventoryPrices)
{
foreach (var _inventoryPrices in _inventory.AccInventoryPrices)
{
if (_inventoryPrices.AccInventoryPriceID == inventoryPrices.AccInventoryPriceID)
{
_inventoryPrices.ApplyDiscount = inventoryPrices.ApplyDiscount;
_inventoryPrices.ApplyMarkup = inventoryPrices.ApplyMarkup;
if (inventoryPrices.Price.HasValue)
_inventoryPrices.Price = inventoryPrices.Price.Value;
else
_inventoryPrices.Price = _inventory.Cost;
if (inventoryPrices.OldPrice.HasValue)
{
_inventoryPrices.OldPrice = inventoryPrices.OldPrice;
}
}
}
}
db.Inventories.Attach(_inventory);
db.Entry(_inventory).State = System.Data.Entity.EntityState.Modified;
}
db.SaveChanges();
db.Dispose();
}
What is the best way to Update all related Entities when working with Queryfilter?
All my Entities inherit from a BaseClass with "IsDeleted" Property.
When i build my Context i use
if (table.GetProperties().Any(column => column.ClrType == typeof(bool) && column.Name == "IsDeleted"))
{
var parameter = Expression.Parameter(table.ClrType);
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(table.ClrType).HasQueryFilter(lambda);
}
the HasQueryfilter to Ignore all deleted Entity. SoftDelete.
When i try to Update my all Collections, i only get the filterd Collections. So its never possible to Update my IsDelete Property with a true value.
if (entry.State == EntityState.Modified)
{
foreach (var collectionEntry in _context.Entry(dbMember).Collections)
{
await collectionEntry.LoadAsync();
foreach (var o in collectionEntry.CurrentValue)
{
var entity = o as BaseEntity;
entity.IsDeleted = dbMember.IsDeleted;
}
}
}
What i need is something like
await collectionEntry.LoadAsync().IgnoreQueryFilters().
I develop method, which resets cache, when Entity Framework is saving changes. I handle this event in objectContext_SavingChanges
private void objectContext_SavingChanges(object sender, EventArgs e)
{
if (_cacheProvider != null)
{
var objectContext = (this as IObjectContextAdapter).ObjectContext;
var entries =
objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added);
var result = new List<string>();
if (entries != null && entries.Any())
{
foreach (var entry in entries.Where(entry => !entry.IsRelationship))
{
var entity = entries.Select(r => r.Entity).FirstOrDefault();
if (entity != null)
{
var genericTypeName =
typeof(List<>).MakeGenericType(ObjectContext.GetObjectType(entity.GetType()))?.ToString();
_cacheProvider.ResetCache(genericTypeName);
}
}
foreach (var entry in entries.Where(entry => entry.IsRelationship))
{
var set = entry.EntitySet as AssociationSet;
if(set !=null)
{
var firstEntitySet = set.AssociationSetEnds[0].EntitySet;
var secondEntitySet = set.AssociationSetEnds[1].EntitySet;
var firstEntitySetName = firstEntitySet.ElementType.FullName + "," + Assembly.GetExecutingAssembly().FullName;
var secondEntitySetName = secondEntitySet.ElementType.FullName + "," + Assembly.GetExecutingAssembly().FullName;
var firstGenericTypeName =
typeof(List<>).MakeGenericType((Type.GetType(firstEntitySetName)))?.ToString();
var secondGenericTypeName =
typeof(List<>).MakeGenericType((Type.GetType(secondEntitySetName)))?.ToString();
_cacheProvider.ResetCache(firstGenericTypeName);
_cacheProvider.ResetCache(secondGenericTypeName);
}
}
}
}
}
It works great for entity's entries of object state entries, but does not work for association sets. I need to reset cache for both entities on other sides of relation. AssociationSet has "FullName" property but this property does not contain real name of type. It contains short name of type and assembly name, but not contains full namespace in solution.
For example, type has real full name in solution DataAccess.Entities.CachedEntity, namespace: DataAccess.Entities, short name: CachedEntity. But full name in association set will be DataAccess.CachedEntity.
Can I get full name of type for each end of relation?
I decided this task so:
var clrTypeNamespace = #"http://schemas.microsoft.com/ado/2013/11/edm/customannotation:ClrType";
var firstEdmType = set.AssociationSetEnds[0]?.EntitySet?.ElementType;
var secondEdmType = set.AssociationSetEnds[1]?.EntitySet?.ElementType;
var firstType = firstEdmType?.MetadataProperties.SingleOrDefault(p => p.Name == clrTypeNamespace)?.Value as Type;
var secondType = secondEdmType?.MetadataProperties.SingleOrDefault(p => p.Name == clrTypeNamespace)?.Value as Type;
We can use namespace clrTypeNamespace of CLR type in EDM and find real type of entity in MetadataProperties
Also, we can use navigation properies for each relationship's set. But this solution is sutable for situations, when navigation properties exist on both sides of relationship.
var asem = set.AssociationSetEnds[0].CorrespondingAssociationEndMember;
var propInfo = asem.MetadataProperties.SingleOrDefault(p => p.Name == "ClrPropertyInfo")?.Value as PropertyInfo;
I want to load a list of objects from db using Entity Framework 6 and Eager loading. But Entity Framework instead uses lazy loading. I've used SQL profiler and the queries are executed when a property referring to the child entities is accessed.
By using the 'Include' you suppose to load related entities in one go but it's not happening. My Code is presented below:
using (var flightsPricingRulesContext = new FlightsPricingRulesDbContext())
{
flightsPricingRulesContext.Configuration.ValidateOnSaveEnabled = false;
flightsPricingRulesContext.Configuration.AutoDetectChangesEnabled = false;
var filter = PredicateBuilder.True<FlightsPricingRulesDataAccess.Models.ServiceFee>();
if (!String.IsNullOrEmpty(selectedMarketId))
{
var selectedMarket = Int32.Parse(selectedMarketId);
filter = filter.And(sf => sf.MarketId == selectedMarket);
}
if (!String.IsNullOrEmpty(selectedApplicationTypeId))
{
var selectedAppplicationType = Int32.Parse(selectedApplicationTypeId);
filter = filter.And(sf => sf.ApplicationType == selectedAppplicationType);
}
var Query =
from P in flightsPricingRulesContext.ServiceFee.AsExpandable().Where(filter)select P;
switch (orderby)
{
case null:
case "":
case "Id":
Query = String.IsNullOrEmpty(orderbydirection) || orderbydirection == "ASC"
?Query.OrderBy(p => p.Id): Query.OrderByDescending(p => p.Id);
break;
case "market":
Query = String.IsNullOrEmpty(orderbydirection) || orderbydirection == "ASC"
? Query.OrderBy(p => p.MarketId)
: Query.OrderByDescending(p => p.MarketId);
break;
}
var takeitems = 10 ;
var skipitems = (Int32.Parse(page) - 1) * 10);
//BY USING INCLUDE EAGER LOADING IS ENABLED
Query = Query.Skip(skipitems).Take(takeitems).
Include(sf => sf.ServiceFeeZone.Select(sfz => sfz.Zone)).
Include(sf => sf.ServiceFeeCarrier).
Include(sf => sf.ServiceFeeClassOfService).
Include(sf => sf.ServiceFeeDate).
Include(sf => sf.ServiceFeeMarkUpAssignment).
Include(sf => sf.ServiceFeeAssignment);
var results = Query.ToList();//HERE A COMPLETE QUERY SHOULD BE
//SENT TO THE DB FOR RETRIEVING ENTITES INCLUDING THEIR CHILDREN
var totalresults = flightsPricingRulesContext.ServiceFee.AsExpandable().Count(filter);
var pagedservicefees = new PagedServiceFee();
pagedservicefees.totalitems = totalresults.ToString();
pagedservicefees.servicefees = new List<FlightsPricingRules.Models.ServiceFee>();
foreach (var servicefeedto in results)
{
var servicefee = new FlightsPricingRules.Models.ServiceFee();
servicefee.id = servicefeedto.Id.ToString();
servicefee.marketId = servicefeedto.MarketId.ToString();
//.....
//SOME MORE PROPERTIES
//
//CHILD ENTITIES
//Zones
servicefee.zones = new List<Zone>();
//HERE AN ADDITIONAL QUERY IS MADE TO LOAD THE CHILD ENTITIES-WHY?
foreach (var zonedto in servicefeedto.ServiceFeeZone)
{
var zone = new Zone();
zone.id = zonedto.ZoneId.ToString();
zone.name = zonedto.Zone.Name;
servicefee.zones.Add(zone);
}
//Carriers
servicefee.carriers = new List<Carr>();
//ALSO HERE AND ADDITIONAL QUERY IS MADE
foreach (var carrierdto in servicefeedto.ServiceFeeCarrier)
{
var carrier = new Carr();
carrier.id = carrierdto.AirlineId.ToString();
servicefee.carriers.Add(carrier);
}
pagedservicefees.servicefees.Add(servicefee);
}
//.......
//SOME MORE CHILD ENTITIES
//
return Json(pagedservicefees, JsonRequestBehavior.DenyGet);
}
OK, I figured it out. It turns out that it does matter where you place the Include statements. I placed the Include statements before the .AsExpandable() and after the parent entity and now eager loading is performed. I can also verify with SQL profiler, the query contains all the necessary joins and it's executed really fast. The correct query is now:
var Query = from P in flightsPricingRulesContext.ServiceFee
.Include(sf => sf.ServiceFeeCarrier)
.Include(sf=>sf.ServiceFeeAssignment)
.Include(sf => sf.ServiceFeeClassOfService)
.Include(sf => sf.ServiceFeeDate)
.Include(sf => sf.ServiceFeeMarkUpAssignment)
.Include(sf => sf.ServiceFeeZone.Select(zo => zo.Zone))
.AsExpandable().Where(filter) select P;
Posting the answer in case someone encounters a same scenario.