1

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).

ZeW
  • 163
  • 2
  • 19
  • ORMs don't deal with triggers. Just add the `AFTER UPDATE trigger` to your table. If you insist on using EF, add the trigger in the migration script. ORMs are *not* SQL or database replacements though. Their job is to Map Objects to Relational tables. Their migration facilities are meant for development or really small schemas. – Panagiotis Kanavos Dec 23 '19 at 09:19
  • [Check this similar question](https://stackoverflow.com/questions/21731889/add-database-trigger-with-entity-framework-code-first-migrations) on how to use migrations to generate triggers. – Panagiotis Kanavos Dec 23 '19 at 09:21
  • Thanks, I just need it for this one thing this time, so I'll keep with the manual Update statement rather than creating a trigger. But just so I know in the future, would this trigger need to be added to every migration going forward if it's in the migration script? – ZeW Dec 23 '19 at 10:11
  • 1
    What version of `Pomelo.EntityFramework.MySql` are you using? We recently fixed a couple of issue about `CURRENT_TIMESTAMP()` for 3.0.0 and 3.1.0. See our [GitHub repo](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/issues?utf8=%E2%9C%93&q=is%3Aissue+current_timestamp+) for further details. – lauxjpn Dec 23 '19 at 21:30
  • 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 – ZeW Jan 03 '20 at 14:15

1 Answers1

5

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.

lauxjpn
  • 4,749
  • 1
  • 20
  • 40
  • From the issue ticket, it looks like it should be ... `.HasDefaultValueSql("'CURRENT_TIMESTAMP'").ValueGeneratedOnAddOrUpdate();` – ZeW Jan 03 '20 at 14:23
  • @ZeW No, the answer here should be the same as the example code I had posted for the [#959](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/959) PR, which is `HasDefaultValueSql("CURRENT_TIMESTAMP")` (without single quotes) and should be correct. In older versions of Pomelo, single quotes where added, that are correct for most default values, but not for the special case of `CURRENT_TIMESTAMP`. See the [MySQL docs](https://dev.mysql.com/doc/refman/8.0/en/timestamp-initialization.html) for further information. – lauxjpn Jan 04 '20 at 12:57
  • If you want to have the column's value automatically updated by the database when updating the row (and not just when creating it), then `ValueGeneratedOnAddOrUpdate()` needs to be added (this is also covered by the [#959](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/pull/959) sample code, but I will add it to my answer here as well). There is a bug in EF Core < 3.1.0, where `ValueGeneratedOnUpdate()` will not generate correct C# code (but an empty function call without parenthesis). But in most use cases, it will be good enough to use `ValueGeneratedOnAddOrUpdate()`. – lauxjpn Jan 05 '20 at 03:00
  • Yes, that was added automatically by me having that annotation `[DatabaseGenerated(DatabaseGeneratedOption.Computed)]`, but as I'm using a version of EF Core < 3.1.0 I guess it's not working. But now I know that it's a bug and I'm not doing something wrong – ZeW Jan 06 '20 at 13:16