EF ChangeTracker Accessing Tracked Entity and its navigation collection - entity-framework

I would like to track changes. I have a class/model
public class Emp
{
public MoreInfo MoreInfo { get; set; }
public ICollection<Works> Works { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
foreach (var e in _db.ChangeTracker.Entries<TEntity>())
{
foreach (var key in e.Properties)
{
if (key.IsModified)
{
//I can get the FirstName, LastName fields
}
}
}
But I cant figure out how do I loop MoreInfo and ICollection Works and check its parameters ?

By EF Core terminology these are not properties, but navigations, so they cannot be accessed through Properties. Use Navigations property to get entries (with common properties/methods) for both reference and collection navigation properties
foreach (var navEntry in e.Navigations)
{
// e.MoreInfo, e.Works
if (navEntry.IsModified)
{
}
}
or Reference and Collections to get the respective entries (with specific properties/methods)
foreach (var refEntry in e.References)
{
// e.MoreInfo
if (refEntry.IsModified)
{
}
}
foreach (var colEntry in e.Collections)
{
// e.Works
if (colEntry.IsModified)
{
}
}
But both properties and navigations are considered to be members, so you can use Members to process them all using the common properties/methods
foreach (var memberEntry in e.Members)
{
// e.MoreInfo, e.Works, e.FirstName, e.LastName
if (memberEntry.IsModified)
{
}
}

Related

Update Navigation Property with Entity.CurrentValues.SetValues

I have a Kalem Entity with a collection of DigerKalemMaliyetleri property, which is a collection of MaliyetBirimi objects. DigerKalemMaliyetleri is of JSON type and stored at the same table as a JSON column.
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}
When I try to update entity with only DigerKalemMaliyetleri property changed:
DataContext.Entry<Kalem>(first).CurrentValues.SetValues(second);
SQL Update command isn't executed and database record is not updated.
How could I update the entity without explicitly setting DigerKalemMaliyetleri property?
Regards
I had the same problem, you cann't actually use SetValues to update navigation property, you nead instead use DataContext.Update(YourNewObj) and then DataContext.SaveChanges();, or if you want to use SetValues approach, you need:
-Get the exist entry
Kalem existObj = DataContext.Kalems.Find(YourNewObj.Id);
-Loop in navigations of updating entry and the existing one to set the values of updating entry:
foreach(var navObj in DataContext.Entry(YourNewObj).Navigations)
{
foreach(var navExist in DatatContext.Entry(existObj).Navigations)
{
if(navObj.Metadata.Name == navExist.MetaData.Name)
navExist.CurrentValue = navObj.CurrentValue;
}
}
-Update also changes of direct properties:
DataContext.Entry(existObj).CurrentValues.SetValues(YourNewObj);
-Save your Updating:
DataContext.SaveChanges();
You can also check if you need to load your Navigations before going in foreach loop, otherwise you will get an error.
Please if you see beter scenario, correct me.
It's hard to know exactly what you're doing without a complete code sample. Note also that you're trying to set all properties of first from second, including e.g. the Id, which is probably not what you want.
Here's a complete code sample which works for me:
await using (var ctx = new BlogContext())
{
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
ctx.Kalem.Add(new()
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 1, Maliyet = 2 }
}
});
await ctx.SaveChangesAsync();
}
await using (var ctx = new BlogContext())
{
var first = ctx.Kalem.Find(1);
var second = new Kalem
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 3, Maliyet = 4 }
}
};
ctx.Entry(first).Property(k => k.DigerKalemMaliyetleri).CurrentValue = second.DigerKalemMaliyetleri;
await ctx.SaveChangesAsync();
}
public class BlogContext : DbContext
{
public DbSet<Kalem> Kalem { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(#"Host=localhost;Username=test;Password=test")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
}
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}

Using sets of Entity Framework entities at runtime

I have an EF6 setup against a sql server db with about 60 tables in it.
I have entities for each table. What i'm trying to do is run the same method against a set of these entities that will be known at runtime.
The method is a qa/qc routine that does some data check on particular fields that are assured to be in each table.
I guess what i want to do is make the entity a parameter to the method so i can call it consecutive times.
I would also want to make a set of entities to pass as the parameter.
something like this:
List<string> entList = new List<string>(){"Table1","Table2","Table3"};
foreach (entName in entList)
{
//create an entity with the string name
//call myQAQCMethod with the entity
}
MyQAQCMethod (entity SomeEntity)
{
//run against this entity
doQAQC(SomeEntity);
}
Can this be done? Is it a job for reflection?
EDIT
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
is precisely want i want to do. The thing is I want to avoid typing this loop 60 times. I think i'm looking for a way to "feed" a set of entities to this single method.
Also, thank you very much for helping me. I'm learning a lot.
You need to abstract an interface (entity framework won't even notice):
interface IQaQcable
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
public class EntityA : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
public class EntityB : IQaQcable
{
public int Id { get; set; }
public int CommonInt { get; set; }
public string CommonString { get; set; }
// other properties and relations
}
// in some unknown utility class
void MyQaQcMethod<T>(T entity) where T : IQaQcable
{
doSomethingWithIQaQcableProperties(entity.CommonInt, entity.CommonString);
}
// in some unknown test class
void Test()
{
var entities = new List<IQaQcable> { new EntityA(), new EntityB() };
foreach (var e in entities)
MyQaQcMethod(e);
}
Now, you could extract a base class from which each derives that actually implements the CommonInt and CommonString properties for each entity needing them, but that can get kind of tricky with Table-Per-Type/Table-Per-Hierarchy, so I'd start with this, and then consider introducing either an abstract or concrete base class as an improvement.
EDIT
Maybe your looking for something simpler than I first thought, based on your last comment.
Let's give ourselves what the DbContext for this might look like:
class Context : DbContext
{
public virtual DbSet<EntityA> EntityAs { get; set; }
public virtual DbSet<EntityB> EntityBs { get; set; }
}
So, it could just be that you wish to do this:
using (var context = new Context())
{
var results = context.EntityAs.Where(a => a.Prop1 == e.Prop1)
.Where(a => a.Prop2 == e.Prop2)
.Select(a => new
{
APropertyICareAbout = a.Prop1,
AnotherPropertyICareAbout = a.Prop2
}).ToArray();
}
Keeping in mind, if there is some set of properties in common across entity classes, you could still do something like the following:
IEnumerable<T> MyQaQcMethod(IQueryable<T> entities, T referenceEntity) where T : IQaQcAble
{
return entities.Where(e => SomePredicate(e, referenceEntity));
}
void Test()
{
using (var context = new Context())
{
// EntityA implements IQaQcAble
var resultsForA = MyQaQcMethod(context.EntityAs, defaultEntity).ToArray();
// so does EntityB, so can call with either
var resultsForB = MyQaQcMethod(context.EntityBs, defaultEntity).ToArray();
}
}
Keep in mind, to avoid modifying the generated entity classes, you could implement the interface members — and the interface — in a separate source file using partial classes. E.g.
// IQaQcAble.cs
internal interface IQaQcAble
{
int CommonInt { get; set; }
string CommonString { get; set; }
}
// a class whose existing property names match the interface
public partial class EntityA : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return CommonInt; }
set { CommonInt = value; }
}
string IQaQcAble.CommonString
{
get { return CommonString; }
set { CommonString = value; }
}
}
// a class whose property names differ
public partial class EntityB : IQaQcAble
{
int IQaQcAble.CommonInt
{
get { return SomeOtherInt; }
set { SomeOtherInt = value; }
}
string IQaQcAble.CommonString
{
get { return SomeOtherInt.ToString(); }
set { SomeOtherInt = Convert.ToInt32(value); }
}
}

