Entity Framework Relationships Not Updating - c#

I am having a problem where by updating an entity does not update entities related to it. Here is an example of the code.
public class Foo
{
public int Id { get; set; }
public virtual ICollection<Bar> Bars { get; set; }
public virtual string SomeString { get; set; }
}
public class Bar
{
public int Id { get; set; }
}
public class FooMapping : EntityTypeConfiguration<Foo>
{
public FooMapping()
{
HasKey(f => f.Id);
HasMany(f => f.Bars);
}
}
public class MyDb : DbContext
{
public IDbSet<Foo> Foos { get; set; }
public IDbSet<Bar> Bars { get; set; }
protected override vod OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new FooMapping());
}
}
I receive an update command containg a fooobject from an external application like this:
public void Update(Foo foo)
{
_myDb.Entry(foo).State = EntityState.Modified;
_myDb.SaveChanges();
}
This updates the SomeString value correctly, however the Bar objects are not modified.
I have tried doing the following:
public void Update(Foo foo)
{
foreach(Bar bar in foo.Bars)
{
_myDb.Entry(bar).State = EntityState.Modified;
}
_myDb.Entry(foo).State = EntityState.Modified;
_myDb.SaveChanges();
}
but this gives a DbUpdateConcurrencyException, with the following info
Store update, insert, or delete statement affected an unexpected
number of rows (0).
Entities may have been modified or deleted since entities were loaded. Refresh
ObjectStateManager entries.
How can I get the Bars to update without having to select them from the database?
Thanks
UPDATE
I have modified the Bar entity and the mappings as so.
public class Bar
{
public int Id { get; set; }
public virtual Foo Foo { get; set; }
}
public class FooMapping : EntityTypeConfiguration<Foo>
{
public FooMapping()
{
HasKey(f => f.Id);
HasMany(f => f.Bars).WithRequired(b => b.Foo);
}
}
public class BarMapping : EntityTypeConfiguration<Foo>
{
public BarMapping()
{
HasKey(b => b.Id);
HasRequired(b => b.Foo);
}
}
This doesn't change the anything. I should mention though that even with the first version using _myDb.Foos.Add(foo) works correctly (adding all Bars). However it is the update which does not.

You may notice that if Foo has new Bar items they will be added and (possibly) the existing ones will be nullified. You can try a few things:
Relate Bar to Foo:
public class Bar
{
public int Id { get; set; }
public virtual Foo Foo { get; set; }
}
public class FooMapping : EntityTypeConfiguration<Foo>
{
public FooMapping()
{
HasKey(f => f.Id);
HasMany(f => f.Bars)
.WithRequired(b => b.Foo); //check this syntax, I'm not entirely sure.
}
}
Try the above. What happened when I had a similar problem was that the "new" Bars where saved and the existing ones where nullified.
The next step you can try is to do a bit of manual deletion when updating:
public void Update(Foo foo)
{
var existing = _myDb.Find(foo.Id);
foreach(Bar bar in existing.Bars.ToList())
{
_myDb.Remove(bar);
}
existing.Bars.Clear();
foreach(Bar bar in foo.Bars)
{
existing.Bars.Add(bar);
}
// map other properties...
_myDb.SaveChanges();
}
Let me know if it works for you.

Related

How to configure an owned entity in order to have one to many relationship with an abstract class ef core .net 6

