I have a problem with something that seems to be a bug in Entity Framework 4.1: I have added a handler on ObjectContext.SavingChanges which updates a property "LastModified" whenever an object is added to or modified in the database. Then I do the following:
Add two objects to the database, and submit (call SaveChanges())
Modify the first object that was added
Extract the two objects ordered by LastModified
The resulting objects are returned in the wrong order. Looking at the objects, I can see that the LastModified property has been updated. In other words, the SavingChanges event was fired properly. But looking in the database, the LastModified column has not been changed. That is, there is now a difference between EF's cached objects and the rows in the database.
I tried performing the same update to LastModified in an overridden "SaveChanges" method:
public override int SaveChanges()
{
SaveChangesHandler();//updating LastModified property on all objects
return base.SaveChanges();
}
Doing this caused the database to be updated properly and the queries returned the objects in proper order.
Here is an entire test program showing the error:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace TestApplication
{
class Program
{
private PersistenceContext context;
private static void Main(string[] args)
{
var program = new Program();
program.Test();
}
public void Test()
{
SetUpDatabase();
var order1 = new Order {Name = "Order1"};
context.Orders.Add(order1);
var order2 = new Order {Name = "Order2"};
context.Orders.Add(order2);
context.SaveChanges();
Thread.Sleep(1000);
order1 = GetOrder(order1.Id); // Modified 1.
order1.Name = "modified order1";
context.SaveChanges();
List<Order> orders = GetOldestOrders(1);
AssertEquals(orders.First().Id, order2.Id);//works fine - this was the oldest object from the beginning
Thread.Sleep(1000);
order2 = GetOrder(order2.Id); // Modified 2.
order2.Name = "modified order2";
context.SaveChanges();
orders = GetOldestOrders(1);
AssertEquals(orders.First().Id, order1.Id);//FAILS - proves that the database is not updated with timestamps
}
private void AssertEquals(long id1, long id2)
{
if (id1 != id2) throw new Exception(id1 + " != " + id2);
}
private Order GetOrder(long id)
{
return context.Orders.Find(id);
}
public List<Order> GetOldestOrders(int max)
{
return context.Orders.OrderBy(order => order.LastModified).Take(max).ToList();
}
public void SetUpDatabase()
{
//Strategy for always recreating the DB every time the app is run.
var dropCreateDatabaseAlways = new DropCreateDatabaseAlways<PersistenceContext>();
context = new PersistenceContext();
dropCreateDatabaseAlways.InitializeDatabase(context);
}
}
////////////////////////////////////////////////
public class Order
{
public virtual long Id { get; set; }
public virtual DateTimeOffset LastModified { get; set; }
public virtual string Name { get; set; }
}
////////////////////////////////////////////////
public class PersistenceContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public PersistenceContext()
{
Init();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
public void Init()
{
((IObjectContextAdapter) this).ObjectContext.SavingChanges += SavingChangesHandler;
Configuration.LazyLoadingEnabled = true;
}
private void SavingChangesHandler(object sender, EventArgs e)
{
DateTimeOffset now = DateTimeOffset.Now;
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
{
SetModifiedDate(now, entry);
}
}
private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
if (modifiedEntity.Entity == null)
{
return;
}
PropertyInfo propertyInfo = modifiedEntity.Entity.GetType().GetProperty("LastModified");
if (propertyInfo != null)
{
propertyInfo.SetValue(modifiedEntity.Entity, now, null);
}
}
}
}
I should add that the SavingChanges handler worked fine before we upgraded to EF4.1 and using Code-First (that is, it worked in EF4.0 with model-first)
The question is: Have I found a bug here, or have I done something wrong?
I'm not sure if this can be considered a Bug. What seems to happen is that the way you manipulate the LastModified property does not trigger INotifyPropertyChanged and thus the changes do not get populated to your Database.
To prove it use:
order2.Name = "modified order2";
((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(order2).SetModifiedProperty("LastModified");
To utilize this knowledge in your SavingChangesHandler:
private void SavingChangesHandler(object sender, EventArgs e)
{
DateTimeOffset now = DateTimeOffset.Now;
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
{
SetModifiedDate(now, entry);
if (entry.State == EntityState.Modified)
{
((IObjectContextAdapter) this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity).SetModifiedProperty("LastModified");
}
}
}
Edit:
I looked into this a little more and you are correct. For some reason MS decided to not fire PropertyChanged events when using PropertyInfo.SetValue anymore. Only one way to find out if this is a bug or a design decision: File a bug report / Post to msdn Forums.
Though changing the property directly via CurrentValue seems to work fine:
private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
if (modifiedEntity.Entity == null)
{
return;
}
modifiedEntity.Property("LastModified").CurrentValue = now;
}
Related
we are using ef core 3.1
And we want to use dynamic query filter,
I tried sample implementation but did not work correctly we expected, filtering always same tenant id,i tried to explain at below
public class TestDbContext : DbContext
{
public DbSet<TenantUser> TenantUsers { get; set; }
private readonly ITenantProvider _tenantProvider;
private Guid? TenantId => _tenantProvider.TenantId;
public TestDbContext (DbContextOptions<TestDbContext > options, ITenantProvider tenantProvider) : base(options)
{
_tenantProvider = tenantProvider;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TenantUser>()
.HasQueryFilter(p => EF.Property<Guid>(p, "TenantId") == TenantId);
}
}
ITenantProvider returns TenantId from HttpContext headers
this code filtering always same tenant id from coming first request
Update:
public class TenantProvider : ITenantProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Guid? TenantId
{
get
{
if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(HeaderNames.TenantId, out var tenantId) &&
Guid.TryParse(tenantId, out Guid parsedTenantId))
{
return parsedTenantId;
}
return null;
}
}
}
For example
First Request TenantId = 60000000-0000-0000-0000-000000000000
This filter => 60000000-0000-0000-0000-000000000000
Second Request TenantId = 10000000-0000-0000-0000-000000000000
This filter => 60000000-0000-0000-0000-000000000000
We tried something similar like that a few years ago. Main problem is here that OnModelCreating method only triggered once. So HasQueryFilter works once and gets the current tenant id from provider and it applies to all queries the same tenant id.
You should also implement a custom IModelCacheKeyFactory
public class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context)
{
if (context is TestDbContext testDbContext)
{
return (context.GetType(), testDbContext.TenantId);
}
return context.GetType();
}
}
And then, you need to replace like this
var builder = new DbContextOptionsBuilder<TestDbContext>();
builder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();
var context = new TestDbContext(builder.Options);
Reference:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.infrastructure.imodelcachekeyfactory
I'm trying to implement item versioning using EF, and what I need to know is whether or not the entity which I called Update() on actually got changed, so I can increment its version number. How can I obtain this information?
My repository Update function looks like this:
public virtual void Update(T entity)
{
dbset.Attach(entity);
dataContext.Entry(entity).State = EntityState.Modified;
}
What I opted for afterall was comparing the serialized versions of the 2:
public void UpdateProduct(Product product)
{
var productInDb = GetByID(product.Id);
if (!JToken.DeepEquals(JsonConvert.SerializeObject(product), JsonConvert.SerializeObject(productInDb)))
product.CurrentVersion++;
product = Update(product);
}
You can get the information from the entity state.
If a property has been changed, the state will be "Modified" otherwise "Unchanged".
using (var ctx = new TestContext())
{
var first = ctx.Entity_Basics.First();
var x1 = ctx.IsModified(first); // false
first.ColumnInt = 9999;
var x2 = ctx.IsModified(first); // true
}
public static class Extensions
{
public static bool IsModified <T>(this DbContext context, T entity) where T : class
{
return context.Entry(entity).State == EntityState.Modified;
}
}
I have an MVC application that uses Entity Framework 5. In few places I have a code that creates or updates the entities and then have to perform some kind of operations on the updated data. Some of those operations require accessing navigation properties and I can't get them to refresh.
Here's the example (simplified code that I have)
Models
class User : Model
{
public Guid Id { get; set; }
public string Name { get; set; }
}
class Car : Model
{
public Guid Id { get; set; }
public Guid DriverId { get; set; }
public virtual User Driver { get; set; }
[NotMapped]
public string DriverName
{
get { return this.Driver.Name; }
}
}
Controller
public CarController
{
public Create()
{
return this.View();
}
[HttpPost]
public Create(Car car)
{
if (this.ModelState.IsValid)
{
this.Context.Cars.Create(booking);
this.Context.SaveChanges();
// here I need to access some of the resolved nav properties
var test = booking.DriverName;
}
// error handling (I'm removing it in the example as it's not important)
}
}
The example above is for the Create method but I also have the same problem with Update method which is very similar it just takes the object from the context in GET action and stores it using Update method in POST action.
public virtual void Create(TObject obj)
{
return this.DbSet.Add(obj);
}
public virtual void Update(TObject obj)
{
var currentEntry = this.DbSet.Find(obj.Id);
this.Context.Entry(currentEntry).CurrentValues.SetValues(obj);
currentEntry.LastModifiedDate = DateTime.Now;
}
Now I've tried several different approaches that I googled or found on stack but nothing seems to be working for me.
In my latest attempt I've tried forcing a reload after calling SaveChanges method and requerying the data from the database. Here's what I've done.
I've ovewrite the SaveChanges method to refresh object context immediately after save
public int SaveChanges()
{
var rowsNumber = this.Context.SaveChanges();
var objectContext = ((IObjectContextAdapter)this.Context).ObjectContext;
objectContext.Refresh(RefreshMode.StoreWins, this.Context.Bookings);
return rowsNumber;
}
I've tried getting the updated object data by adding this line of code immediately after SaveChanges call in my HTTP Create and Update actions:
car = this.Context.Cars.Find(car.Id);
Unfortunately the navigation property is still null. How can I properly refresh the DbContext immediately after modifying the data?
EDIT
I forgot to originally mention that I know a workaround but it's ugly and I don't like it. Whenever I use navigation property I can check if it's null and if it is I can manually create new DbContext and update the data. But I'd really like to avoid hacks like this.
class Car : Model
{
[NotMapped]
public string DriverName
{
get
{
if (this.Driver == null)
{
using (var context = new DbContext())
{
this.Driver = this.context.Users.Find(this.DriverId);
}
}
return this.Driver.Name;
}
}
}
The problem is probably due to the fact that the item you are adding to the context is not a proxy with all of the necessary components for lazy loading. Even after calling SaveChanges() the item will not be converted into a proxied instance.
I suggest you try using the DbSet.Create() method and copy across all the values from the entity that you receive over the wire:
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
this.Context.Entry(newEntry).CurrentValues.SetValues(obj);
return newEntry;
}
UPDATE
If SetValues() is giving an issue then I suggest you try automapper to transfer the data from the passed in entity to the created proxy before Adding the new proxy instance to the DbSet. Something like this:
private bool mapCreated = false;
public virtual TObject Create(TObject obj)
{
var newEntry = this.DbSet.Create();
if (!mapCreated)
{
Mapper.CreateMap(obj.GetType(), newEntry.GetType());
mapCreated = true;
}
newEntry = Mapper.Map(obj, newEntry);
this.DbSet.Add(newEntry;
return newEntry;
}
I use next workaround: detach entity and load again
public T Reload<T>(T entity) where T : class, IEntityId
{
((IObjectContextAdapter)_dbContext).ObjectContext.Detach(entity);
return _dbContext.Set<T>().FirstOrDefault(x => x.Id == entity.Id);
}
Can EntityFramework support an EAV model? Is this a workable scenario, or a nightmare? I want to use an EAV model for a system, and I'd like to embrace EF if possible, but I'm concerned that these two philosophies are in conflict.
It depends how do you expect to use EAV in the application. EF can be used to map this:
public partial class Entity
{
// Key
public virtual int Id { get; set; }
// Other common properties
// Attributes
public virtual ICollection<EavAttriubte> Attributes { get; set; }
}
// The simplest implementation
public class EavAttribute
{
// Key
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Value { get; set; }
}
This is what can be persisted and what can be queried by Linq-to-entities. Now you can make your entity usable by defining helper properties (can be used only in your application but not by persistance or querying). These helper properties can be used only for well known attributes which will always exists for entity type - optional attributes must be still accessed in collection:
public partial class Entity
{
// Just example without error handling
public decimal Price
{
get
{
return Int32.Parse(Attributes.Single(a => a.Name == "Price"));
}
set
{
Attributes.Single(a => a.Name == "Price").Value = value.ToString();
}
}
}
This is not very nice because of conversions and collection searching. If you access data multiple times they will be executed multiple times.
I didn't tried it but I think this can be avoided by implementing a similar interface by each entity:
public interface IEavEntity
{
// loads attribute values from Attributes collection to local fields
// => conversion will be done only once
void Initialize();
// saves local values back to Attributes collection
void Finalize();
}
Now you will handle ObjectMaterialized and SavingChanges events on ObjectContext. In the first handler you will execute Initialize if materialized object implements IEavEntity in the second handler you will iterate ObjectStateManager to get all updated or inserted entities implementing IEavEntity and you will execute Finalize. Something like:
public void OnMaterialized(object sender, ObjectMaterializedEventArgs e)
{
var entity = e.Entity as IEavEntity;
if (entity != null)
{
entity.Initialize();
}
}
public void SavingChanges(object sender, EventArgs e)
{
var context = sender as ObjectContext;
if (context != null)
{
foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
var entity = entry.Entity as IEavEntity;
if (entity != null)
{
entity.Finalize();
}
}
}
}
}
is there any way of going through all the new/modified entities and setting their, inserted_at, updated_at fields?
With ObjectStateManager I can get a list of those entities but could not find a way of setting the entity property values.
foreach (var item in db.ObjectStateManager.GetObjectStateEntries(EntityState.Added))
{
System.Data.Objects.DataClasses.EntityObject entity = (System.Data.Objects.DataClasses.EntityObject)(item.Entity);
// now how can I set its .inserted_at to DateTime.Now
}
here is my current solution
public interface IUpdateTrack
{
DateTime? updated_at { get; set; }
Guid? updated_by { get; set; }
}
public interface IInsertTrack
{
DateTime? inserted_at { get; set; }
Guid? inserted_by { get; set; }
}
implement the interface in the partial class
public partial class crm_customer : BaseDB.IInsertTrack, BaseDB.IUpdateTrack
in the repository class
public void Save()
{
foreach (var item in db.ObjectStateManager.GetObjectStateEntries(EntityState.Added))
{
System.Data.Objects.DataClasses.EntityObject entity = (System.Data.Objects.DataClasses.EntityObject)(item.Entity);
if (item.Entity is BaseDB.IInsertTrack)
{
IInsertTrack insert_track = (IInsertTrack)(item.Entity);
insert_track.inserted_at = DateTime.Now;
insert_track.inserted_by = BaseDB.SessionContext.Current.ActiveUser.UserUid;
}
}
foreach (var item in db.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
{
if (item.Entity is BaseDB.IUpdateTrack)
{
IUpdateTrack update_track = (IUpdateTrack)(item.Entity);
update_track.updated_at = DateTime.Now;
update_track.updated_by = BaseDB.SessionContext.Current.ActiveUser.UserUid;
}
}
I would like a solution that does not require implementing the interface for each class in the model, its error prone, you might forget to implement this interfaces for some classes.
I am using EF4 using database-first approach.
Yes, there is a perfect way to accomplish this in Entity Framework 4.0, Thanks to Julia Lerman for pointing out this nice trick.
using System.Data.Common;
using System.Data.Metadata.Edm;
...
var entries = from e in db.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified)
where e.Entity != null
select e;
foreach (var entry in entries) {
var fieldMetaData = entry.CurrentValues.DataRecordInfo.FieldMetadata;
FieldMetadata updatedAtField = fieldMetaData
.Where(f => f.FieldType.Name == "updated_at").FirstOrDefault();
if (updatedAtField.FieldType != null) {
string fieldTypeName = updatedAtField.FieldType.TypeUsage.EdmType.Name;
if (fieldTypeName == PrimitiveTypeKind.DateTime.ToString()) {
entry.CurrentValues.SetDateTime(updatedAtField.Ordinal,
DateTime.Now);
}
}
}
You can then call this code from within the SavingChanges event to be sure that any
updated_at field is automatically updated.
By the way, the System.Data.Metadata.Edm namespace gives you access to
the PrimitiveTypeKind class.