Removing object from virtual collection or Moving to a different one in Entity Framework Code First

I have an object with a self referencing parent child relationship:
[Table("Content")]
public class Content
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ContentID { get; set; }
public string Text { get; set; }
public int? ParentID { get; set; }
public virtual Content Parent { get; set; }
private ICollection<Content> _contents { get; set; }
public virtual ICollection<Content> Contents
{
get { return _contents ?? (_contents = new HashSet<Content>()); }
set { _contents = value; }
}
}
I am trying to work with the edit function so that if the parent ID changes, the object is correctly removed from the OLD parent's children and added to the new one (or is correctly set to null)
I have tried a number of combinations in order to make the code correctly change the parent ID but I am just not able to crack the correct thing to do here.
[HttpPost]
public ActionResult Edit(Content content)
{
if (ModelState.IsValid)
{
Content oldContent = context.Contents
.Where<Content>(c => c.ContentID == content.ContentID)
.Single<Content>();
// If the parent has changed.
if (content.ParentID != oldContent.ParentID)
{
// if the old parent is not NULL remove from collection
if (oldContent.ParentID != null) {
Content oldParent = context.Contents
.Where<Content>(c => c.ContentID == oldContent.ParentID)
.Single<Content>();
oldParent.Contents.Remove(content);
context.Entry(oldParent).State = EntityState.Modified;
context.SaveChanges();
}
// if the new parent is not NULL add to the new collection
if (content.ParentID != null) {
Content parent = context.Contents
.Where<Content>(c => c.ContentID == content.ParentID)
.Single<Content>();
parent.Contents.Add(content);
context.Entry(parent).State = EntityState.Modified;
context.SaveChanges();
}
}
context.Entry(oldContent).CurrentValues.SetValues(content);
context.SaveChanges();
return RedirectToAction("Index");
}
ViewBag.PossibleParents = context.Contents;
return View(content);
}
The problem is that the data table has two fields related to ParentID - the first ParentID is correctly changing, the second, Parent_ContentID is not. The second one is used for looping through the .Contents property from the parent.
What am I missing? How can I remove the current object from the "related objects" collection of the parent?
oldParent.Contents.Remove(oldContent);
This worked for me. Thanks, #Slauma!

