EF Core DatabaseGeneratedOption.Computed not working - c#

I've recently started working with .NET CORE v2.
I'm trying to set up my database by using a code-first approach in my web-api template.
Background: I've previously worked with the Laravel framework and I would like to replicate laravel's timestamp() function in migration files which basically creates two columns: UpdatedAt and CreatedAt in a table.
The values in those columns are populated with the correct values when the ORM's (Elqoquent) functions that INSERT or UPDATE columns are used. It's seamless and you don't need to worry about it.
In my c# code I have the following model
public class A {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime Created_At { get; set; } = DateTime.UtcNow;
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Updated_At { get; set; }
// I've tried to add "= DateTime.UtcNow;" at the end of Updated_At as well
}
I also have the following class that is used to seed the database.
public static class DatabaseSeeder
{
private static AppContext _context;
public static void SeedDatabase(this AppContext appContext)
{
_context = appContext;
_context.Database.EnsureCreated();
// Verify if data exist.
if (_context.A.Any())
{
return;
}
SeedAs();
}
private static void SeedAs()
{
var defaults = new A[]
{
new A
{
Name = "Value 1",
},
new A
{
Name = "Value 2",
},
new A
{
Name = "Value 3",
}
};
_context.As.AddRange(defaults);
_context.SaveChanges();
}
}
The SeedDatabase function is called in the Configure function of the Startup class which basically seeds the database should it not contain any data at startup.
Problem: The issue I am encoutering is that when I launch my development server the first time, the application notices that the database does not contain any values so it tries to seed it. The following error is returned:
MySql.Data.MySqlClient.MySqlException (0x80004005): Field 'Updated_At'
doesn't have a default value
I don't seem to understand why this fails because when I ran the same piece of code by removing the Updated_At property and its annotation, no error was returned and the database was seeded as expected with the Created_At field containing the value of DateTime.UtcNow.
The expected behavior of the DatabaseGenerated(DatabaseGeneratedOption.Computed annotation is to have a database generated value based on the data type of the property on insert or update.
Can anyone tell me why, it fails when I try to seed my database.

At first, you have already defined the initial value for Created_At in your class: = DateTime.UtcNow;
That's why it works for Created_At but does not work for Updated_At. Since Updated_At does not have an initial value and the Database does not have any default value defined for this column.
To make it work right, you have to define the default values for your SQL Columns in the DbContext Class.
I guess, with MySQL you should use the function NOW():
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<A>()
.Property(s => s.Created_At )
.HasDefaultValueSql("NOW()");
modelBuilder.Entity<A>()
.Property(s => s.Updated_At )
.HasDefaultValueSql("NOW()");
}
Docs: Data Annotations - DatabaseGenerated Attribute in EF 6 & EF Core

Related

How to link non-primitive field (PK) with primitive key (FK) in EF Core via fluent API [duplicate]

