Is there a way to have EF Core populate navigation properties before the change tracker snapshots values? - entity-framework-core

I have an order model in my core with a collection of (abstract base) OrderLines which can be either a NoteLine or an ItemLine. Each ItemLine has various pricing details relevant to that specific line.
public class Order : IEntity<int>
{
public int Id { get; private set; }
protected ICollection<OrderLine> _Lines;
public IEnumerable<OrderLine> Lines => _Lines.AsEnumerable();
public decimal TotalSellEx
{
get => _Lines.OfType<ItemLine>().Sum(l => l.TotalSellEx);
protected set { }
}
}
public class NoteLine
{
public string Note { get; protected set; }
}
public class ItemLine
{
public int StockId { get; protected set; }
public decimal TotalSellEx => ItemSellEx * Quantity;
public decimal ItemSellEx { get; protected set; }
public decimal GrossProfit { get; protected set; }
public int Quantity { get; protected set; }
}
public abstract class OrderLine
{
public decimal LineNumber { get; protected set; }
}
public class OrderConfiguration : EntityConfiguration<Order, int>
{
public override void Configure(EntityTypeBuilder<Statement> builder)
{
base.Configure(builder);
builder.HasMany(o => o.Lines)
.WithOne(o => o.Statement);
builder.Navigation(o => o.Lines)
.AutoInclude();
}
}
My Order model then uses a getter only property to keep a rolling sum of the total values as the list is manipulated by various functions and is successfully persisted to the database due to the empty setter to get around EF Core's need for a setter.
The problem occurs when I pull the record out of the database and change tracker kicks in. It seems to be that the snapshot of this model is created before navigation properties are populated so the total value is always 0 in the change tracker.
This isn't a big problem until the total value is modified in such a way that it becomes 0 properly. In which case EF Core doesn't see a change and doesn't populate the UPDATE statement with this column. At this point any projections containing this property are wrong, showing the previous value before it was set back to 0.
For the record, it always seems that once the object is retrieved the lines are all there, just not during change tracking snapshotting.
Is there a way to get around this behaviour? Or do I have to settle for bandaid work arounds?

Related

Entity Framework 6 - GetObjectStateEntries expected modified entities have state "Unchanged"

Issue
When I create my Account-class as example A, the object appears with state "EntityState.Modified" in my SaveChanges()-method as expected.
When I use example B, the object appears as "EntityState.Unchanged" and does no apear in the result of the GetObjectStateEntries(EntityState.Added | EntityState.Modified) call.
Can anyone explain to me, why the model object appears unchanged in example B while everything works in example A?
Thankyou
Classes
I update 'Created' and 'LastUpdated' using this method in my Context object:
public class CrmContext : DbContext
{
public override int SaveChanges()
{
DateTime now = DateTime.Now;
foreach (ObjectStateEntry entry in (this as IObjectContextAdapter).ObjectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
var account = entry.Entity as Account;
if (account != null)
{
account.LastUpdated = now;
if (entry.State == EntityState.Added)
{
account.Created = now;
}
}
}
}
return base.SaveChanges();
}
}
Model example A (WORKING):
public class Account
{
[Key]
public int AccountId { get; set; }
public string Name { get; set; }
[Timestamp]
public Byte[] TimeStamp { get; set; }
public DateTime Created { get; set; }
public DateTime LastUpdated { get; set; }
}
Model example B (NOT WORKING):
public class Account : BaseObject
{
[Key]
public int AccountId { get; set; }
public string Name { get; set; }
}
public class BaseObject
{
[Timestamp]
public Byte[] TimeStamp { get; set; }
public DateTime Created { get; set; }
public DateTime LastUpdated { get; set; }
}
UPDATE:
The problem does not seem to have anything to do with whether the model class inherits from a base class or not as I cannot reproduce a working example without using the suggested DetectChanges() call.
You should call change detection manually at the beginning of the overridden SaveChanges method:
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges();
//...
}
I have no explanation why the entity state might be different for model A and model B. But the missing call to DetectChanges generally can be a reason why an entity is in state Unchanged (temporarily) although it has been modified. For example if you have POCO entities (not change tracking proxies), just load an entity, change a property and then call your overridden SaveChanges the state will be Unchanged until base.SaveChanges is called which would update the state to Modified and save the change to the database. But your code that sets the LastUpdated property would have been skipped and LastUpdated had the old value in the database.

