Update inherited Entity in Entity Framework 7 - c#

I have the following BaseballDbContext class:
public class BaseballDbContext : DbContext
{
public DbSet<BaseballTeam> teams { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Hitter>();
modelBuilder.Entity<Pitcher>();
}
}
And my model classes are:
public class BaseballTeam
{
public int Id { get; set; }
public string teamName { get; set; }
public List<BaseballPlayer> players { get; set; }
}
public abstract class BaseballPlayer
{
public int Id { get; set; }
public int age { get; set; }
public string name { get; set; }
}
public class Hitter : BaseballPlayer
{
public int homeruns { get; set; }
}
public class Pitcher : BaseballPlayer
{
public int strikeouts { get; set; }
}
Initially seeded data in the players table:
Now I want to update name and homeruns property of one of the hitters:
BaseballTeam team = _ctx.teams.Include(q => q.players).FirstOrDefault();
Hitter hitter = team.players.OfType<Hitter>().FirstOrDefault();
hitter.name = "Tulowitzki"; //that property will be updated
hitter.homeruns = 399; //but that will not :(
int i = team.players.FindIndex(q => q.Id == hitter.Id);
team.players[i] = hitter;
_ctx.Update(team);
_ctx.SaveChanges();
After I run the code only player's name got update, but not the homeruns property:
How to update property of both child and parent class ?

From this answer but this is a workaround: Save changes to child class properties using base class query with Entity Framework TPH patten :
Do Not track changes using AsNoTracking()
using (var context = new BaseballDbContext())
{
var team = context.teams.Include(q => q.players).AsNoTracking().FirstOrDefault();
var hitter = team.players.OfType<Hitter>().FirstOrDefault();
hitter.name = "Donaldson";
hitter.homeruns = 999;
context.Update(team);
context.SaveChanges();
}
I think you should have a look at the opened issues related to inheritance and may be open a new issue

Related

Entity Framework Core: model's related entity is being saved but won't load [duplicate]

This question already has answers here:
EF codefirst : Should I initialize navigation properties?
(6 answers)
Closed 1 year ago.
I have a model PlayerContext which has one entity of type Inventory (which is a derived type of ItemContainer). These are all within my DbContext called ChunkContext. When I create a new PlayerContext a Inventory is also constructed and both are saved in the database with the correct ID's and everything (I can confirm by looking in the DB). For some reason when I load the PlayerContext later (after it's been saved) the Inventory is not being loaded (it has the default value and ID of 0 which is incorrect).
Here is how I'm loading the player (and adding to them to the database if they are a new player):
using (var db = new ChunkContext())
{
PlayerContext playerContext = db.Players
.Where(p => p.username == username)
.Include(p => p.inventory)
.FirstOrDefault();
// this means the player couldn't be grabbed from the DB
if (playerContext == null)
{
playerContext = new PlayerContext();
playerContext.username = username;
db.Players.Add(playerContext);
db.SaveChanges();
}
return new Player(peer, playerContext);
}
And here is all of the model classes:
class ChunkContext : DbContext
{
...
public DbSet<PlayerContext> Players { get; set; }
...
public DbSet<ItemContainerContext> ItemContainers { get; set; }
public DbSet<Inventory> Inventories { get; set; }
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.Entity<PlayerContext>().HasOne(p => p.inventory);
...
modelBuilder.Entity<ItemContainerContext>().HasKey(ic => ic.id);
...
base.OnModelCreating(modelBuilder);
}
_
[Index(nameof(username), IsUnique = true)]
public class PlayerContext
{
public PlayerContext()
{
inventory = new Inventory();
}
...
[Key]
public int id { get; set; }
...
[Required]
public Inventory inventory { get; set; }
...
}
_
public class Inventory : ItemContainerContext
{
... unrelated gameplay code ...
}
_
public class ItemContainerContext
{
[Key]
public int id { get; set; }
public ItemContainerContext()
{
...
}
...
}
Try to fix PlayerContext, by removing constructor code and adding a foreign key
public class PlayerContext
{
...
[Key]
public int id { get; set; }
...
public int inventoryId { get; set; }
[ForeignKey(nameof(inventoryId))]
[InverseProperty("playerContext")]
public Inventory inventory { get; set; }
...
}
public class Inventory : ItemContainerContext
{
[InverseProperty(nameof(PlayerContext.inventory))]
public virtual PlayerContext playerContext{ get; set; }
... ...
}
and fix the code
playerContext = new PlayerContext();
playerContext.inventory = new Inventory();
playerContext.username = username;
db.Players.Add(playerContext);
db.SaveChanges();

Disable name convention for navigation property ef-core2.2