I've been trying to use a value object as a unique id in entity framework core. I've gotten to the point where it is saving to the database correctly, but EF is not querying the database correctly to find my entity. It looks like EF is loading the whole table, then doing the mapping in memory. When I look at the query it does not include a predicate for the id.
I'm dealing with an existing database that uses 10 character string ids with padded zeros so I'm seeing if working with them as value objects is going to work. My next thing to try is just use a Guid and have the 'SalesOrderNumber' as a separate field. That's just for this case though, what I'm really trying to figure out is if it is possible to use a value object as an primary key in entity framework.
Entity:
public class SalesOrder: Entity<SalesOrderNumber>
{
private SalesOrder() { }
public SalesOrder(SalesOrderNumber id, DateTime dueDate)
{
Id = id;
DueDate = dueDate;
Open = true;
}
public override SalesOrderNumber Id { get; protected set; }
public DateTime DueDate { get; private set; }
public bool Open { get; private set; }
}
Value Object:
public class SalesOrderNumber: ValueObject
{
private readonly string _salesOrderNumber;
public SalesOrderNumber(string salesOrderNumber)
{
if (string.IsNullOrWhiteSpace(salesOrderNumber) || salesOrderNumber.Length > 10)
throw new InvalidOperationException($"Sales Order Number {salesOrderNumber} is invalid");
_salesOrderNumber= salesOrderNumber;
}
protected override IEnumerable<object> GetAtomicValues()
{
yield return _salesOrderNumber;
}
public override string ToString() => _salesOrderNumber;
}
DB Config:
public void Configure(EntityTypeBuilder<SalesOrder> builder)
{
builder.HasKey(e => e.Id);
builder.Property(e => e.Id).HasConversion(number => number.ToString(), s => new SalesOrderNumber(s));
}
I've reviewed some other SO posts but none of them have addressed the query issue I've run into:
EF Core / DbContext > Map custom type as primary key
The performance problem you laid out seems to be an open issue reported on github:
EF Core non-primitive type value object as primary key?
Someone also reported a quick fix:
https://github.com/aspnet/EntityFrameworkCore/issues/13669#issuecomment-439589393
If I understood the quick fix right, adding implicit conversion operator from the value object to the backing primitive type, and an implicit conversion operator for the backward direction, may resolve your issue. Please leave a comment if this works for you.

Querying a view in SQL through Entity Framework Core

I am trying to query an already existing view in SQL Server which I am connected to in Visual Studio 2019.
I've created a class corresponding to the name of my Users database called UsersDbContext.:
class UsersDbContext : DbContext
{
public DbSet<EventView> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Data Source=REDACTED;User ID=REDACTED;Password=REDACTERD;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False");
}
}
In this database I have a view named eventview.fss.
How do I access the data from here from Entity Framework?
In my main class where I want to process the data:
private static readonly UsersDbContext _context = new UsersDbContext();
var myid = _context.Users.FromSqlRaw($"SELECT Id FROM eventview.fss");
In my models class I've added a model for the view. It is very simple:
public class EventView
{
public string Date { get; set; }
public string Id { get; set; }
}
What am I missing? Ideally I'd like to have the the rows from the view be put into a List<EventView> then I can work with the data from there.
Edit: when I try to run _context.Users.FromSqlRaw($"SELECT Id FROM eventview.fss").ToList(); I get the error for SqlException: Invalid object name eventview.fss
You can tell EF Core how to retrieve the data set so you don't need to use raw sql. Add a dbset in the context. In OnModelCreating, something like:
builder.Entity().HasNoKey().ToView("eventview.fss", "YourSchemaName");
My problem was that my connection string was missing the Database property. Once I've added it now I can pull the data.

C# Pomelo mySQL trigger onUpdate Timestamp

