I already solved this problem, but I don't understand why this is happening. I would love a more detailed explanation.
For context, I have this class:
public class Test : Entity<Test>
{
public decimal DecimalProp { get; private set; }
}
And the EF mapping/config is like this:
public class TestConfig : IEntityTypeConfiguration<Test>
{
public void Configure(EntityTypeBuilder<Test> builder)
{
builder
.ToTable("Tests");
builder
.Property(t => t.DecimalProp)
.HasDefaultValue(-1)
.HasColumnType("decimal(4,1)")
.IsRequired();
}
}
When I add via EF a new Test with DecimalProp = 0, in SQL it saves as -1, but when I add with another number, like 5, it saves correctly.
I solved the problem using ValueGeneratedNever on the mapping class:
public class TestConfig : IEntityTypeConfiguration<Test>
{
public void Configure(EntityTypeBuilder<Test> builder)
{
builder
.ToTable("Tests");
builder
.Property(t => t.DecimalProp)
.HasColumnType("decimal(4,1)")
.ValueGeneratedNever()
.HasDefaultValue(-1)
.IsRequired();
}
}
My colleagues think that since 0 is the default value for C#'s decimal, EF thinks it should use the default value on SQL which is now -1, but for me it doesn't feel right, since 0 could be a valid value.
Related
I have this fluent configuration for my ApplicationSettings entity
builder.OwnsOne(
applicationSettings => applicationSettings.ActivitySettings,
builder =>
{
builder.Property(activitySettings => activitySettings.PastDaysAllowedThreshold)
.HasColumnName("ActivityPastDaysAllowedThreshold")
.HasDefaultValue(7);
builder.HasCheckConstraint(
"CK_ApplicationSettings_ActivityPastDaysAllowedThreshold",
"ActivityPastDaysAllowedThreshold >= 0");
});
Where ActivitySettings looks like this
public sealed class ActivitySettings // : ValueObject
{
public ActivitySettings(int pastDaysAllowedThreshold)
{
EnsureArg.IsGte(pastDaysAllowedThreshold, 0, nameof(pastDaysAllowedThreshold));
PastDaysAllowedThreshold = pastDaysAllowedThreshold;
}
public int PastDaysAllowedThreshold { get; } // already tried adding a setter.
}
And ApplicationSettings has this method
public void SetActivitySettings(ActivitySettings activitySettings)
{
EnsureArg.IsNotNull(activitySettings, nameof(activitySettings));
ActivitySettings = activitySettings;
RaiseDomainEvent(new ActivitySettingsUpdated(Id, activitySettings));
}
Everything is working perfectly, except for when I set PastDaysAllowedThreshold to 0. I can see that the tracked entity from the repo thinks that the value has been set to 0, but when I look in the database it has not changed.
What is going on
Solved this by updating the config to
builder.OwnsOne(
applicationSettings => applicationSettings.ActivitySettings,
builder =>
{
builder.Property(activitySettings => activitySettings.PastDaysAllowedThreshold)
.HasColumnName("ActivityPastDaysAllowedThreshold")
.HasDefaultValue(7)
.ValueGeneratedNever();
builder.HasCheckConstraint(
"CK_ApplicationSettings_ActivityPastDaysAllowedThreshold",
"ActivityPastDaysAllowedThreshold >= 0");
});
Notice the addition of ValueGeneratedNever()
Before adding this, for some reason ef was generating this in my snapshot
b1.Property<int>("PastDaysAllowedThreshold")
.ValueGeneratedOnAdd() // this was causing the issue
.HasColumnType("int")
.HasDefaultValue(7)
.HasColumnName("ActivityPastDaysAllowedThreshold");
And when profiling those queries when trying to set the value to 0, I could see that no SQL was being executed whatsoever.
Does Entity Framework support generic relations?
E.g.
public class Comment<T> : Entity
where T : Entity
{
public string Text { get; set; }
public long EntityId { get; set; }
public T Entity { get; set; }
}
public class DocumentComment : Comment<Document> {
}
public class DeliveryComment : Comment<Delivery> {
}
UPDATE: I'll expand on my process as I tried this.
I tried creating configurations to make it work as I figured I needed to specify the types of variables but got an error when trying to add the migration.
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(nameof(Comment));
builder.Property(x => x.Id);
builder.HasKey(x => x.Id);
builder.HasOne(x => x.Entity)
.WithMany()
.HasForeignKey(x => x.EntityId)
.OnDelete(DeleteBehavior.ClientCascade)
}
}
public class DocumentCommentConfiguration : CommentConfiguration<DocumentComment,Document>
{
public Configure(EntityTypeBuilder<DocumentComment> builder)
{
}
}
public class DeliveryCommentConfiguration : CommentConfiguration<DeliveryComment,Delivery>
{
public Configure(EntityTypeBuilder<DeliveryComment> builder)
{
}
}
Actually using conventions based setup worked just fine for me, so (at least based on provided info) you don't need to provide IEntityTypeConfiguration's.
If you still want to then (apart from the compilation issues, cause currently provided code is not compliable) you need to fix the table name in CommentConfiguration<TU,T> (by default you can't map different entity types to the same table). For example:
public class CommentConfiguration<TU,T> : IEntityTypeConfiguration<TU>
where TU : Comment<T>
where T : Entity
{
public Configure(EntityTypeBuilder<TU> builder)
{
builder.ToTable(typeof(TU).Name);
// ... rest of the config
}
}
Full code sample used for testing.
Also can be useful - inheritance in EF Core.
I have been tasked with adding audit columns to a specific table.
public interface IAuditedEntity
{
DateTime CreatedAt { get; }
DateTime LastUpdatedAt { get; }
}
public class MyEntity: IAuditedEntity
{
private readonly DateTime _utcNow = DateTime.UtcNow;
public DateTime CreatedAt { get => _utcNow; private set { } }
public DateTime LastUpdatedAt { get => _utcNow; private set { } }
}
I was thinking of filling the values by overriding SaveChangesAsync using ChangeTracker.
One of my co-workers suggests these values should be testable and I am not sure how that can be done with this implementation.
I have also never seen a testable auto-generated field and am wondering what would be the best approach for such (or does this requirement makes sense at all).
Both CreatedBy and LastUpdatedBy should be exactly, and precisely, defined in your test cases. Hence, I believe both can be asserted in the test cases.
Similarly, unless you have some weird timing requirements it should be fairly easy to put constraints on the CreatedAt and LastUpdatedAt fields too.
I would further suggest that if you are on EFCore, then, use in-memory database for easier readback and asserts here.
var now = DateTime.Now;
repository.Insert(entity);
// At this point, I think both the entity itself and if you can read it back from the store should have acceptable values or the test fails.
Assert.IsTrue(entity.CreatedBy == "me");
Assert.IsTrue(entity.LastUpdatedBy == "me");
Assert.IsTrue((now - entity.CreatedAt).TotalMilliseconds <= tolerance);
Assert.IsTrue((now - entity.LastUpdatedAt).TotalMilliseconds <= tolerance);
var newEntity = repository.Get(entity.Id);
// same tests here.
// Repeat similarly for update, and ensure created attributes don't change.
I guess it depends a bit what you mean by auto-generated. I'm not sure how you could test them if they were auto-generated by the database, but if you populate the values in the SaveChangesAsync method, then you could define an interface for the task, and then mock it up in your tests. Something like
public interface IAuditSource
{
public DateTime Now { get; }
public string LoggedOnUser { get; }
}
public class AuditSource : IAuditSource
{
public DateTime Now => DateTime.Now;
public string LoggedOnUser => // however you authenticate
}
public class MyDbContext : DbContext
{
private IAuditSource _auditSource;
public MyDbContext(IAuditSource auditSource): base() {}
public override int SaveChangesAsync()
{
// If Changed Entities are IAuditedEntity
// Set values using _auditSource
}
}
public void MyTestMethod()
{
var auditSource = new Mock<IAuditSource>()
auditSource.Setup(x => x.Now).Returns(new DateTime(2020, 01, 28))
var context = new MyDbContext(auditSource.Object)
}
while no answer satisfies my requirements atm,
as a temporary solution, i have decided to go with the following
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>(entity =>
{
entity.Property(e => e.CreatedAt)
.HasDefaultValueSql("GetUtcDate()")
.ValueGeneratedOnAdd();
entity.Property(e => e.LastUpdatedAt)
.HasDefaultValueSql("GetUtcDate()")
.ValueGeneratedOnAddOrUpdate();
});
base.OnModelCreating(modelBuilder);
}
since values are not manually fed into the entity, there is no need to test it.
if a better answer comes along, i would considering changing it.
In my DbContext class I have the following:
void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<string>().Configure(c => { c.HasMaxLength(250); });
}
but also inside the EntityTypeConfiguration class for a specific model I have:
{
Property(p => p.Name).HasMaxLength(500);
}
The problem is that setting the property to 500 inside the Configuration class is not taking effect and EF is still validating the max to be 250.
How can I have a general setting of 250 but override by fluent api as needed inside each class?
#Lutti Coelho: My mistake
Add code below in OnModelCreating method
modelBuilder.Entity<Student>()
.Property(p => p.StudentName)
.MaxLength(500);
If you want to give a string field 500 characters.
You can do this by using the "MaxLength" attribute.
I am creating a sample table like the following.
My Context Configuration.
void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<string>().Configure(c => { c.HasMaxLength(250); });
}
I am creating a table named FooEntity.
using System;
using System.ComponentModel.DataAnnotations;
namespace Foo.Model
{
public class FooEntity
{
[MaxLength(500)]
public string FooName { get; set; }
}
}
After adding this attribute, the migration output is as follows.
Even if a 250 character limit is given, it will be passed as 500 characters in SQL.
CreateTable(
"dbo.FooEntity",
c => new
{
FooName = c.String(maxLength: 500)
});
I'm trying to use Entity Framework code first to try to map a simple lookup table to a heirarchy of types and want to use the primary key of the table as the discriminator column for a "table per heirarchy" entity. More acturately, I'm trying to make this work against an existing database.
Here is a contrived sample app I put together trying to figure out if I can make it work or not:
using System;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
namespace EfTest
{
public abstract class Base
{
public int LookupId { get; set; }
public String Name { get; set; }
public abstract String GetTest();
}
public class Derived1 : Base
{
public override string GetTest() { return Name + "1"; }
}
public class Derived2 : Base
{
public override string GetTest() { return Name + "2"; }
}
public class Derived3 : Base
{
public override string GetTest() { return Name + "3"; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer<MyContext>(new DropCreateDatabaseIfModelChanges<MyContext>());
using(var context = new MyContext())
{
foreach (var item in context.Lookups)
{
Console.WriteLine("{0} {1} {2}", item.LookupId, item.Name, item.GetType().FullName);
}
}
Console.ReadKey();
}
}
public class MyContext : DbContext
{
public DbSet<Base> Lookups { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var config = modelBuilder.Entity<Base>();
config.HasKey(e => e.LookupId).ToTable("dbo.Lookup");
config.Property(e => e.LookupId)
.HasColumnName("LookupId")
.IsRequired()
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
config.Property(e => e.Name)
.IsRequired()
.HasColumnName("Name")
.HasMaxLength(32)
.HasColumnType("varchar");
config.Map<Derived1>(e => e.Requires("LookupId").HasValue(1).IsRequired());
config.Map<Derived2>(e => e.Requires("LookupId").HasValue(2).IsRequired());
config.Map<Derived3>(e => e.Requires("LookupId").HasValue(3).IsRequired());
//config.Map<Derived1>(e => e.Requires("Name").HasValue("Item1").IsRequired());
//config.Map<Derived2>(e => e.Requires("Name").HasValue("Item2").IsRequired());
//config.Map<Derived3>(e => e.Requires("Name").HasValue("Item3").IsRequired());
}
}
}
However, this raises an exception stating:
Problem in mapping fragments starting at line 24:Condition member
'Base.LookupId' with a condition other than 'IsNull=False' is mapped.
Either remove the condition on Base.LookupId or remove it from the
mapping.
I've also tried discrimiating with the "Name" column with similar results.
The errors look like it complaining that I'm trying to map to nullable column in the database, however, the table that actually gets created has both columns marked not null, as I would expect.
Is what I'm trying to do simply not supported by EF?
This is not possible. Each column can have only single special purpose in the mapping. Having a column as a key and discriminator are two special purposes. Discriminator column has special meaning of selecting the correct type to be materialized and because if that it must not be present in mapped entity (you cannot set it from your application). Also having discriminator on column which must be unique is incorrect.