Entity framework SaveChanges() in a different DbContext inserting unnecessary rows

my supermarket model contains a StockItem class and an Alert class which contains a StockItem field:
public class StockItem
{
public int ID { get; set; }
public string Name { get; set; }
public int CurrentQuantity { get; set; }
public int MinQuantity { get; set; }
}
public class Alert
{
public int ID { get; set; }
public int Message{ get; set; }
public virtual StockItem StockItem { get; set; }
}
I Have a function that fetches all StockItems with one DbContext:
using (var db = new MyDbContext())
{
return db.StockItems.ToList();
}
And another function that process these items, and adding new Alerts in a another DbContext:
foreach (var item in items)
{
if (item.CurrentQuantity < item.MinQuantity)
{
using (var db = new MyDbContext())
{
db.Alerts.Add(new Alert(){StockItem = item, Message = "Low Quantity"});
db.SaveChanges();
}
}
}
The problem is: When an Alert is Saved, a new Stock Item (with a different id) is added to the database, although it is already there!
any solutions?
I think you should Attach the stockitem first.
Try this:
foreach (var item in items)
{
if (item.CurrentQuantity < item.MinQuantity)
{
using (var db = new MyDbContext())
{
db.StockItems.Attach(item);
db.Alerts.Add(new Alert {StockItem = item, Message = "Low Quantity"});
db.SaveChanges();
}
}
}
using (var db = new MyDbContext())
{
var items = db.StockItems.ToList();
foreach (var item in items)
{
if (item.CurrentQuantity < item.MinQuantity)
{
db.Alerts.Add(new Alert {StockItem = item,
Message = "Low Quantity"});
db.SaveChanges();
}
}
}
In this case you dont need to do attach. EF can only track changes in its own life cycle, in your first case when you do,
using (var db = new MyDbContext())
{
return db.StockItems.ToList();
}
You are disposing MyDbContext, so EF makes all stock items as independent (detached items), and when you add them to different context, context assumes that it is a new item and it will insert the one.
The best way will be to keep Context alive throughout the changes you want to make. Also note, keeping context alive for longer time does not mean you will keep database connection open all the time. EF will open and close database connection automatically only when you are executing query and you are calling save changes.
Otherwise you have to attach as Ben suggested.

ef4 record stamping, inserted_at, inserted_by

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.