I'm trying to create a log table in Pomelo.MySQL which has an onUpdate Timestamp, but I can't seem to trigger it with Entity Framework.
This is my model for the table
public class OrganisationLog
{
[Key]
public int Id { get; set; }
[Column(TypeName = "VARCHAR(1024)")]
[Required(AllowEmptyStrings = false)]
public string MachineName { get; set; }
[DefaultValue("CURRENT_TIMESTAMP")]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime LastContact { get; set; }
public int OrganisationId { get; set; }
[ForeignKey("OrganisationId")]
public Organisation Organisation { get; set; }
}
And below is the function that should work.
private void UpdateOrganisationLog(Organisation organisation, string machineName)
{
try
{
OrganisationLog organisationLog = _context.OrganisationLogs
.Where(x => x.OrganisationId == organisation.Id && x.MachineName == machineName)
.FirstOrDefault();
if (organisationLog == null)
{
organisationLog = new OrganisationLog()
{
MachineName = machineName,
OrganisationId = organisation.Id,
LastContact = DateTime.Now
};
_context.OrganisationLogs.Add(organisationLog);
}
else
{
_context.Update(organisationLog);
}
_context.SaveChanges();
}
catch (Exception e)
{
Console.WriteLine("Error " + e.Message);
}
}
I ended up making it work with a manual SQL statement, but I want to figure it out through Entity Framework.
_context.Database.ExecuteSqlCommand($"UPDATE organisationlogs SET LastContact = CURRENT_TIMESTAMP(6) WHERE Id = {organisationLog.Id}");
Could it have something to do with CURRENT_TIMESTAMP(6) rather than CURRENT_TIMESTAMP()? Not sure why Entity Framework has made it as (6).
According to the EF Core docs on Default Values, data annotations are not supported:
You can not set a default value using Data Annotations.
If it would have been supported by EF Core, than using it for CURRENT_TIMESTAMP would probably still not have worked, because it is not a System.DateTime value, but technically a SQL fragment.
In your case, a FluentAPI configuration like the following, that uses .HasDefaultValueSql() to specify the SQL fragment, should work for Pomelo 3.0.1+:
class MyContext : DbContext
{
// ...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrganisationLog>(entity =>
{
entity.Property(e => e.LastContact)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
});
}
}
The DatabaseGenerated(DatabaseGeneratedOption.Computed) attribute should not be necessary.
If you want to have the value not just generated on creation, but also updated automatically when changing the table row, use the following model definition instead:
class MyContext : DbContext
{
// ...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OrganisationLog>(entity =>
{
entity.Property(e => e.LastContact)
.ValueGeneratedOnAddOrUpdate();
});
}
}
In case you only want the value to be updated when changing the table row, but not when creating it, you can use ValueGeneratedOnUpdate() with EF Core 3.1.0.
There is a bug in EF Core < 3.1.0, where ValueGeneratedOnUpdate() will not generate correct C# code. This should not be an issue for most people, because lifetime support for EF Core 3.0.0 is very limited anyway (and as mentioned above, the feature is only supported by Pomelo since 3.0.1). If you need a workaround for 3.0.1 >= Pomelo < 3.1.0 anyway, then using ValueGeneratedOnAddOrUpdate() instead will work for most use cases.
See #959 on our GitHub repo for the fix that implemented support for datetime columns in conjunction with CURRENT_TIMESTAMP and for further details.
I'm using Pomelo.EntityFramework.MySql version 2.1.4. I'm hosting this with Elastic Beanstalk so I need to use an older version of dotnet
Everything above is not going to work correctly for Pomelo 2.1.4 (using a timestamp or timestamp(6) column might work, but you would need to manually change the DEFAULT statement to remove the single quotes, in case you scaffold the database). But you can always just change the table definition as a workaround.
If you are using migrations, the following line (or something similar) can be added to an Up() method for example:
migrationBuilder.Sql("ALTER TABLE `OrganisationLog` CHANGE COLUMN `LastContact` datetime(6) CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;");
Not ideal, but it should do its job for older Pomelo versions.

"Cannot insert explicit value for identity column in table" when inserting object with EF Core

