I'm updating two owned types using In-Memory Database Provider and works fine. When I use SQLite Database Provider I'm getting an exception.
Add-Migration Initial -Verbose
Update-Database -Verbose
Set Copy to output Directory to Copy if newer for newly created MyDatabase.db
Run the program
``` C#
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace OwnedTypesTest
{
public class Material
{
public int Id { get; set; }
public string Name { get; set; }
public List
private Material() { }
public Material(string name, IEnumerable<MaterialLocationDto> materialLocationDtos)
{
Name = name;
MaterialLocations = materialLocationDtos?.Select(ml => new MaterialLocation(this, ml.Location, ml.RewardAmount, ml.TransportationRewardAmount, ml.TransportationRewardMinimumAmount)).ToList();
}
public void Update(string name, IEnumerable<MaterialLocationDto> materialLocationDtos)
{
Name = name;
MaterialLocations = materialLocationDtos?.Select(ml => new MaterialLocation(this, ml.Location, ml.RewardAmount, ml.TransportationRewardAmount, ml.TransportationRewardMinimumAmount)).ToList();
}
}
public class MaterialLocationDto
{
public Location Location { get; set; }
public decimal? RewardAmount { get; set; }
public decimal? TransportationRewardAmount { get; set; }
public int? TransportationRewardMinimumAmount { get; set; }
}
public class Location
{
public int Id { get; set; }
public string Name { get; set; }
public List<MaterialLocation> MaterialLocations { get; set; }
private Location() { }
public Location(string name, IEnumerable<LocationMaterialDto> locationMaterialDtos)
{
Name = name;
MaterialLocations = locationMaterialDtos?.Select(lm => new MaterialLocation(lm.Material, this, lm.RewardAmount, lm.TransportationRewardAmount, lm.TransportationRewardMinimumAmount)).ToList();
}
public void Update(string name, IEnumerable<LocationMaterialDto> locationMaterialDtos)
{
Name = name;
MaterialLocations = locationMaterialDtos?.Select(lm => new MaterialLocation(lm.Material, this, lm.RewardAmount, lm.TransportationRewardAmount, lm.TransportationRewardMinimumAmount)).ToList();
}
}
public class LocationMaterialDto
{
public Material Material { get; set; }
public decimal? RewardAmount { get; set; }
public decimal? TransportationRewardAmount { get; set; }
public int? TransportationRewardMinimumAmount { get; set; }
}
public class MaterialLocation
{
public int MaterialId { get; protected set; }
public Material Material { get; protected set; }
public int LocationId { get; protected set; }
public Location Location { get; protected set; }
public Reward Reward { get; protected set; }
public TransportationReward TransportationReward { get; protected set; }
private MaterialLocation() { }
public MaterialLocation(Material material, Location location, decimal? rewardAmount, decimal? transportationRewardAmount, int? transportationRewardMinmumAmount)
{
Material = material;
Location = location;
Reward = new Reward(rewardAmount);
TransportationReward = new TransportationReward(transportationRewardAmount, transportationRewardMinmumAmount);
}
}
// Owned type
public class Reward
{
public decimal? Amount { get; protected set; }
private Reward() { }
public Reward(decimal? amount)
{
Amount = amount;
}
}
// Owned type
public class TransportationReward
{
public decimal? Amount { get; protected set; }
public int? MinimumAmount { get; protected set; }
private TransportationReward() { }
public TransportationReward(decimal? amount, int? minimumAmount)
{
Amount = amount;
MinimumAmount = minimumAmount;
}
}
public class MyDbContext : DbContext
{
public DbSet<Material> Materials { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<MaterialLocation> MaterialLocations { get; set; }
public MyDbContext() { }
public MyDbContext(DbContextOptions options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MaterialLocation>().HasKey(ml => new { ml.MaterialId, ml.LocationId });
modelBuilder.Entity<MaterialLocation>().OwnsOne(ml => ml.Reward);
modelBuilder.Entity<MaterialLocation>().OwnsOne(ml => ml.TransportationReward);
}
}
// Used for SQLite initial migration
public class MyDbContextFactory : IDesignTimeDbContextFactory<MyDbContext>
{
public MyDbContext CreateDbContext(string[] args)
{
DbContextOptionsBuilder<MyDbContext> optionBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionBuilder.EnableSensitiveDataLogging();
optionBuilder.UseSqlite("Data Source=MyDatabase.db");
return new MyDbContext(optionBuilder.Options);
}
}
class Program
{
static async Task Main()
{
ServiceCollection services = new ServiceCollection();
//services.AddDbContext<MyDbContext>(options => options.UseInMemoryDatabase("MyDatabase"));
services.AddDbContext<MyDbContext>(options => options.UseSqlite("Data Source=MyDatabase.db").EnableSensitiveDataLogging());
using (ServiceProvider serviceProvider = services.BuildServiceProvider())
{
int locationId, material1Id, material2Id;
using (IServiceScope scope = serviceProvider.CreateScope())
{
MyDbContext context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
Material material1 = new Material("Material1", null);
Material material2 = new Material("Material2", null);
Location location = new Location("Location1",
new LocationMaterialDto[]
{
new LocationMaterialDto { Material = material1, RewardAmount = 10.23M, TransportationRewardAmount = 5.25M, TransportationRewardMinimumAmount = 10 },
new LocationMaterialDto { Material = material2 },
});
context.Materials.Add(material1);
context.Materials.Add(material2);
context.Locations.Add(location);
context.SaveChanges();
locationId = location.Id;
material1Id = material1.Id;
material2Id = material2.Id;
}
using (IServiceScope scope = serviceProvider.CreateScope())
{
MyDbContext context = scope.ServiceProvider.GetRequiredService<MyDbContext>();
Location location = await context.Locations.Include(m => m.MaterialLocations).ThenInclude(ml => ml.Material).FirstOrDefaultAsync(l => l.Id == locationId);
Material material1 = await context.FindAsync<Material>(material1Id);
Material material2 = await context.FindAsync<Material>(material2Id);
foreach (MaterialLocation materialLocation in location.MaterialLocations)
{
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Reward amount before changing: {materialLocation.Reward?.Amount}");
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Transportation reward amount before changing: {materialLocation.TransportationReward?.Amount}");
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Transportation reward minimum amount before changing: {materialLocation.TransportationReward?.MinimumAmount}");
}
Console.WriteLine();
LocationMaterialDto[] locationMaterialDtos = new LocationMaterialDto[]
{
new LocationMaterialDto
{
Material = material1,
RewardAmount = location.MaterialLocations[0].Reward?.Amount,
TransportationRewardAmount = location.MaterialLocations[0].TransportationReward?.Amount,
TransportationRewardMinimumAmount = location.MaterialLocations[0].TransportationReward?.MinimumAmount
},
new LocationMaterialDto
{
Material = material2,
RewardAmount = 7.88M,
TransportationRewardAmount = 1.22M,
TransportationRewardMinimumAmount = 2
}
};
location.Update(location.Name, locationMaterialDtos);
foreach (MaterialLocation materialLocation in location.MaterialLocations)
{
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Reward amount before saving changes: {materialLocation.Reward.Amount}");
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Transportation reward amount before saving changes: {materialLocation.TransportationReward.Amount}");
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Transportation reward minimum amount before saving changes: {materialLocation.TransportationReward.MinimumAmount}");
}
Console.WriteLine();
await context.SaveChangesAsync();
foreach (MaterialLocation materialLocation in location.MaterialLocations)
{
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Reward amount after saving changes: {materialLocation.Reward.Amount}");
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Transportation reward amount after saving changes: {materialLocation.TransportationReward.Amount}");
Console.WriteLine($"({materialLocation.Location.Name} {materialLocation.Material.Name}) - Transportation reward minimum amount after saving changes: {materialLocation.TransportationReward.MinimumAmount}");
}
Console.WriteLine();
Console.ReadLine();
}
}
}
}
}
### Expected
In-Memory Database Provider
(Location1 Material1) - Reward amount before changing: 10.23
(Location1 Material1) - Transportation reward amount before changing: 5.25
(Location1 Material1) - Transportation reward minimum amount before changing: 10
(Location1 Material2) - Reward amount before changing:
(Location1 Material2) - Transportation reward amount before changing:
(Location1 Material2) - Transportation reward minimum amount before changing:
(Location1 Material1) - Reward amount before saving changes: 10.23
(Location1 Material1) - Transportation reward amount before saving changes: 5.25
(Location1 Material1) - Transportation reward minimum amount before saving changes: 10
(Location1 Material2) - Reward amount before saving changes: 7.88
(Location1 Material2) - Transportation reward amount before saving changes: 1.22
(Location1 Material2) - Transportation reward minimum amount before saving changes: 2
(Location1 Material1) - Reward amount after saving changes: 10.23
(Location1 Material1) - Transportation reward amount after saving changes: 5.25
(Location1 Material1) - Transportation reward minimum amount after saving changes: 10
(Location1 Material2) - Reward amount after saving changes: 7.88
(Location1 Material2) - Transportation reward amount after saving changes: 1.22
(Location1 Material2) - Transportation reward minimum amount after saving changes: 2
SQLite Database Provider
System.InvalidOperationException
HResult=0x80131509
Message=The instance of entity type 'Reward' with the key value '{MaterialLocationMaterialId: 1, MaterialLocationLocationId: 1}' is marked as 'Added', but the instance of entity type 'MaterialLocation' with the key value '{MaterialId: 1, LocationId: 1}' is marked as 'Modified' and both are mapped to the same row.
Source=Microsoft.EntityFrameworkCore.Relational
StackTrace:
at Microsoft.EntityFrameworkCore.Update.ModificationCommand.AddEntry(IUpdateEntry entry)
at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.CreateModificationCommands(IList1 entries, IUpdateAdapter updateAdapter, Func1 generateParameterName)
at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__95.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.<SaveChangesAsync>d__93.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter1.GetResult()
at Microsoft.EntityFrameworkCore.DbContext.
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at OwnedTypesTest.Program.
```
EF Core version: 3.0.0-rc1.19456.14
Database provider: Microsoft.EntityFrameworkCore.InMemory and Microsoft.EntityFrameworkCore.Sqlite
Target framework: .NET Core 3.0.100-rc1-014190
Operating system: Windows 10
IDE: Visual Studio 2019 16.2.5
I just installed .NET Core 3.1.100-preview1-014459 and updated all EF Core packages to 3.1.0-preview1.19506.2.
Run the program and everything is working fine now.
Thanks @AndriySvyryd
Is there no workaround for this?
@Neme12 I couldn't find any workarounds.
The only thing that saved me was to upgrade to .NET Core 3.1 preview1 and EF Core 3.1 preview 1.
I don't get it. Nullable owned entity types are a new feature but when I simply assign to it to make it non-null, I get an exception...? This is the most basic usage I could think of. If this doesn't work... then what does? Owned entity types are now completely unusable 馃槥 And it's not like I have a choice in making them nullable - it didn't used to be null, and now it is, and I can't make it not null?
Is there really going to be no fix for this for 3.0? @AndriySvyryd
@Neme12 For entities that own an entity and can be null in some cases (due all properties nullable) a woraround is to set entry state to Modified when changing it.
public class Parent {
public Child Child1 {get;set;}
public Child Child2 {get;set;}
}
public class Child
{
public string Name {get;}
}
// modifiy
var parent = context.Parents.FirstOrDefault();
parent.Child2 = new Child();
context.Entry(parent.Child2).State = EntityState.Modified; // if child2 is null then this ensures that it will not have the state added
Most helpful comment
I don't get it. Nullable owned entity types are a new feature but when I simply assign to it to make it non-null, I get an exception...? This is the most basic usage I could think of. If this doesn't work... then what does? Owned entity types are now completely unusable 馃槥 And it's not like I have a choice in making them nullable - it didn't used to be null, and now it is, and I can't make it not null?
Is there really going to be no fix for this for 3.0? @AndriySvyryd