EF Core version: 6.0.6
I have a class User which owns many class Post. The class posts own many class Comment and now I would like to handle a relationship between comments and reactions(reaction is an abstract class which can be like, star etc). As I read in the official doc I cannot implement reactions as owned because it has an inheritance hierarchy.
public class User
{
private readonly List<Post> _posts;
public long Id{ get; }
public string Username{ get; }
public DateTime CreatedAt { get; }
public IReadOnlyCollection<Post> Posts => _posts.AsReadOnly();
private User() : base(default, default)
{
CreatedAt = DateTime.UtcNow;
UpdatedAt = DateTime.UtcNow;
_posts= new List<Post>();
}
//bla bla public constructor etc
}
public class Post
{
private readonly List<Comment> _comments;
public long PostId { get; }
public DateTime CreatedAt { get; }
public IReadOnlyCollection<Comment> Comments => _comments.AsReadOnly();
private Post()
{
CreatedAt = DateTime.UtcNow;
_comments = new List<Comment>();
}
//bla bla public constructor etc
}
public class Comment
{
private readonly List<Reaction> _reactions;
public long CommentId { get; }
public string Text { get; }
public DateTime CreatedAt { get; }
public IReadOnlyCollection<Reaction> Reactions => _reactions.AsReadOnly();
private Comment()
{
CreatedAt = DateTime.UtcNow;
_reactions = new List<Reaction>();
}
//bla bla public ctor
}
and finally the class Reaction
public abstract partial class Reaction
{
public long Id { get; }
protected Reaction() { }
protected Reaction(long id)
{
Id = id;
}
}
which has two child classes
the class Star
public abstract partial class Reaction
{
public class Star : Reaction
{
private readonly List<Caption> _captions;
public decimal Value { get; private set; }
public IReadOnlyCollection<Caption> Captions => _captions.AsReadOnly();
public Star(long id, decimal value, IReadOnlyCollection<Caption> captions) : base(id)
{
Value = value;
_captions = captions.ToList();
}
}
}
and the class Like
public abstract partial class Reaction
{
public class Like : Reaction
{
private readonly List<Caption> _captions;
public IReadOnlyCollection<Caption> Captions => _captions.AsReadOnly();
public Like(long id, Caption[] captions) : base(id)
{
_captions = captions.ToList();
}
}
}
Finally the class
public class Caption
{
public int Id { get; }
public string Text { get; }
public Caption(int id, string text)
{
Id = id;
Text = text;
}
}
I have stacked how to configure the database schema.
Comments cannot own many reactions due to an owned entity cannot have an inheritance hierarchy.
I tried to implement a table-per-type solution for reactions which is abstract class.
Here is the configuration
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("Users");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).ValueGeneratedNever().IsRequired();
builder.Property(x => x.Username).IsRequired();
builder.Property(x => x.CreatedAt).IsRequired();
builder.OwnsMany(x => x.Posts, post =>
{
post.ToTable("Posts");
post.HasKey(PostsId);
post.Property(x => x.PostsId).ValueGeneratedNever().IsRequired();
post.Property(x => x.CreatedAt).IsRequired();
post.OwnsMany(x => x.Comments, comment =>
{
comment.ToTable("Comments");
comment.HasKey(CommentId);
comment.Property(x => x.CommentId).ValueGeneratedNever().IsRequired();
comment.Property(x => x.Text).HasMaxLength(100).IsRequired();
comment.Property(x => x.CreatedAt).IsRequired();
//how to continue the configuration of reactions and captions???
});
});
}
}
I don't know how to configure a relationship between comments and reactions.A comment could have many reactions(one to many) and a reaction could have many captions(one to many)

How to syncronize records with Entity Framework Core for one to many relation during the update operation? Not add a new entity, just syncronize

I have the following entities: PushTemplate and PushTemplateMessage. One PushTemplate can have many PushTemplateMessages. I have repositories for this. All works for the create operation. But problem starts when I try to update PushTemplate and set new text for messages. Insetad of an update I see new one PushTemplateMessage. I'll show my code.
Entity PushTemplate:
public class PushTemplate
{
public int PushTemplateId { get; set; }
[Required]
public List<PushTemplateMessage> Messages { get; set; }
public DateTime CreatedAt { get; set; }
public PushTemplate()
{
CreatedAt = DateTime.UtcNow;
}
}
Entity PushTemplateMessage:
public class PushTemplateMessage
{
public int PushTemplateMessageId { get; set; }
public string PushTitle { get; set; }
public string PushMessage { get; set; }
public PushTemplate PushTemplate { get; set; }
}
Repository PushTemplateRepository:
public class PushTemplateRepository : IPushTemplateRepository
{
private readonly ApplicationDbContext _applicationContext;
public PushTemplateRepository(ApplicationDbContext applicationContext)
{
_applicationContext = applicationContext;
}
public IQueryable<PushTemplate> PushTemplates => _applicationContext.PushTemplates;
public void Save(PushTemplate pushTemplate)
{
if (pushTemplate.PushTemplateId == 0)
{
_applicationContext.PushTemplates.Add(pushTemplate);
}
else
{
PushTemplate dbEntity = _applicationContext.PushTemplates.Find(pushTemplate.PushTemplateId);
dbEntity.Messages = new List<PushTemplateMessage>();
_applicationContext.SaveChanges();
dbEntity.Messages = pushTemplate.Messages;
}
_applicationContext.SaveChanges();
}
}
Database context:
public class ApplicationDbContext : IdentityDbContext
{
private readonly string _connectionString;
public DbSet<PushTemplate> PushTemplates { get; set; }
public DbSet<PushTemplateMessage> TemplateMessages { get; set; }
public ApplicationDbContext(IConfiguration configuration)
{
_connectionString = configuration.GetConnectionString("MakeAppDb");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(_connectionString, b => b.MigrationsAssembly("MakeAppPushesNet_2"));
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PushTemplate>()
.HasMany(x => x.Messages)
.WithOne(y => y.PushTemplate)
.OnDelete(DeleteBehavior.Cascade);
}
}
And finally call to update operation from my controller:
PushTemplate pushTemplate = new PushTemplate
{
Messages = pushTemplateMessages // new list of messages
};
_pushTemplateRepository.Save(pushTemplate);
After this operation I have the old PushTemplateMessage and new version of PushTemplateMessage. But I need only new one! As you can see, in the repository I have tried to 'clean' old PushTemplateMessages to set then new list. But it continues to merge old data with new! Where is the mistake?
Ok, I have solved it. This post was helpful. This is from the answer.
So, if you want to synchronize (not add), even if you want set new values as NULL you have to use include("EntityName") on your db context. Now my save/update repository code is:
public void Save(PushTemplate pushTemplate)
{
if (pushTemplate.PushTemplateId == 0)
{
_applicationContext.PushTemplates.Add(pushTemplate);
}
else
{
PushTemplate dbEntity = _applicationContext.PushTemplates
.Include(x => x.Messages)
.FirstOrDefault(x => x.PushTemplateId == pushTemplate.PushTemplateId);
dbEntity.Messages = pushTemplate.Messages;
}
_applicationContext.SaveChanges();
}
As you can see I just use Include(). And now I can set null or synchronize it with a new list of values.

