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.
Related
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.
Entity Framework is not respecting my Identity columns. It insists on trying to insert a value into an Identity (auto-increment) column in my MS SQL DB, which is obviously an error since the DB is supposed to supply the value.
System.Data.SqlClient.SqlException: 'Cannot insert explicit value for identity column in table 'Assignee' when IDENTITY_INSERT is set to OFF.'
Why is it trying to do that? I've paired it down to a schema involving one table and one column:
CREATE TABLE [dbo].[Assignee](
[AssigneeID] INT IDENTITY(-1, 1) NOT NULL
CONSTRAINT [Assignee$PrimaryKey] PRIMARY KEY CLUSTERED
( [AssigneeID] ASC ))
After publishing this schema to my local DB I use Scaffold-DbContext to generate entity and context classes. The generated Assignee class contains just this public property.
public int AssigneeId { get; set; }
The context only refers to Assignee here:
modelBuilder.Entity<Assignee>(entity =>
{
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
});
Searching around I see people claiming that for E.F. to respect Identity columns, the context should configure the property with ValueGeneratedOnAdd(). In other words, the line in the context class should read:
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID")
.ValueGeneratedOnAdd();
I have two problems with this:
I'm starting with an existing DB and generating entity classes. If I need ValueGeneratedOnAdd() then why isn't Scaffold-DbContext generating it?
Even if I manually edit the generated context class and add ValueGeneratedOnAdd() it still doesn't work with the same error.
Elsewhere I see suggestions to use UseSqlServerIdentityColumn(). That also doesn't work for me. Points 1 and 2 still apply.
Any help would be greatly appreciate. Please don't suggest that I use IDENTITY_INSERT as that defeats the entire point of using auto-increment columns.
(I am using Entity Framework Core 2.2.3 and Microsoft SQL Server 14)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Todo>(entity =>
{
entity.Property(x => x.Id)
.HasColumnName("id")
.HasColumnType("int")
.ValueGeneratedOnAdd()
**.UseIdentityColumn();**
}
Try do this.
Ef Core Dependency : Microsoft.EntityFrameworkCore.SqlServer
This works for me:
modelBuilder.Entity<Assignee>().Property(e => e.AssigneeId).UseIdentityColumn();
So UseIdentityColumn() is the key.
I'm using Microsoft.EntityFrameworkCore.SqlServer v3.1.8.
Short version
We are getting and experiencing different results here one can reproduce the issue, others can not. My experience it depends on if the Id property's value is 0 or not.
Detailed version
My experience, that the default behavior (based on name convention) is definitely working, so in case you are naming your db entity's attribute (C# property) to Id or EntityNameId it should work. No C# entity class attributes neither OnModelCreating config is necessary.
The same time if the issue is there neither No C# entity class attributes neither OnModelCreating config will fix it.
...because if the Id property's value is not 0, the generated SQL will contain the explicit field name and value, so we got the error.
This is clearly and issue in EF core, but workaround is easy..
For DB first try adding [key] as a data annotation
With Data annotation
[Key]
public int AssigneeId { get; set; }
fluent API
modelBuilder.Entity<Assignee>()
.HasKey(o => o.AssigneeId);
See here or here if you want to use fluent API
I've tried to reproduce this issue based on your example but it appears to work just fine. I did not use Scaffold though, just coded class and I tried the model creating code you had and it hasn't had an issue. I suspect there has to be more to this though because with just the "Assignee" class, EF convention is expecting an "Assignees" table, so I suspect there is more mapping being set up.
Tested with EF Core 2.0.3 and 2.2.4
DB: used the OP's script.
Entity:
[Table("Assignee")]
public class Assignee
{
public int AssigneeId { get; set; }
}
I had to use the Table attribute to map to the table name.
Context:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Assignee>(entity =>
{
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
});
}
as-per OP comment.
Test:
[Test]
public void TestIncrement()
{
using (var context = new TestDbContext())
{
var newItem = new Assignee();
context.Assignees.Add(newItem);
context.SaveChanges();
}
}
Works as expected.
However, what I'd normally have for the entity:
[Table("Assignee")]
public class Assignee
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column("AssigneeID")]
public int AssigneeId { get; set; }
}
And then nothing for this column needed in the context OnModelCreating override.
I suspect that there is some additional configuration lurking somewhere given there is no mention of the table name issue, either manually added or via scaffold that is goofing up EF. I was full-on expecting EF to fail without the Key/DbGenerated attributes, but it seemed to work just fine.
Edit: Also tried this with scafolding running Scaffold-DbContext across the existing schema. Again, worked without an issue.
For comparison against your tests:
Generated DbContext: (Unaltered save removing the warning and connection string details.)
public partial class AssigneeContext : DbContext
{
public AssigneeContext()
{
}
public AssigneeContext(DbContextOptions<AssigneeContext> options)
: base(options)
{
}
public virtual DbSet<Assignee> Assignee { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("Data Source=machine\\DEV;Initial Catalog=Spikes;uid=user;pwd=password;MultipleActiveResultSets=True");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasAnnotation("ProductVersion", "2.2.4-servicing-10062");
modelBuilder.Entity<Assignee>(entity =>
{
entity.Property(e => e.AssigneeId).HasColumnName("AssigneeID");
});
}
}
Generated Entity: (Unaltered)
public partial class Assignee
{
public int AssigneeId { get; set; }
}
I did figure out why my table annotation was needed. EF Core (Not sure if applies to EF6 as well) was basing the convention for the table name on the DbSet variable name in the DbContext. I couldn't see any config difference with the scaffold generated context and my own, except the DbSet name. I renamed my original DbContext's DbSet name to "Assignee" and it worked without the Table attribute.
That said, based on the information present your code should work. Something is lurking in the details because this example does work so you will need to provide more detail about an example that definitely doesn't work in your case.
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
I'm trying to implement Soft Delete in a .NET Core 1.1.* Web App, backed by Entity Framework Core 1.1.*. I'm using Sql Server as my DB.
Migrating to .NET core 2.* is not an option at the moment.
After reading books, tuts and 3ds, I've implemented this feature using a Discriminator column. The deletion procedure is apparently working as expected. What's wrong is the data retrieval: deleted entities are still shown within my EF query results.
Current situation
Here's some C# code. I'll keep things as simple as possible
The interfaces:
// Soft deletion interface
public intercace ISoftDeletable
{}
// Another interface for some shadow properties
public interface IEntity
{}
The base class:
public abstract class Entity : IEntity, ISoftDeletable
{
public int MyBaseProp { get; set; }
}
One of my derived classes:
public class MyDerivedEntity: Entity
{
public string Name { get; set; }
public IList<MyChildEntity> Children { get; set; }
}
public class MyChildEntity: Entity
{
public string MyChildProp { get; set; }
}
The Context
public class MyContext: DbContext
{
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{ }
public DbSet<MyDerivedEntity> EntitiesToUse { get; set; }
protected override void OnModelCreating(ModelBuilder builder)
{
foreach (var entity in builder.Model.GetEntityTypes())
{
if (typeof(IEntity).IsAssignableFrom(entity.ClrType))
{
builder.Entity(entity.ClrType).Property<string>("MyShadowProperty");
}
if (typeof(ISoftDeletable).IsAssignableFrom(entity.ClrType))
{
// Discriminator column
builder.Entity(entity.ClrType).HasDiscriminator("IsDeleted", typeof(bool)).HasValue(false);
// Shadow Property
builder.Entity(entity.ClrType).Property(typeof(bool), "IsDeleted").IsRequired(true).HasDefaultValue(false);
builder.Entity(entity.ClrType).Property(typeof(bool), "IsDeleted").Metadata.IsReadOnlyAfterSave = false;
}
}
// Other model configurations
base.OnModelCreating(builder);
}
// SaveChangesAsync are almost the same
public override int SaveChanges()
{
AuditEntities();
return base.SaveChanges();
}
private void AuditEntities()
{
foreach (EntityEntry<IEntity> entry in ChangeTracker.Entries<IEntity>())
{
// do something with MyShadowProperty...
}
foreach (EntityEntry<ISoftDeletable> entry in changeTracker.Entries<ISoftDeletable>().Where(w => w.State == EntityState.Deleted))
{
// Set the entity as Softly Deleted
entry.Property("IsDeleted").CurrentValue = true;
// Ensure the entity state is modified to prevend hard deletion
entry.State = EntityState.Modified;
}
}
}
The Problem
Everything works as expected, except the data retrieval.
Here's a sample call:
var results = await _context.EntitiesToUse.Include(e => e.SomeChildEntity).AsNoTracking();
I expect the results to include only available myDerivedEntities with .IsDeleted == false. The problem is that my deleted entities are not filtered out. Why?
Please, what's wrong with my code? Am I missing something?
Thank you all so much!
Entity Framework Core 2.0 supports Global Query Filter
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ISoftDeletable>().HasQueryFilter(e => !e.IsDeleted);
base.OnModelCreating(modelBuilder);
}
You can find more info and examples here
I recommend you the built-in EF Core Global Query Filter but in some situations, Entity Framework Plus could also help.
Disclaimer: I'm the owner of this project
EF+ Query Filter allows you to filter DbSet Globally and by Instance.
// using Z.EntityFramework.Plus; // Don't forget to include this.
var ctx = new EntitiesContext();
ctx.Filter<IUser>(q => q.Where(x => !x.IsSystemUser ));
// SELECT * FROM Customers WHERE IsSystemUser = FALSE
var list = ctx.Customers.ToList();
Wiki: EF+ Query Filter
EDIT: Answer sub-question
Please, Is your library compatible with EF Core 1.1
Yes, it should be compatible with .NET Standard 1.3
This question, asked a year ago, is similar:
Does the Entity Framework 4 support generators for id values like NHibernate?
But what I'd like to know is if the code first CTP adds support for identity generation strategies. If not, does anyone know a good extension point in EF to implement something similar?
I'm currently working with model classes which use GUID as the identifier. When inserting using EF they retain their Guid.Empty initial values. I know that you can set a default value for the column in the DB to newid() but that defeats the purpose of client-side identity generation.
Is Entity Framework just not mature enough to be used in a distributed, disconnected system?
No, Entity framework code-first is still just nice wrapper around EFv4. There are no NHibernate like generators. If you want client side Id generator you will have to override SaveChanges in derived DbContext and implement your own logic of assigning Ids to new entities.
Edit:
Some high level example:
public class Context : DbContext
{
// Helper for example
// DO NOT USE IN REAL SCENARIOS!!!
private static int i = 0;
public DbSet<MyEntity> MyEntities { get; private set; }
public Context()
: base("connection")
{
MyEntities = Set<MyEntity>();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>().HasKey(e => e.Id);
// Turn off autogeneration in database
modelBuilder.Entity<MyEntity>()
.Property(e => e.Id)
.HasDatabaseGeneratedOption(HasDatabaseGeneratedOption.None);
// Other mapping
}
public override int SaveChanges()
{
foreach (var entry in ChangeTracker.Entries<MyEntity>()
.Where(e => e.State == EntityState.Added))
{
// Here you have to add some logic to generate Id
// I'm using just static field
entry.Entity.Id = ++i;
}
return base.SaveChanges();
}
}
public class MyEntity
{
public int Id { get; set; }
// Other properties
}
No.
Mine Entity Framework 4.1.10715 installed by NuGet.
maybe you could use attribute
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get;set;}
please see (The full list of annotations supported in EF 4.1 : CTRL+F in page) here.
[Key]
public string Id { get; set; }
and then use new GUID ToString
Units.Add( new Unit(){Id=Guid.NewGuid().ToString(), Name="123"});