I am trying to insert a new row to my table, using EF Core, SQL Server and C#, but I am having trouble getting EF Core to use the identity column properly.
Here's what I am doing:
I am creating a new object using the Entity Framework generated class (I've included the entity class definition at the end of my post)
EmployeePermissions employee_permission = new EmployeePermissions
{
FkEmployee = PkEmployee,
FkPermission = permission_key
};
Then I call db.EmployeePermissions.add(employee_permission), which works on all of my calls where an object comes from [FromBody] <Entity Class> <object_variable> (albeit using other tables).
But here, when I instantiate the class myself, I get this error:
SqlException (0x80131904): Cannot insert explicit value for identity column in table 'Employee_Permissions' when IDENTITY_INSERT is set to OFF.
I don't understand why this is happening — I want it to auto increment the identity column. I used ObjectDumper to see what is getting passed to .Add(), which is as follows:
properties {
PkEmployeePermissions = 0 [System.Int32] // this is the identity column, of course
FkEmployee = 31 [System.Int32]
FkPermission = 6 [System.Int32]
FkEmployeeNavigation = <null>
FkPermissionNavigation = <null>
}
I have investigated the other calls which are working fine (the ones where [FromBody] creates an object) and the identity column simply equals 0 in those calls too, so I don't understand what is different here.
Have I misconfigured something in the database? I have double checked in the column properties that the column PkEmployeePermissions is indeed an identity column, so it should be auto incremented.
Here's the Entity class if it helps:
public partial class EmployeePermissions
{
public int PkEmployeePermissions { get; set; }
public int FkEmployee { get; set; }
public int FkPermission { get; set; }
public Employee FkEmployeeNavigation { get; set; }
public Permission FkPermissionNavigation { get; set; }
}
It turned out I had updated the primary key column, PkEmployeePermission to be an identity column and forgotten to re-generate the database scaffolding using EF Core.
The root cause of the problem was that inside of the OnModelCreating() method (in the generated database context file) the field PkEmployeePermission had the method .ValueGeneratedNever() called on it, which meant that even though it was a primary key, EF Core did not automatically generate an incremented value for that column. By commenting out that method in the database context file, the code worked properly.
The default convention is for the primary key of a class to be called either Id or (classname)Id - this is not the case in your class.
Also: if you're mapping to an IDENTITY column in SQL Server, you must add a relevant data annotation to the key column.
Try this:
public partial class EmployeePermissions
{
// add the [Key] annotation to indicate the primary key of the class/table
[Key]
// add the [DatabaseGenerated(..)] annotation to indicate an IDENTITY column in the table
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PkEmployeePermissions { get; set; }
public int FkEmployee { get; set; }
public int FkPermission { get; set; }
public Employee FkEmployeeNavigation { get; set; }
public Permission FkPermissionNavigation { get; set; }
}
Now you should be able to insert those objects, and have them stored properly in your SQL Server table.

EF Seed ignoring the DatabaseGeneratedOption.Computed attribute

I have a problem with the attribute DatabaseGenerate(DatabaseGeneratedOption.Computed), it seems to be the case that it is not respected by the AddOrUpdate() call within the Seed method.
I have made a simple project, to illustrate my issue:
public class EFModel : DbContext
{
public EFModel()
: base("name=EFModel")
{
}
public virtual DbSet<MyEntity> MyEntities { get; set; }
}
public class MyEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[StringLength(50)]
public string Name { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[StringLength(50)]
public string DefaultName { get; set; }
}
The Seed call
protected override void Seed(DummyEF.EFModel context)
{
context.MyEntities.AddOrUpdate(new MyEntity { Id = 10, Name = "Samual", DefaultName = "Sam" });
context.MyEntities.AddOrUpdate(new MyEntity { Id = 11, Name = "David" });
}
When I run the Update-Database command, with the first Seed row (that is with Samual, and specifying a value for the default name) it works fine. When I run it with the second line (that is with Dadid, and without specifying a value for the DefaultName) it fails:
Validation failed for one or more entities. See
'EntityValidationErrors' property for more details.
The table it self is valid, and has a default constraint, so normal insert into via SQL works.
It just seems to be the case, that the Seed is ignoring the fact that
the entity property is marked with the
DatabaseGeneratedOption.Computed attribute.
Any idea why this is ignored?
I am using EF 6 code first.
When you use DatabaseGeneratedOption.Computed you are telling EF not to add or update the information in the database because the database will generate it as is common for timestamps or computed SQL columns, so it makes no sense to try and seed that column. You are getting the validation error because you have it marked required.
The conclusion must be that the current version of EF 6 does not cater for the combination of the attriburtes Required and DatabaseGenerated(DatabaseGeneratedOption.Computed)
So if one wants to set a column to not be nullable, yet avoid EF from validating it before the save to the database, one cannot use the Required attribute.
I guess that the Fluent API or modification of the migration code needs to be used in stead.

Categories