Error Creating Controller With Custom Type using Entity Framework MVC ASP.NET

I'm trying to create a controller with a model that has custom types in it...
I have a main class with ID and a type 'Work', where I declared three properties, one of which relies on the other two. Then I created a dbset type. Are my mapping properties incorrect?
I am getting the following error:
There was an error running the selected code generator: "Unable to retrieve metadata for 'Stack.Models.Work'". the property sum is not a declared property on type Math. Verify that the property has not been explicitly excluded from the model by using the Ignore Method or NotMappedAttribute data annotation. Make sure it is a valid primitive property.
namespace stack.Models
{
public class Work
{
[Key]
public int ID { get; set; }
public Work ()
{
this.Maths = new Math();
}
public Math Maths { get; set; }
}
[ComplexType]
public class Math
{
public int first { get; set; }
public int second { get; set; }
public int sum
{
get
{
try
{
return first + second;
}
catch
{
return 0;
}
}
}
}
public class WorkDBContext: DbContext
{
public DbSet<Work> Working { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Work>()
.Property(c => c.Maths.first).IsRequired();
modelBuilder.Entity<Work>()
.Property(c => c.Maths.second).IsRequired();
modelBuilder.Entity<Work>()
.Property(c => c.Maths.sum).IsRequired();
base.OnModelCreating(modelBuilder);
}
}
}
Sum is not a simple type (column in the database) it is a data function(calculated from other properites), you do not have to store it in the database.
[ComplexType]
public class Math
{
public int first { get; set; }
public int second { get; set; }
[NotMapped]
public int sum
{
get
{
return first + second;
}
}
}
Remove this line:
modelBuilder.Entity<Work>()
.Property(c => c.Maths.sum).IsRequired();

Factory-like pattern without manually writing discriminator into Factory

I have the following:
public abstract class FooBase
{
public virtual int Id { get; set; }
}
public class Foo1 : FooBase { /* could be stuff here */ }
public class Foo2 : FooBase { /* could be stuff here */ }
public class Bar
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual SOMETHING WhichFoo { get; set }
}
...where WhichFoo deals with which FooBase to use (potentially string of "Foo1" but this seems quite messy).
With mappings:
public class FooBaseMap : ClassMap<FooBase>
{
public FooBaseMap()
{
this.Id(x => x.Id);
this.DiscriminateSubClassesOnColumn("Type");
}
}
public class Foo1Map : SubclassMap<Foo1>
{
public Foo1Map() { this.DiscriminatorValue("Foo1"); }
}
public class Foo2Map : SubclassMap<Foo2>
{
public Foo2Map() { this.DiscriminatorValue("Foo2"); }
}
What I want to be able to do is to create a new Foo, either 1 or 2, based on a value stored in Bar. So:
Bar bar = this.Session.Get<Bar>(1);
FooBase foo1 = bar.GetANewFoo(); // returns a new Foo1
Bar anotherBar = this.Session.Get<Bar>(123);
FooBase foo2 = bar.GetANewFoo(); // returns a new Foo2
Where GetANewFoo() could be a method, a property which returns an empty instance of Foo1 or Foo2.
Effectively what I want to do is to store the type of FooBase to be created by GetANewFoo in Bar.
What's the best way of going about this without explicitly having to manually write "Foo1" or "Foo2" to a Bar when I create one?
You can create a column in the Bar table which is going to save what kind of Foo it is. So, if this SOMETHING type of property is an enum, or a string, or an int (whatever), you can use switch-case statement to identify the kind of Foo, create what you need and return.
Just like this:
public FooBase GetANewFoo()
{
switch (WhichFoo)
{
case "Foo1": return new Foo1();
case "Foo2": return new Foo2();
default: return null;
}
}
Okay I think I got it - actually rehashing a method I used for the Strategy Pattern.
public abstract class FooFactoryBase
{
protected FooFactoryBase() { } // for NHibernate
protected FooFactoryBase(Guid id)
{
this.Id = id;
}
public virtual Guid Id { get; set; }
public virtual IList<Bar> Bars { get; set; }
public abstract FooBase CreateFoo();
}
public class Foo1Factory : FooFactoryBase
{
public readonly static Guid Guid = new Guid("abc123...");
public Foo1Factory() : base(Guid) { }
public override FooBase CreateFoo()
{
return new Foo1();
}
}
Then Bar becomes:
public class Bar
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual FooFactoryBase FooFactory { get; set; }
}
And mappings:
public class BarMap : ClassMap<Bar>
{
this.Id(x => x.Id);
this.Map(x => x.Name);
this.References(x => x.FooFactory);
}
public class FooFactoryBaseMap : ClassMap<FooFactoryBase>
{
this.Id(x => x.Id);
this.HasMany(x => x.Bars).Inverse();
this.DiscriminateSubClassesOnColumn("Id");
}
public class Foo1FactoryMap : SubClassMap<Foo1Factory>
{
this.DiscriminatorValue(Foo1Factory.Guid);
}
Now when I create my database I can populate it with all my FooFactorys, when adding Bars I can just load the appropriate Factory from the database and then call my code like so:
Bar bar = this.Session.Get<Bar>(10);
FooBase foo = bar.FooFactory.CreateFoo();
and the appropriate FooBase will be created.
Sometimes you just need to ask the question to figure it out :)

