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?
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 have two or more application and can`t update in one time
I want use type INT in model in one new application with field "Serial" with type SMALLINT in db and it must work when type in DB will changed to INT in feature without exception in new code
For old applications
public class Machine
{
public int Id { get; set; }
public short Serial { get; set; }
...
}
For new applications
public class Machine
{
public int Id { get; set; }
public int Serial { get; set; }
...
}
Here where can be exeption
var machine = context.Machines.FirstOrDefault(o => o.id == id);
When i use Stored Procedures it type indepeded, i what do same
var serial = Convert.ToInt32(sqlDataReader["Serial"]);
Add view in database with cast to SMALLINT or INT if you want to increase or decrease maximum value for choosed field
Sample:
CREATE VIEW [dbo].[view_machines]
AS
SELECT id, CAST(serial AS INT) AS serial
FROM dbo.machines
now you can change in table machines field type to INT or SMALLINT anytime
I have a BaseEntity:
public class BaseEntity : IBaseEntity
{
[ScaffoldColumn(false)]
public string ID { get; set; }
[ScaffoldColumn(false)]
public DateTime CreatedOn { get; set; }
[ScaffoldColumn(false)]
public bool Deleted { get; set; }
}
And a person entity:
public class People : BaseEntity
{ //Fields }
And I want to do Update, Insert, Delete with a stored procedure, so in my context do this:
modelBuilder.Entity<People>()
.MapToStoredProcedures();
For don't update "CreatedOn" field, I override the SaveChanges as:
public override int SaveChanges()
{
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is IBaseEntity
&& (x.State == System.Data.Entity.EntityState.Added || x.State == System.Data.Entity.EntityState.Modified));
foreach (var entry in modifiedEntries)
{
IBaseEntity entity = entry.Entity as IBaseEntity;
if (entity != null)
{
DateTime now = DateTime.Now;
if (entry.State == System.Data.Entity.EntityState.Added)
{
entity.CreatedOn = now;
}
else
{
base.Entry(entity).Property(x => x.CreatedOn).IsModified = false;
}
}
}
return base.SaveChanges();
}
But when do update, stored procedure will be run and set default value for #createdOn parameter and return exception.
How can I use MapToStoredProcedures and update not every column?
Can you just let the database do the work of setting the value for you?
public class BaseEntity : IBaseEntity
{
[ScaffoldColumn(false)]
public string ID { get; set; }
[ScaffoldColumn(false)]
[DatabaseGeneratedOption(DatabaseGeneratedOption.Computed)]
public DateTime CreatedOn { get; set; }
[ScaffoldColumn(false)]
public bool Deleted { get; set; }
}
And then your UPDATE procedure shouldn't even pass the parameter at all. Inside the UPDATE stored proc, set the value for CreatedOn to GETDATE(), GETUTCDATE(), or whatever sets a date value in your database if you're not using SQL Server.
Idea here is that setting the DatabaseGenerated option tells Entity Framework that it doesn't need to send that parameter to your procs because the database will generate the value for that column/property. You DO have to remember that the stored proc MUST return that column in its resultset -- but it can just use the default value for that column, that was generated in the proc.
If you do this, the override for SaveChanges to set the default value isn't even needed, the value will be set for you after the SaveChanges().
FYI: Entity Framework 6.x here.
I'm using following class to insert products to database.
ID column is primary key.
After adding multiple products to db context (without calling savechanges method) all newly added rows identity columns are zero!
My scene...
User adds several products and browse them on the data grid.
User selects one product and adds some barcodes to selected product.
When user finishes the job clicks on save button and application calls SaveChanges method!
When user wants to add some barcodes to products firstly I need to find selected product from context and adds entered barcode text to Barcodes list. But I cant do that because all products identity columns value are the same and they are zero.
How can I solve this problem?
public class Product
{
public int ProductID { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public virtual List<Barcode> Barcodes { get; set; }
}
public class Barcode
{
public int BarcodeID { get; set; }
public string BarcodeText { get; set; }
public int ProductID { get; set; }
public virtual Product Product { get; set; }
}
Identity column value is assigned by database when you are inserting record into table. Before you call SaveChanges no queries are executed, nothing is passed to database and back. Context just keeps in-memory collection of entities with appropriate state (state defines which time of query should be executed during changes saving - for new entities, which have Added state, insert query should be generated and executed). So, ID stays with its default value, which is zero for integer. You should not give value manually. After inserting entities into database, context will receive ID value and update entity.
UPDATE: When you are adding Barcode to existing product, then EF is smart enough to update keys and foreign keys of entities:
var product = db.Products.First(); // some product from database
var barcode = new Barcode { BarcodeText = "000" };
// at this point barcode.ID and barcode.ProductID are zeros
product.Barcodes.Add(barcode);
db.SaveChanges(); // execute insert query
// at this point both PK and FK properties will be updated by EF
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