Can I Embed an object in an EF entity (serialize on save, deserialize on access)?

I have a class that I want to keep meta data for -- there a several interaction scenarios so meta allows me to keep different meta for different interaction types.
class Feed()
{
Guid FeedId { get; set; }
ObjectMetaDictionary Meta { get; set; }
}
I would like EF to serialize this ObjectMetaDictionary and store it as a string/VarChar in the database. When I retrieve a record I want it to be deserialized as an ObjectMetaDictionary.
Does EF support this? How can I do it?
I am using Entity Framework Code First.
SOLVED: I provided an answer below that solved my problem. I will accept this answer as soon as SO allows me to.
Apparently this is actually quite easy. I was able to get it working thanks to some help from this previous SO answer.
Fluent configuration in OnModelCreating allows us to tell EF what to use as the value property for serializing to the DB and back out again.
Here's my solution:
public class Feed
{
public virtual Guid FeedId { get; set; }
public virtual FeedMetaData Meta { get; set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
}
public class FeedMetaData
{
public Dictionary<string, string> Data { get; set; }
public string Serialized
{
get { return JsonConvert.SerializeObject(Data); }
set
{
if(string.IsNullOrEmpty(value)) return;
var metaData = JsonConvert.DeserializeObject<Dictionary<string, string>>(value);
Data = metaData ?? new Dictionary<string, string>();
}
}
// addl code removed...
}
public class FeedsDbContext : DbContext
{
public DbSet<Feed> Feeds { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<FeedMetaData>()
.Property(p => p.Serialized)
.HasColumnName("Meta");
modelBuilder.ComplexType<FeedMetaData>().Ignore(p => p.Data);
base.OnModelCreating(modelBuilder);
}
}
Have your Entity Framework object be simple and have a String property for the column in the database.
class Feed()
{
Guid FeedId { get; set; }
String Meta { get; set; }
}
Create methods that save and load the property as such: (it's been a while since I've used EF, so i'm not sure if you can create a transient property with these getter/setters or if you need something else)
//Reading from string column Meta
(ObjectMetaDictionary) XamlServices.Load(new StringReader(someFeed.Meta));
//Saving to string column Meta
someFeed.Meta = XamlServices.Save(value);
This brings another whole issue to your project though. Changing your ObjectMetaDictionary might cause it to not deserialize from the database correctly. Your ObjectMetaDictionary becomes essentially part of your database schema and you will need to handle versioning or changes accordingly.
The feature HasConversion saved my life. Unlock all json formats! Enjoy it!
public partial class Feed
{
public int Id { get; set; }
//this column will be mapped to a "nvarchar(max)" column. perfect!
public Dictionary<string, string> Meta { get; set; }
}
public class FeedsDbContext : DbContext
{
public FeedsDbContext(DbContextOptions<FeedsDbContext> options)
: base(options)
{
}
public virtual DbSet<Feed> Feed { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Feed>(entity =>
{
entity.Property(p => p.Meta).HasConversion(
x => JsonConvert.SerializeObject(x) //convert TO a json string
, x => JsonConvert.DeserializeObject<Dictionary<string, string>>(x) //convert FROM a json string
);
});
}
}

Entity Framework: Developing a Model that does not do Updates and Deletes

I am trying to figure out a way to develop a database model using Entity Framework that does not do updates or deletes. The business requirements want the complete history of all changes that are made to each record in the system, for analysis reasons. So instead I want to always modify by inserting a new record to the database.
Is there a clean way to get Entity Framework to do that? Or am I going to be jumping through a lot hoops to get this sort of behavior. The basic model is pretty simple, some stuff, like constructors, removed since they don't add much to the discussion:
public class Container
{
public Guid Id { get; private set; }
public ICollection<Container> RelatedContainers { get; private set; }
public ICollection<Item> Items { get; private set; }
}
public class Item
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public string Value { get; private set; }
}
Basically you need to override SaveChanges() method in DbContext. In your method get all the objects that have the EntityState Deleted or Modified and set the status UnChanged.
public class YourDbContext:DbContext{
public override int SaveChanges(){
foreach ( var ent in this.ChangeTracker
.Entries()
.Where(p =>p.State == System.Data.EntityState.Deleted
p.State == System.Data.EntityState.Modified))
{
ent.State =System.Data.EntityState.Unchanged;
}
}
}

Problems using TPT (Table Per Type) in EF 4.2 and deletion of parent objects

From what I understand on several posts the TPT architecure, with EF, does not create the necessary ON DELETE CASCADE when using a shared primary key.... It was also said that the EF context will handle the proper order of deletion of the sub-classed tables (however I do get an error that it breaks the constraint and that I can fix it with adding the ON DELETE CASCADE on the sub-class table)...
more background info...
I have a Section class, which has a number, title, and a list of pages. The page is designed using a super class which holds basic page properties. I have about 10+ sub-classes of the page class. The Section class holds an ICollection of these pages. The DB is created properly with the exception of no ON DELETE CASCADE on the sub-classed tables.
My code will create the entities and adds to the DB fine. However, if I try to delete a section (or all sections) it fails todelete due to the FK constraint on my sub-class page table...
public abstract BaseContent
{
... common properties which are Ignored in the DB ...
}
public class Course : BaseContent
{
public int Id {get;set;}
public string Name {get;set;}
public string Descripiton {get;set;}
public virtual ICollection<Chapter> Chapters{get;set;}
...
}
public class Chapter : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Course MyCourse{get;set;}
public virtual ICollection<Section> Sections{get;set;}
...
}
public class Section : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Chapter MyChapter {get;set;}
public virtual ICollection<BasePage> Pages {get;set;}
...
}
public abstract class BasePage : BaseContent, IComparable
{
public int Id { get; set; }
public string Title { get; set; }
public string PageImageRef { get; set; }
public ePageImageLocation ImageLocationOnPage { get; set; }
public int PageNumber { get; set; }
public virtual Section MySection { get; set; }
...
}
public class ChapterPage : BasePage
{
public virtual int ChapterNumber { get; set; }
public virtual string ChapterTitle { get; set; }
public virtual string AudioRef { get; set; }
}
public class SectionPage : BasePage
{
public virtual int SectionNumber { get; set; }
public virtual string SectionTitle { get; set; }
public virtual string SectionIntroduction { get; set; }
}
... plus about 8 other BasePage sub-classes...
public class MyContext: DbContext
{
...
public DbSet<Course> Courses { get; set; }
public DbSet<Chapter> Chapters { get; set; }
public DbSet<Section> Sections { get; set; }
public DbSet<BasePage> Pages { get; set; }
...
}
.. Fluent API ... (note Schema is defined to "" for SqlServer, for Oracle its the schema name)
private EntityTypeConfiguration<T> configureTablePerType<T>(string tableName) where T : BaseContent
{
var config = new EntityTypeConfiguration<T>();
config.ToTable(tableName, Schema);
// This adds the appropriate Ignore calls on config for the base class BaseContent
DataAccessUtilityClass.IgnoreAllBaseContentProperties<T>(config);
return config;
}
public virtual EntityTypeConfiguration<BasePage> ConfigurePageContent()
{
var config = configureTablePerType<BasePage>("PageContent");
config.HasKey(pg => pg.Id);
config.HasRequired(pg => pg.Title);
config.HasOptional(pg => pg.PageImageRef);
config.Ignore(pg => pg.ImageLocationOnPage);
return config;
}
public virtual EntityTypeConfiguration<ChapterPage> ConfigureChapterPage()
{
var config = configureTablePerType<ChapterPage>("ChapterPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.ChapterNumber);
config.Ignore(pg => pg.ChapterTitle);
return config;
}
public virtual EntityTypeConfiguration<SectionPage> ConfigureSectionPage()
{
var config = configureTablePerType<SectionPage>("SectionPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.SectionNumber);
config.Ignore(pg => pg.SectionTitle);
return config;
}
... other code to model other tables...
So the app is able to populate content and the relationships are properly set up. However, when I try to delete the course, I get the error that the delete failed due to the constraint on the ChapterPage to PageContent table..
Here is the code which deletes the Course (actually I delete all courses)...
using (MyContext ctx = new MyContext())
{
ctx.Courses.ToList().ForEach(crs => ctx.Courses.Remove(crs));
AttachLookupEntities(ctx);
ctx.SaveChanges();
}
If I add the 'ON DELETE CASCADE' in the ChapterPage and SectionPage table for its shared primary with PageContent, the delete goes through.
In summary,
The only solution that I have seen is to manually alter the constraints to add the ON DELETE CASCADE for all of my sub-class page tables. I can implement the change, as I have code which generates the DB script for the EF tables I need (a small subset of our whole DB) since we will not use EF to create or instantiate the DB (since it does not properly support migrations as yet...).
I sincerely hope that I have miscoded something, or forgot some setting in the model builder logic. Because if not, the EF designers have defined an architecure (TPT design approach) which cannot be used in any real world situation without a hack workaround. It's a half finished solution. Do not get me wrong, I like the work that has been done, and like most MSFT solutions its works for 70% of most basic application usages. It just is not ready for more complex situations.
I was trying to keep the DB design all within the EF fluent API and self-contained. It's about 98% there for me, just would be nice if they finished the job, maybe in the next release. At least it saves me all the CRUD operations.
Ciao!
Jim Shaw
I have reproduced the problem with a little bit simpler example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace EFTPT
{
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<BasePage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
public Parent Parent { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<BasePage> BasePages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Pages)
.WithRequired(p => p.Parent); // creates casc. delete in DB
modelBuilder.Entity<BasePage>()
.ToTable("BasePages");
modelBuilder.Entity<DerivedPage>()
.ToTable("DerivedPages");
}
}
class Program
{
static void Main(string[] args)
{
using (var ctx = new MyContext())
{
var parent = new Parent { Pages = new List<BasePage>() };
var derivedPage = new DerivedPage();
parent.Pages.Add(derivedPage);
ctx.Parents.Add(parent);
ctx.SaveChanges();
}
using (var ctx = new MyContext())
{
var parent = ctx.Parents.FirstOrDefault();
ctx.Parents.Remove(parent);
ctx.SaveChanges(); // exception here
}
}
}
}
This gives the same exception that you had too. Only solutions seem to be:
Either setup cascading delete for the TPT constraint in the DB manually, as you already tested (or put an appropriate SQL command into the Seed method).
Or load the entites which are involved in the TPT inheritance into memory. In my example code:
var parent = ctx.Parents.Include(p => p.Pages).FirstOrDefault();
When the entities are loaded into the context, EF creates actually two DELETE statements - one for the base table and one for the derived table. In your case, this is a terrible solution because you had to load a much more complex object graph before you can get the TPT entities.
Even more problematic is if Parent has an ICollection<DerivedPage> (and the inverse Parent property is in DerivedPage then):
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<DerivedPage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
public Parent Parent { get; set; }
}
The example code wouldn't throw an exception but instead delete the row from the derived table but not from the base table, leaving a phantom row which cannot represent an entity anymore because BasePage is abstract. This problem is not solvable by a cascading delete but you were actually forced to load the collection into the context before you can delete the parent to avoid such a nonsense in the database.
A similar question and analysis was here: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/