Mapping a simple array

I'm using fluent Nhibernate to map a simple class
And using Schema Generation to create this class on MySQL DB.
I can't use IList<> for my properties (I'm mapping cross-language domain classes)
So I have to use simple arrays..
I Want NHibernate to create a connection table between the two classes,
These are the domain classes:
public class ClassOne
{
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo
{
public virtual Guid Guid { get; set; }
public virtual string Title { get; set; }
}
And this is the map:
public class ClassOneMap : ClassMap<ClassOneMap>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags)
.Cascade.SaveUpdate());
}
}
public class ClassTwoMap : ClassMap<ClassTwo>
{
public ClassTwoMap()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
}
}
The schema generates great! It has a ClassOne, ClassTwo and ClassTwoToClassOne Tables
But when I'm trying to persist an instance of ClassOne I have an Invalid Cast exception..
This is solved by changing the arrays to IList's but I can't really do that..
Can anyone tell me how to configure the Fluent mapping to use Arrays without changing the schema architecture?
Thanks A'lot!
Ok, played around this and hope that solve the question.
So models are:
public class ClassOne : Entity
{
public virtual string Title { get; set; }
public virtual ClassTwo[] Tags { get; set; }
}
public class ClassTwo : Entity
{
public virtual string Title { get; set; }
}
Base class contains the Id definition which is long in my case. Should not be a problem with Guids
Mapping class: We are using FluentNhibernate with some convention, also the idea is in HasManyToMany
public class ClassOneMappingOverride : IAutoMappingOverride<ClassOne>
{
public void Override(AutoMapping<ClassOne> mapping)
{
mapping.HasManyToMany(x => x.Tags).AsArray(x => x.Id).ParentKeyColumn("classOneId")
.ChildKeyColumn("classTwoId")
.Table("ClassOneLinkClassTwo")
.Cascade.SaveUpdate();
}
}
Please note that if you not indicate ParentKey, ChildKey and Table it will not create the link table.
The unit test which insert data looks like:
public class ClassOneDataPart : DataPartBase, IDataPart
{
public void AddToDatabase()
{
var classOne = new ClassOne { Title = "classOne" };
var classTwo1 = new ClassTwo { Title = "class21" };
var classTwo2 = new ClassTwo { Title = "class22" };
var tags = new[] { classTwo1, classTwo2 };
classOne.Tags = tags;
this.SaveData(classOne);
this.SaveData(classTwo1);
this.SaveData(classTwo2);
}
}
and the result into database is:
Regards,
Ion
Map the collection as a private field and expose it as an array. This also makes it easy to expose AddTag and RemoveTag methods without manipulating the array.
public class ClassOne
{
private IList<ClassTwo> _tags;
public virtual Guid Guid { get; set; }
public virtual String Title { get; set; }
public virtual ClassTwo[] Tags
{
// possibly expose as method to hint that the array is re-built on every call
get { return _tags.ToArray(); }
}
}
public class ClassOneMap : ClassMap<ClassOne>
{
public ClassOneMap ()
{
Id(x => x.Guid).GeneratedBy.GuidComb();
Map(x => x.Title);
HasManyToMany(x => x.Tags).Access.CamelCaseField(Prefix.Underscore)
.Cascade.SaveUpdate());
}
}
Try to use .AsArray(x=>x.Id)

Categories

Resources