Closed RMarjanovic closed 2 months ago
Can you also add a definition of base class : Entity
Thank you for looking into this and your quick reply. I managed to reproduce the issue in this repo.
Base class entity added. Fully working test example demonstrating the error in the repo link above.
public abstract class Entity<TId> : IEquatable<Entity<TId>>
where TId : notnull
{
public TId Id { get; private init; }
protected Entity(TId id)
{
Id = id;
}
public static bool operator ==(Entity<TId> first, Entity<TId> second)
{
return first is not null && second is not null && first.Equals(second);
}
public static bool operator !=(Entity<TId> first, Entity<TId> second)
{
return !(first == second);
}
public override bool Equals(object? obj)
{
if (obj is null) return false;
if (obj.GetType() != GetType()) return false;
if (obj is not Entity<TId> entity) return false;
return Id.Equals(entity.Id);
}
public override int GetHashCode()
{
return Id.GetHashCode() * 41;
}
public bool Equals(Entity<TId>? other)
{
if (other is null) return false;
if (other.GetType() != GetType()) return false;
return Id.Equals(other.Id);
}
}
I have copied all files from your project into the source of the library, but getting:
Column, parameter, or variable #5: Cannot specify a column width on data type real.
Could you Fork the library, and add those files there. Then make a like this: (where the issue would be reproducible)
[Fact]
public static void RunTestTemp()
{
using var context = new TestContext(ContextUtil.GetOptions());
var playerId = PlayerId.Create("--wvkCLQSFiG9xpBbTTXBg");
var modStat1 = ModStat.Create("000NeMAdR1Cx6FVye4UV6Q", playerId.Value, StatType.Unitstatmaxhealth, 1, 2, 2, false);
var modStat2 = ModStat.Create("000NeMAdR1Cx6FVye4UV6Q", playerId.Value, StatType.Unitstatmaxhealth, 2, 2, 2, false);
var modStatLis = new List<ModStat>
{
modStat1.Value,
modStat2.Value
};
var mod = Mod.Create("000NeMAdR1Cx6FVye4UV6Q", playerId.Value, "163", ModType.POTENCY, ModRarity.MODRARITY3, ModSlot.ARROW, 1, ModTier.A, 2, modStatLis);
context.Mods.Add(mod.Value);
context.BulkSaveChanges();
}
Could you please advice which file and / or directory you want me to add the test to?
The error you're getting is from a migration done using sql server (I was using postgres) which caused real types to add a width. Ie real(8). I had to manually remove these in the migration to get it to work in sql server.
It can be added into EFCoreBulkTestAtypical.cs
So are using this with Postres or with SqlServer when having the exception? Also if you can strip the example into minimal component that would still reproduce the issue. Currently it has multiple files and classes, probably most of those are no required for the issue.
I have slimmed it down into one file, and removed all unnecessary classes and concepts. It looks like the error occurs when trying to add the one to many with mod->modstat as I had no issue with adding just the mod.
You have a different context than me so I used my own created context in place of your using var context = new TestContext(ContextUtil.GetOptions());
I was using postgres when I first noticed the error, but I have tried different approaches and databases to get this to work, hence why I know why sql server has issues with float and precision.
Added a pull request for the changes.
#region issue1343
[Fact]
public async static void RunTestTemp()
{
using var context = new MyDbContext(ContextUtil.GetOptions());
var playerId = PlayerId.Create("--wvkCLQSFiG9xpBbTTXBg");
var modStat1 = ModStat.Create("000NeMAdR1Cx6FVye4UV6Q", playerId);
var modStatLis = new List<ModStat>
{
modStat1
};
var mod = Mod.Create("000NeMAdR1Cx6FVye4UV6Q", playerId, modStatLis);
await context.Mods.AddAsync(mod);
await context.BulkSaveChangesAsync();
}
public sealed class Mod
{
public ModId Id { get; private set; }
public PlayerId PlayerId { get; private set; }
public IReadOnlyCollection<ModStat> Stats => _stats;
private readonly List<ModStat> _stats = [];
private Mod(ModId id, PlayerId playerId)
{
Id = id;
PlayerId = playerId;
}
public static Mod Create(string id, PlayerId playerId, IEnumerable<ModStat> modStat)
{
var modId = ModId.Create(id);
var mod = new Mod(modId, playerId);
mod.AddStats(modStat);
return mod;
}
private void AddStats(IEnumerable<ModStat> modStats)
{
_stats.AddRange(modStats);
}
}
public sealed class ModStat
{
public ModId ModId { get; init; }
public PlayerId PlayerId { get; init; }
private ModStat(ModId modId, PlayerId playerId)
{
ModId = modId;
PlayerId = playerId;
}
public static ModStat Create(string modId, PlayerId playerId)
{
var mId = ModId.Create(modId);
return new ModStat(mId, playerId);
}
}
public sealed class PlayerId
{
public string Value;
private PlayerId(string value)
=> Value = value;
public static PlayerId Create(string value)
=> new PlayerId(value);
}
public sealed class ModId
{
public string Value;
private ModId(string value)
=> Value = value;
public static ModId Create(string value)
=> new ModId(value);
}
public class MyDbContext(DbContextOptions options) : DbContext(options)
{
public virtual DbSet<Mod> Mods { get; set; } = default!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly);
modelBuilder.Entity<Mod>()
.ToTable("Mods");
modelBuilder.Entity<Mod>()
.HasKey(x => new { x.Id, x.PlayerId });
modelBuilder.Entity<Mod>()
.Property(x => x.Id)
.HasConversion(x => x.Value, v => ModId.Create(v))
.ValueGeneratedNever()
.IsRequired();
modelBuilder.Entity<Mod>()
.Property(x => x.PlayerId)
.HasConversion(x => x.Value, v => PlayerId.Create(v))
.ValueGeneratedNever()
.IsRequired();
modelBuilder.Entity<Mod>()
.HasMany(x => x.Stats)
.WithOne();
modelBuilder.Entity<Mod>()
.Metadata
.FindNavigation(nameof(Mod.Stats))!
.SetPropertyAccessMode(PropertyAccessMode.Field);
modelBuilder.Entity<ModStat>()
.ToTable("ModStat");
modelBuilder.Entity<ModStat>()
.HasKey(x => new { x.ModId, x.PlayerId });
modelBuilder.Entity<ModStat>()
.Property(x => x.ModId)
.HasConversion(x => x.Value, v => ModId.Create(v))
.ValueGeneratedNever()
.IsRequired();
modelBuilder.Entity<ModStat>()
.Property(x => x.PlayerId)
.HasConversion(x => x.Value, v => PlayerId.Create(v))
.ValueGeneratedNever()
.IsRequired();
}
}
#endregion
Fixed with latest commit, new nugget will be published in a few days.
Hey and thank you for the work you've put into this library! This is my first time using it and trying to get it set up in my current applications, please bare with me.
TDLR; when performing a bulk insert/update with only BulkSaveChanges I'm getting an invalid cast exception.
Long version: I'm following domain driven design principles, using strongly typed ids and abstract factory methods to handle conversions. However when the BulkSaveChanges method runs it throws. Original EfCore SaveChanges is working fine. In this case "ModId", "PlayerId" are strings under the hood. Any help with this is highly appreciated.
Full exception:
The domain models has been slimmed down for brevity.
Db configurations
Use case (slimmed down for brevity):
Save changes is run through a pipeline behavior (CompleteAsync), and if changes are above 1000 it uses BulkSaveChanges (without any Config provided) instead of ordinary SaveChanges