I trying to use a identity class inside of my domain object but when i want to create migration for create database the ef core 2.2 say me:
System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.InvalidOperationException: 'Warehouse' cannot be used as a property on entity type 'Existence' because it is configured as a navigation.
my dbcontext is
public class WarehousesContext : BaseContext<WarehousesContext>
{
public WarehousesContext(DbContextOptions<WarehousesContext> options) : base(options)
{
}
public WarehousesContext() : base() { }
public DbSet<Warehouse> Warehouses { get; set; }
public DbSet<Existence> Existences { get; set; }
public DbSet<Entry> Entries { get; set; }
public DbSet<Exit> Exits { get; set; }
public DbSet<Transfer> Transfers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("Inventory");
modelBuilder.Entity<Warehouse>().ToTable("Warehouses");
modelBuilder.Entity<Warehouse>().HasKey(w => w.Id);
modelBuilder.Entity<Warehouse>().Property(w => w.Id).HasConversion(v => v.Id, v => new WarehouseId(v));
modelBuilder.Entity<Existence>().ToTable("Existences");
modelBuilder.Entity<Existence>().HasKey(e => e.Id);
modelBuilder.Entity<Existence>().Property(e => e.Id).HasConversion(v => v.Id, v => new ExistenceId(v));
modelBuilder.Entity<Existence>().OwnsOne(e => e.Warehouse);
modelBuilder.Entity<Existence>().OwnsOne(e => e.Product);
}
}
my existences class is
public class Existence
{
public ExistenceId Id { get; private set; }
public WarehouseId Warehouse { get; private set; }
public ProductId Product { get; private set; }
public decimal Quantity { get; private set; }
public string Batch { get; private set; }
private Existence() { }
public Existence(WarehouseId warehouse, ProductId product, decimal quantity, string batch)
{
Warehouse = warehouse;
Product = product;
Quantity = quantity;
Batch = batch;
}
internal void Add(decimal quantity)
{
Quantity += quantity;
}
internal void Subtract(decimal quantity)
{
Quantity -= quantity;
if (Quantity < 0)
throw new Exception();
}
and my warehouseId class
public class WarehouseId
{
public string Id { get; private set; }
public WarehouseId()
{
this.Id = Guid.NewGuid().ToString();
}
public WarehouseId(string id)
{
Id = id;
}
}
i think the problem are that i use the "entityId" pattern to name my identity class so i want to know if existe some way to tell ef core "don't try to use navigation property convection here"
Change your Existence class as below(you can add your methods accordingly)
public class Existence
{
public string Id { get; private set; }
[ForeignKey("Warehouse")]
public string WarehouseId { get; private set; }
public ProductId Product { get; private set; }
public decimal Quantity { get; private set; }
public string Batch { get; private set; }
public virtual Warehouse Warehouse{get;set;)
}
public class Warehouse
{
//your other Warehouse properties
//add below line, if one to one relation
public virtual Existence Existence{get; set;}
//or, add below line, if one to many relation
//public virtual IList<Existence> Existence{get; set;}
}
Remove below lines from OnModelCreating method,
modelBuilder.Entity<Existence>().OwnsOne(e => e.Warehouse);
modelBuilder.Entity<Existence>().OwnsOne(e => e.Product);
and you can refer below SO question to correct your identity generation.
How does Entity Framework generate a GUID for a primary key value?

Entity Framework query with simple join

I am using Entity Framework 6 in a project and am having trouble creating a query.
Say my classes are defined like:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString)
{
}
public DbSet<EntityXXX> XXXSet { get; set; }
public DbSet<EntityYYY> YYYSet { get; set; }
}
public class EntityXXX
{
public string XXXName { get; set; }
public int id { get; set; }
public int YYYid { get; set; }
}
public class EntityYYY
{
public string YYYName { get; set; }
public int id { get; set; }
}
The YYYid property of EntityXXX is the 'id' of the EntityYYY instance that it relates to.
I want to be able to fill a Grid with rows where the first Column is XXXName and the second column is YYYName (from its related EntityYYY), but I can't see how to do this?
I'm sure it's really simple, but I'm new to EF.
You need to put a virtual navigation property on your EntityXXX
public virtual EntityYYY YYY { get; set; }
Then you can do a projection:
db.XXXSet
.Select(x => new { x.XXXName, YYYName = x.YYY.YYYName })
.ToList();
Which will get you the list you need.

Add items to an entity collection via ChangeTracker

