I have a problems with Entity Framework (V 6.1.0), if a DateTime property is annotated as a Date type:
Column(TypeName = "Date")
public class MyTable
{
[DatabaseGenerated]
public Guid Id { get; set; }
[Key, Column(TypeName = "Date"), DataType(DataType.Date)]
public DateTime DateKey { get; set; }
[Required]
public string SomeProperty { get; set; }
}
The column of my table in SQL is created correctly as a DATE column.
Now if I try to insert a new row and set my date property e.g. with DateTime.Now() I get following error:
Store update, insert, or delete statement affected an unexpected
number of rows (0). Entities may have been modified or deleted since
entities were loaded. Refresh ObjectStateManager entries.
The problem is in the generated SQL code by EF:
exec sp_executesql N'
INSERT [dbo].[MyTable]([DateKey], [SomeProperty]) VALUES (#0, #1)
SELECT [Id] FROM [dbo].[MyTable]
WHERE ##ROWCOUNT > 0 AND [DateKey] = #0',
N'#0 datetime2(7), #1varchar(max)',
#0='2014-03-26 08:58:07',
#1='abcdef'
The parameter for DateKey is declared as DATETIME2. I think, This should be DATE.
This select statement cannot return any row, if there is any time part. If I change parameter #0 to DATE (as it is annotated in my model, I get no error even if time part is still included.
Is this a bug in EF?
Regards,
Daniel
EDIT:
I don't know if this is relevant for my question. I'm configured in OnModelCreating all my date column as DATETIME2 by default:
modelBuilder.Properties().Configure(p => p.HasColumnType("datetime2"));
EDIT on 2014-03-27
Providing a complete repro:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EFDateColumnAsKey
{
class Program
{
static void Main(string[] args)
{
MyContext ctx = new MyContext();
ctx.CalendarItems.Add(new CalendarItem() { StartDate = DateTime.Now.Date }); // This works
ctx.SaveChanges();
ctx.CalendarItems.Add(new CalendarItem() { StartDate = DateTime.Now }); // This not !!
ctx.SaveChanges();
ctx.Dispose();
}
public class MyContext : DbContext
{
public DbSet<CalendarItem> CalendarItems { get;set; }
}
public class CalendarItem
{
[DatabaseGenerated( System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Key, Column(Order = 1), DataType(DataType.Date)]
public DateTime StartDate { get; set; }
}
}
}
I have added some details to bug #2185. In summary, when we create a SqlCommand for the INSERT operation we define a parameter of type DATETIME2 for any property of type System.DateTime, but for compatibility reasons when we create the database schema by default we will create a column of type DATETIME.
The SQL we generate for the insert looks like this:
INSERT [dbo].[CalendarItems]([StartDate]) VALUES (#0);
SELECT [Id] FROM [dbo].[CalendarItems] WHERE ##ROWCOUNT > 0 AND [StartDate] = #0;
When you pass an arbitrary System.DataTime instance as a DATETIME2 parameter the least significant digits will be preserved, but as soon as the value of the DATETIME2 parameter gets stored in a DATETIME column, the value will be truncated.
Since the value stored in StartDate no longer matches the value of parameter #0, the second statement (i.e. the SELECT query we use to detect rows affected) will return zero rows and the command will fail, the transaction will get rolled back, etc.
As you already found, the possible workarounds are:
Use a surrogate identity key instead of declaring a key of type DateTime
Use DateTime2 for the column
Truncate the value in-memory before sending it to the database.
Hope this helps,
Diego
Related
I create a table with primary key.
I tried to insert new data with entityframework6, but it would get 23502 error.
But I add the default value to the column before I insert it.
I don't understand why it would get this error.
Table DDL:
CREATE TABLE ERRORLOG(
id numeric NOT NULL,
message varchar(50) NULL,
CONSTRAINT pterrorlog_pk PRIMARY KEY (id)
);
Model:
public partial class ERRORLOG
{
[Key]
[Column(Order = 0)]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
public string MESSAGE { get; set; }
}
Funcation:
using (DbContext Db as new DbContext)
using (TransactionScope transactionScope = new TransactionScope())
{
ERRORLOG iLog = new ERRORLOG();
iLog.MESSAGE = Message;
Db.ERRORLOG.Add(iLog);
Db.SaveChanges(); //Get 23502 error
}
Here is the insert script, it looks like didn't insert the id, why is that?
INSERT INTO "pterrorlog"("message") VALUES (#p_0) RETURNING "id"
Edit:
After I add this script on the Model, it works fine now.
public partial class ERRORLOG
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
public string MESSAGE { get; set; }
}
Looks like Entity Framework auto insert a value to the column.
After I add the script to prevent this issue, it works fine now.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
Model would like:
public partial class ERRORLOG
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
public string MESSAGE { get; set; }
}
You can use PGAdmin to profile the SQL that EF is actually attempting to execute on SaveChanges. C# is case sensitive while Postgres defaults to lower case. If I recall NPGSQL will format all EF SQL Queries with double-quotes so if your Entities were declared with properties like ID, it would be generating statements like INSERT INTO "ERRORLOG" ( "ID", "MESSAGE" ) VALUES ( ... ) so a column named "id" wouldn't be getting set.
If you want your entities to use a different case than the DB, and leave Postgres using lower case then I'd recommend using [Column] attributes to rename the columns:
public partial class ERRORLOG
{
[Key, Column(Name = "id")]
public long ID { get; set; } = DateTimeOffset.Now.ToUnixTimeMilliseconds();
[Column(Name = "message")]
public string MESSAGE { get; set; }
}
The other detail is that Order on the Column attribute is only needed when dealing with composite keys, such as many-to-many joining tables where the PK is made up of two or more columns. It isn't needed for normal single-value PKs.
If that isn't the cause, checking the insert statement in PGAdmin should give you a clue what NPGSQL / EF is attempting to execute.
I am using EF Core to handle my database and I want insert a record to my postgresql database.
But when I insert the record and do the save changes, the app throws an error:
Index was out of range. Must be non-negative and less than the size of
the collection. (Parameter 'index')
I understand that this error can appear when I work with collections but in this case I am doing an insert.
my base entity:
public abstract class BaseEntity: IBaseEntity<long>
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key, Column("id", Order = 0)]
public long Id { get; set; }
[Column("createdAt")]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedAt{ get; set; }
}
the insert from business layer:
var entityToInsert = new Delivery()
{
State = 1, //active
Date = DateTime.Now,
};
await this.UOW.Delivery.CreateAsync(entityToInsert);
await this.UOW.SaveChangesAsync();
My generic repository method:
public async Task<TEntity> CreateAsync(TEntity entity)
{
await Context.AddAsync<TEntity>(entity);
return entity;
}
the delivery table definition:
id bigint // type identity "by default" autoincrment 1 per 1
state integer
date timestamp without time zone (not null)
createdAt timestamp without time zone (not null)
What am I doing wrong?
I'm trying to use a DateTime LastModifiedDate column with optimistic concurrency in Entity Framework (will likely upgrade it to DateTime2.) I've set the Concurrency Mode to Fixed. But when I retrieve an entity, change a column and try to save, get a concurrency exception.
The stored LastModifiedDate is 2017-01-04 21:16:55.283 but look at the SQL Entity Framework is generating for the update:
UPDATE [dbo].[Facilities]
SET [Password] = #0
WHERE (([pk_FacilityID] = #1) AND ([LastModifiedDate] = #2))
-- #0: 'bz0dkK+smlat9psrIrbyXkxjpcXcDK1DeUiha7jCRkU=' (Type = String, Size = 255)
-- #1: '6801bdcf-266d-46bd-b15e-dac21116208d' (Type = Guid)
-- #2: '1/4/2017 9:16:55 PM' (Type = DateTime2)
Notice it's passing a formatted DateTime string for #2 which does not include milliseconds. Well of course it doesn't match if it's not passing in the same value it retrieved! I've verified that at runtime, the .NET DateTime does include the 0.283 seconds. Please tell me there's a way to pass in the full value. Why does it behave this way and how can I change it to include milliseconds?
-- #2: '1/4/2017 9:16:55 PM' (Type = DateTime2)
That's not the actual parameter value sent. That's just the logging, and it leaves off the fractional seconds. There is a problem, but that's not it.
If you profile you should see something like this, that shows the details of the parameter type and value.
exec sp_executesql N'UPDATE [dbo].[Facilities]
SET [Name] = #0
WHERE (([FacilityId] = #1) AND ([LastModified] = #2))
',N'#0 nvarchar(max) ,#1 int,#2 datetime2(7)',#0=N'newName',#1=1,#2='2017-08-31 15:45:55.3030000'
What's happening is the datetime2(7) value is not round-trip converting to and back from datetime. And the easiest fix is just to use datetime2(7) for your table column. If you want to use the column for optimistic concurrency control, you need the extra precision anyway.
So something like this:
using System;
using System.Linq;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity;
namespace Ef6Test
{
public class Facility
{
public int FacilityId { get; set; }
public string Name { get; set; }
[ConcurrencyCheck(),Column(TypeName ="datetime2")]
public DateTime LastModified { get; set; }
}
class Db : DbContext
{
public DbSet<Facility> Facilities { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Db>());
using (var db = new Db())
{
var f = db.Facilities.Add(new Facility() { LastModified = DateTime.Now, Name = "Faclity1" });
db.SaveChanges();
}
using (var db = new Db())
{
var f = db.Facilities.First();
f.Name = "newName";
db.SaveChanges();
}
Console.ReadKey();
}
}
}
I have below entity with a calculated/computed column:
public EntityA
{
[Key(), Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
.....
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public virtual string RefId {
get
{
return this.Id.ToString().PadLeft(7, '0');
}
private set
{
}
}
}
RefId is a computed column that depends on the Id value.
After performing commit changes to database with SaveChanges I can check that Id and RefId have been set correctly for the entity I am currently inserting on database, but If I open the database and check RefId column for this entity, I can observe that RefId column has not been set, if figures as NULL. Why? Any ideas?
DatabaseGeneratedOption.Computed means that's the value is computed by the database engine. So EF will never update this value, only read from the db.
So if you want this value in the db:
- remove DatabaseGeneratedOption.Computed and set the value in your code, or
- set a computed column at database engine side.
Otherwise you should set RefId as not mapped. But in this case you will have no column in the db.
I have an entity with a ModifiedDateTime property which I want to be updated with the current datetime from the database instead of the "application" server executing the application.
Every time I want to update or add a person to my datebase on SQL Server 2008 I want to fill ModifiedDateTime filed. It's not like I can change update query as with data adapter command when I work with dataset and to define for my ModifiedDateTime filed to be GetDate(). I created stored function to return me a value of GetDate() method, but I have a problem to import procedure which returns values as int, string or no value at all, already just entity values as Person for example in my case. Why is that?
Anyway, it would be of great help if you can help me to retrieve the current DateTime from the database server.
Is there a reason you just can't push it down to your database? If you include DateTime.Now in your entity query, it will push it down (getdate) to the database.
Example linq to entities
var dQuery = dbContext.CreateQuery<DateTime>("CurrentDateTime() ");
DateTime dbDate = dQuery.AsEnumerable().First();
SQL Generated ..
SELECT GetDate() AS [C1] FROM ( SELECT cast(1 as bit) AS X ) AS [SingleRowTable1]
Might be a better way to do it ?
This is an update of #Nix response to EF4:
var dateQuery = dbContext.Database.SqlQuery<DateTime>("SELECT getdate()");
DateTime serverDate = dateQuery.AsEnumerable().First();
An update for .net core 2.0
var dual = databaseContext
.Set<Dual>()
.FromSql("SELECT -1 AS Id, GETDATE() AS DateTime")
.First();
The fake entity
public class Dual
{
public int Id { get; set; }
public DateTime DateTime { get; set; }
}
In VS 2008, if you add a function template to return a scalar, it does not add the code to make it easy to use. You need to access the function template directly -- I use the partial class to build the needed methods for ease of use. They fixed this in VS2010.
public DateTime GetDateTime()
{
var returnValue = new DateTime();
using (var connection = new EntityConnection(Connection.ConnectionString))
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = "myStoredProc";
command.CommandType = CommandType.StoredProcedure;
try
{
returnValue = Convert.ToDateTime(command.ExecuteScalar());
}
finally
{
connection.Close();
}
}
}
return returnValue;
}
More information:
Function Imports in Entity Model with a non-Entity Return Type
EF 3
var dual = await db
.Set<Dual>()
.FromSqlRaw(#"
SELECT
CURRENT_TRANSACTION_ID() as Id,
SYSDATETIMEOFFSET() AS DateTime
")
.FirstAsync();
DateTimeOffset serverTime = dual.DateTime;
public class Dual
{
public long Id { get; set; }
public DateTimeOffset DateTime { get; set; }
}
modelBuilder.Entity<Dual>(entity =>
{
entity.HasNoKey();
entity.ToView(nameof(Dual));
});