Generate money type fields using code first EF CTP5

In this blog post: EF4 Code First Control Unicode and Decimal Precision, Scale with Attributes, Dane Morgridge used attributes to control the creation of different types on your database.
...And I found this pretty unique BTW!!!
How do I generate money type fields in my resulting database using code first API of EF CTP5, if is possible to do it from your model, using conventions or attributes?
Sorry about my English is not my main language.
Thanks in advance.
For example, consider this Invoice class:
public class Invoice
{
public int InvoiceId { get; set; }
public decimal Amount { get; set; }
}
You can do it with fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>()
.Property(i => i.Amount)
.HasColumnType("Money");
}
Or you can do it with Data Annotations:
public class Invoice
{
public int InvoiceId { get; set; }
[Column(TypeName="Money")]
public decimal Amount { get; set; }
}
using System.Data.Entity.ModelConfiguration.Configuration.Properties.Primitive;
public class MoneyAttribute : Attribute { }
public class MoneyAttributeConvention : AttributeConfigurationConvention<PropertyInfo, DecimalPropertyConfiguration, MoneyAttribute> {
public override void Apply(PropertyInfo memberInfo, DecimalPropertyConfiguration configuration, MoneyAttribute attribute) {
configuration.ColumnType = "money";
}
}
then you use like that
[Money]
public decimal Value { get; set; }