I chose to put a complete example of my code in order to demonstrate what I exactly need. Briefly, what I would like to do, is to get a generic code in public override int SaveChanges() that could work to all entities that implements a translation rather than write a one-by-one.
ENTITIES
public partial class EntityOne
{
public long EntityOneId { get; set; }
public string SomeProperty { get; set; }
public virtual ICollection<EntityOneTranslation> EntityOneTranslations { get; set; }
public EntityOne()
{
this.EntityOneTranslations = new HashSet<EntityOneTranslation>();
}
}
public class EntityOneTranslation : EntityTranslation<long, EntityOne>
{
public string LocalizedEntityOneProp1 { get; set; }
public string LocalizedEntityOneProp1 { get; set; }
}
public partial class EntityTwo
{
public long EntityTwoId { get; set; }
public string SomeProperty { get; set; }
public virtual ICollection<EntityTwoTranslation> EntityTwoTranslations { get; set; }
public EntityTwo()
{
this.EntityTwoTranslations = new HashSet<EntityTwoTranslation>();
}
}
public class EntityTwoTranslation : EntityTranslation<long, EntityTwo>
{
public string LocalizedEntityTwoProp1 { get; set; }
public string LocalizedEntityTwoProp2 { get; set; }
}
public class EntityTranslation<TEntityKey, TEntity> : ILanguage
{
[Key, Column(Order = 0), ForeignKey("Entity")]
public TEntityKey EntityKey { get; set; }
[Key, Column(Order = 1), ForeignKey("Language")]
public long LanguageId { get; set; }
public virtual TEntity Entity { get; set; }
public virtual Language Language { get; set; }
}
INTERFACE
public interface ILanguage
{
long LanguageId { get; set; }
}
Here is the target
How would I get the entity navigation property using reflection/or something in order to reuse my code that could work too all entities that has a translation property collection?
I already tried to ask the same thing from 2 other posts, but I didn't give all the info. I guess that's why nobody could give me the expected answer.
Adding new entries over entity navigation property collection
Cast PropertyInfo to Collection
SAVE CHANGES OVERRIDE
public override int SaveChanges()
{
foreach (var entityOneEntry in ChangeTracker.Entries<EntityOne>())
{
if (entityOneEntry.State == EntityState.Added)
{
//Get entity localized properties values of current language.
var currentLanguageEntry = entityOneEntry.Entity.EntityOneTranslations.FirstOrDefault();
var localizedEntityOneProp1 = currentLanguageEntry.LocalizedEntityOneProp1;
var localizedEntityOneProp2 = currentLanguageEntry.LocalizedEntityOneProp2;
//Get all languages but the current one.
var languages = Language.Where(l => l.LanguageId != currentCulture.Key);
//Add missing translations copying the same values.
foreach (var language in languages)
entityOneEntry.Entity.EntityOneTranslations.Add(new EntityOne()
{
LanguageId = language.LanguageId,
LocalizedEntityOneProp1 = localizedEntityOneProp1,
LocalizedEntityOneProp2 = localizedEntityOneProp2
});
}
}
foreach (var entityOneEntry in ChangeTracker.Entries<EntityTwo>())
{
if (entityOneEntry.State == EntityState.Added)
{
//Get entity localized properties values of current language.
var currentLanguageEntry = entityOneEntry.Entity.EntityTwoTranslations.FirstOrDefault();
var localizedEntityTwoProp1 = currentLanguageEntry.LocalizedEntityTwoProp1;
var localizedEntityTwoProp2 = currentLanguageEntry.LocalizedEntityTwoProp2;
//Get all languages but the current one.
var languages = Language.Where(l => l.LanguageId != currentCulture.Key);
//Add missing translations copying the same values.
foreach (var language in languages)
entityOneEntry.Entity.EntityTwoTranslations.Add(new EntityTwo()
{
LanguageId = language.LanguageId,
LocalizedEntityTwoProp1 = localizedEntityTwoProp1,
LocalizedEntityTwoProp2 = localizedEntityTwoProp2
});
}
}
}

EF6 Interceptor to set a value on Insert or Update

I am having troubles trying to figure out how to use the EF6 interceptors to set a value on Insert/Update.
What I wanted to do is to have an interceptor to automatically create a new instance of Audit like so:
public class FooContext : DbContext
{
public DbSet<Invoice> Invoices { get; set; }
public DbSet<Audit> Audits { get; set; }
}
public class Invoice
{
public int Id { get; set; }
public string Name { get; set; }
public Audit AuditAndConcurrencyKey { get; set; }
}
public class InvoiceItem
{
public int Id { get; set; }
public Invoice Header { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
//For legacy reasons. I know this design is wrong :(
public Audit AuditAndConcurrencyKey { get; set; }
}
public class Audit
{
public int Id { get; set; }
public int InstanceId { get; set; }
public string Message { get; set; }
}
[Test]
public void WillCreateAudit()
{
using (var db = new FooContext())
{
var inv = new Invoice {Name = "Foo Invoice"};
var invLine = new InvoiceItem {Header = inv, Price = 1, Name = "Apple"};
db.Invoices.Add(inv);
db.SaveChanges();
//Inceptors should figure out that we are working with "Invoice" and "InvoiceLine"
//And automatically create an "Audit" instance
Assert.That(inv.AuditAndConcurrencyKey != null);
Assert.That(invLine.AuditAndConcurrencyKey != null);
Assert.That(inv.AuditAndConcurrencyKey == invLine.AuditAndConcurrencyKey)
}
}
The first thing I checked is this example for SoftDeleteInterceptor. I don't think this is what I want because it looks like at the point where we are already generating the expression tree, we are no longer aware of the type of object you are working with.
I checked this example as well, but again, it looks like we are injecting strings instead of setting object references.
Ideally I want something like this:
public class AuditInterceptor
{
public void Intercept(object obj)
{
if (!(obj is Invoice) && !(obj is InvoiceItem))
return; //not type we are looking for, by-pass
//Set the audit here
}
}

Categories