I have a problem with the SQLite in-memory database. The normal database is working.
This is my model code
public class Log
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public string Message { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedAt { get; set; }
}
The SQL statement to create the table
CREATE TABLE [dbo].[AuditLogs]
(
[Id] UNIQUEIDENTIFIER NOT NULL DEFAULT newid() PRIMARY KEY,
[Message] varchar(max) NOT NULL CONSTRAINT ensure_json CHECK (ISJSON([Message])> 0),
[CreatedAt] datetime NOT NULL default GetDate()
)
The error
Microsoft.Data.Sqlite.SqliteException (0x80004005): SQLite Error 19: 'NOT NULL constraint failed: AuditLogs.CreatedAt'.
Do you have any solution?
You could just use this:
public Guid Id { get; set; } = Guid.NewGuid();
The problem with the Entity Framework is that it won't generate keys for you. If it is database-generated then some trigger in the database would still need to create this ID. This is generally done when the ID is of type int, but not Guid. Then again, SQLite is a weird database provider to begin with as it doesn't really has datatypes. Data type definitions are more suggestions and not enforced by the engine. (But EF will enforce it.)
Anyways, since you use Guids there's nothing wrong with assigning new values to the property, as they will be overwritten by the value in the database on retrieval. But SQLite isn't really generating values for you.
Also, I would use public DateTime CreatedAt { get; set; } = DateTime.Now(); for the same reason. I myself actually had similar problems but I use the Fluid API instead and use this:
var hostBuilder = modelBuilder.Entity<Host>();
hostBuilder
.Property(r => r.Id)
.HasColumnOrder(0)
.IsRequired()
.HasColumnName("Key")
.HasColumnType("varchar(36)")
.HasComment($"Primary key");
hostBuilder
.Property<DateTime>("Created")
.HasColumnOrder(1)
.HasDefaultValueSql("CURRENT_TIMESTAMP")
.ValueGeneratedOnAdd()
.HasComment($"When was it created?");
hostBuilder
.HasKey(r => r.Id)
.HasName($"PK_Visitor_Host_Key");
And my class only has the Id property defined, as I don't need the Created field in my project. It still gets added, though! The HasDefaultValueSql() call will tell that the field is database-generated, including how it's generated. You might want to look into this Fluid API for your project. I prefer it over those attributes as it provides more options and better control, plus I can add fields to tables that are not important for my code, yet still required for other purposes...
(Btw. You don't want timestamps to be unique as two records could be created at exactly the same timestamp on fast systems.)
Related
I'm still new with Entity Framework and I'm struggling with an issue while inserting records to the database on .NET Core API service I'm developing.
Summarizing what is going on, I'm not generating the schema from EF, instead I create the tables in SQL and just have EF access it.
I have a model objects similar to this:
[Table("events")]
public class Event
{
[Key]
[Required]
[Column("id")]
public Guid Id { get; set; }
public string EventName { get; set; }
public List<Session> Sessions { get; set; }
}
[Table("sessions")]
public class Session
{
[Key]
[ForeignKey("FK_events-sessions")]
[Column("eventId")]
public Guid EventId { get; set; }
public byte Hour { get; set; }
public byte Day { get; set; }
public byte Month { get; set; }
}
Other then this, on the context class, I declare a one to many relationship:
modelBuilder.Entity<Event>().HasMany(s => s.Sessions);
In the SQL DB, the table columns have matching names, but all start with lowercase letters.
The issue I'm having is that when I perform the POST operation, where an Event object with multiple session is added and saved, it throws an error:
Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
---> Microsoft.Data.SqlClient.SqlException (0x80131904): The column name 'eventId' is specified more than once in the SET clause or column list of an INSERT. A column cannot be assigned more than one value in the same clause. Modify the clause to make sure that a column is updated only once. If this statement updates or inserts columns into a view, column aliasing can conceal the duplication in your code.
I've enabled Information logging on EF and I see it doing this:
INSERT INTO [sessions] ([eventId], [Hour], [Day], [EventId], [Month])
VALUES (#p0, #p1, #p2, #p3, #p4);
INSERT INTO [sessions] ([eventId], [Hour], [Day], [EventId], [Month])
VALUES (#p0, #p1, #p2, #p3, #p4);
As you can see, it's adding the Id column twice for some reason.
I wonder if the first one is my mapping that I annotate with [Column("eventId")] and the other one a generated one from the object Event.
Any idea?
Thank you.
I've fixed the issue.
My DB schema, had Sessions with its key being only the Foreign Key, which made the entries not 100% unique.
It would allow to have duplicated entries.
For now, I've decided to change the primary key of Sessions to a composite key using EventId and Day.
My current OnModelCreating modelBuilder is the following:
modelBuilder.Entity<Session>()
.HasOne(c => c.Event)
.WithMany(s => s.Sessions)
.HasForeignKey(k => k.EventId);
modelBuilder.Entity<Session>()
.HasKey(k => new { k.EventId, k.Day});
Hope it helps other people in the future.
TL/DR - Is there a way to force EF Core to allow me to update a Discriminator column?
I'm playing around with Entity Framework Core for the first time and trying to implement a simple system where I have Clients of 3 types to manage. Two of these types are only meaningfully different in their type value itself (type1 and type2). The third type (special) just has an extended set of possible relationships.
So, in the database I created a single table to store the core data:
CREATE TABLE [Clients].[Clients](
[ClientId] [varchar](30) NOT NULL,
[ClientName] [varchar](100) NOT NULL,
[ProtocolType] [varchar](30) NOT NULL,
[ClientLogoUrl] [varchar](max) NULL,
CONSTRAINT [PK_Clients_Clients] PRIMARY KEY ([ClientId]),
CONSTRAINT [UQ_Clients_Client_Names] UNIQUE ([ClientName]),
CONSTRAINT [UQ_Clients_Client_Protocol_XRef] UNIQUE ([ClientId],[ProtocolType]),
CONSTRAINT [FK_Clients_Client_Protocol] FOREIGN KEY([ProtocolType])
REFERENCES [Clients].[Protocols] ([ProtocolType])
)
In my code I then created these models:
public class Client
{
public string ClientId { get; set; }
public string ClientName { get; set; }
public string ProtocolType { get; set; }
public string ClientLogoUrl { get; set; }
public Protocol Protocol { get; set; }
}
And for the one subtype of Clients that have more possible relationships:
public class SpecialClient : Client
{
public List<Service> Services { get; set; }
//And more
}
I initially had this mapped in OnModelCreating as:
modelBuilder.Entity<Clients.Client>()
.HasDiscriminator<string>("ProtocolType")
.HasValue<Clients.SpecialClient>("special");
But this ended up querying the database only for protocol types special and Client - not what I wanted. I'd assumed EF would just use the base class for "all other values" but it did not. So then I tried:
modelBuilder.Entity<Clients.Client>()
.HasDiscriminator<string>("ProtocolType")
.HasValue<Clients.SpecialClient>("special")
.HasValue<Clients.Client>("type1")
.HasValue<Clients.Client>("type2");
But this effectively only took the last supplied value and would only query the database for special and type2. So, I finally accepted that I'd need to introduce a redundant extra class with no extra functionality just so that I could make the mapper happy:
public class Type2Client : Client {}
And:
modelBuilder.Entity<Clients.Client>()
.HasDiscriminator<string>("ProtocolType")
.HasValue<Clients.SpecialClient>("special")
.HasValue<Clients.Client>("type1")
.HasValue<Clients.Type2Client>("type2");
Finally - EF is happy, all is good, I can move on with my life. However, the next challenge is, clients can change protocols.
So, I load a client. I change its protocol type. I save it:
_context.Update(client);
await _context.SaveChangesAsync();
And EF generates this SQL:
SET NOCOUNT ON;
UPDATE [Clients].[Clients] SET [ClientLogoUrl] = #p0, [ClientName] = #p1
WHERE [ClientId] = #p2;
SELECT ##ROWCOUNT;
Harumph. No SET for the ProtocolType column. Is there some cunning way to make this work?
You need to change default "after save" behavior of discriminator column. By default, discriminator column has AfterSaveBehavior set to Throw. That means that if explicit value is set, or changed - exception is thrown when trying to save changes. That also means Update will not mark such property as modified, because that will lead to exception during SaveChanges anyway.
So in your case change save behavior to Save for discriminator column (do that after you made this column discriminator of course):
modelBuilder.Entity<Client>()
.Property(c => c.ProtocolType).Metadata.AfterSaveBehavior = PropertySaveBehavior.Save;
And it should work as you expect.
I'm getting this error on EF.
Cannot insert explicit value for identity column in table
'GroupMembers_New' when IDENTITY_INSERT is set to OFF.
The column on the Db is identity increment and on the EF design file, StoreGeneratedPattern is identity as well. Seems like EF is trying to insert 0 every time I try to save.
Some suggestions says ID is reserved on tables or drop the table and rerun the scripts.
Any ideas?
Here's some code:
GroupMember groupMember = new GroupMember();
groupMember.GroupId = group.Id;
groupMember.UserId = (new UserId(group.Owner));
//groupMember.Id = _groupContext.GroupMembers.Count();
group.GroupMembers.Add(groupMember);
_groupContext.SaveChanges();
I have run into this before. This error means you are trying to assign a value explicitly to a column where the database automatically assigns it.
Suggestion:
Update your edmx file to reflect any changes you may have made in the database.
If the database automatically assigns the value, you should see the "IsDbGenerated=true" attribute in your designer file under that property. If it's not there, you can add it manually.
Try this:
using System.ComponentModel.DataAnnotations.Schema;
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public decimal Identity_Col { get; set; }
The Entity Framework class file adds these lines of code to the Identity column.
Put these attribs on top of the property which is identity:
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
I encountered the same problem and error message in my AspNetCore 2.x application.
The only way I could solve it was by removing this line in the ModelBuilder.Entity method of the DbContext class:
// remove: entity.Property(e => e.Id).ValueGeneratedNever();
EF Code first: Because of an auto-increment PK 'id' field AND a guid column, design like this:
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid FileToken { get; set; }
there was a duplicate identity. I changed it to:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[DefaultValue("newid()")]
public Guid FileToken { get; set; }
and the problem went away.
In EF 6, there is a property of the field/column in your model for doing this:
StoreGeneratedPattern.
Set this to "Identity" in the property dropdown list.
(I don't know about EF 4. The above answer, using IsDbGenerated, seems to be for EF 4.)
And this corresponds in the underlying XML to an attribute to the element:
<Property Name="MyTableId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
--but you don't need to deal with the XML manually, since you can use the designer.
How this gets messed up isn't clear. I had the problem even after refreshing my model from the database. Perhaps it gets confused if you set the PK on the table, or change its name, after you have already generated the model. (I am using the table/database-first approach, not code first.)
You can't use the above approach of putting the C# attribute on the entity code, because in this situation the entity code is generated by EF. EF is supposed to understand ("by itself") that the field is an identity.
I had this issue in my app; and got fixed it changing the property "StoredGeneratedPattern" of the id field to Identity.
So, Go to the model; look up for the table; click on propierties of the primary key fiel; and change the property.
See intercepting Entity Insert for generated always columns like StartTime and EndTime columns on history tables, rowversion columns as well.
I solved this by removing primary key in model from inserting data. because primary key auto increment.
var book = new Book
{
// Id = 1, //Don't need to write this
Genre = "Technology",
Author = "Charles Petzold",
Title = "Programming Windows 5th Edition",
Price = 30,
Publisher = "Microsoft Press"
};
_unitOfWork.Books.Add(book);
Well, You need give a value to ID, for example for the object Auto, just you should VarAuto.Id = 0;
After that you could do it something like this =>
using( MyContext db = new MyContext()){
db.Autos.Add(VarAuto);
db.SaveChanges();
}
That is the solution just give value to id, EF could be recognize the identity value in the table.
Just Try.
I'm using DB first and the table has identity column. I didn't use the db-scaffolding to generate this, I copied it from another entity and by mistake I took this property with.
So
Try to check the DBContext Class. I got this error, and the issue was with this property ".ValueGeneratedNever()"
I have just removed it and it works fine,
modelBuilder.Entity<TableName>(entity =>
{
entity.Property(e => e.Id)
//.ValueGeneratedNever()
.HasColumnName("ID");
});
Note: a moderator deleted this answer as a duplicate and left my other answer up, on a question with only the sql-server tag (which was the first question I arrived at from google). Since this question has the entity framework tag, posting the answer again here.
This is for EntityFramework Core 3.1.22. Using the wrong property to specify a foreign key causes Entity Framework to demote the primary key to ... something else. Entity Framework will then always attempt to insert an explicit value, which throws a database exception because it can't insert the value it's been told is a primary key and shouldn't be inserted.
Microsoft.EntityFrameworkCore.DbUpdateException: 'An error occurred while updating the entries. See the inner exception for details.'
Inner Exception:
SqlException: Cannot insert explicit value for identity column in table 'FOO' when IDENTITY_INSERT is set to OFF.
Code example. We have a 1-to-1 class mapping:
public class Foo /* child */
{
public int FooPrimaryKey { get; set; }
public int BarPrimaryKey { get; set; }
public virtual Bar PropertyBar {get; set; }
}
public class Bar
{
public int BarPrimaryKey { get; set; }
public virtual Foo PropertyFoo {get; set; }
}
modelBuilder.Entity<Foo>(entity =>
{
entity.HasKey(e => e.FooPrimaryKey);
entity.ToTable("FOO", "dbo");
entity.HasOne(d => d.PropertyBar)
.WithOne(x => x.PropertyFoo)
// wrong, this throws the above exception
.HasForeignKey<Bar>(x => x.BarPrimaryKey);
});
The foreign key should instead be (same key, different type):
.HasForeignKey<Foo>(x => x.BarPrimaryKey);
If you don't want to use EF core's auto-generating primary key values feature, you can turn it off. You can add your data to the primary key
It should resolve the error - Set Identity Insert off
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int StudentId { get; set; }
Setting Database Generation option to None helped me.
You can find more about it here- https://learn.microsoft.com/en-us/ef/core/modeling/generated-properties?tabs=data-annotations
Add this line in order to allow the Id column to receive 1,2,3 and 4 values instead of being auto-numbered.
Sql("SET IDENTITY_INSERT MembershipTypes ON")
I've made up a generic repository to make CRUD operations in a MVC project.
When i try to delete a row from a table that has an identity on SQLServer, the code generated by the Ormlite Delete method and inspected with the profiler doesn't not affect any rows.
This is the Crud operation for the deletion (pretty simple):
public void Destroy<T>(T entity)
{
using (var db = dbFactory.Open())
{
db.Delete<T>(entity);
}
}
The Type T in my test is represented by the following class:
[Alias("FT_TEST_DEVELOPMENT")]
public class TestTable
{
[AutoIncrement]
[PrimaryKey]
public int ID { get; set; }
public string DESCR { get; set; }
public DateTime? TIMESTAMP { get; set; }
public DateTime DATE { get; set; }
public decimal PRICE { get; set; }
public int? QTY { get; set; }
}
And the inspected code corresponds to the following:
exec sp_executesql N'DELETE FROM "FT_TEST_DEVELOPMENT" WHERE "ID"=#ID AND "DESCR"=#DESCR AND "TIMESTAMP"=#TIMESTAMP AND "DATE"=#DATE AND "PRICE"=#PRICE AND "QTY"=#QTY ',
N'#ID int,#DESCR nvarchar(6),#TIMESTAMP datetime,#DATE datetime,#PRICE decimal(2,0),#QTY int',
#ID=4,#DESCR=N'SECOND',#TIMESTAMP=NULL,#DATE='2015-06-01 00:00:00',#PRICE=15,#QTY=NULL
When I execute this perfectly sensed statement the server tells me that no row
Disclaimer: as some names where in my native language, I translated them so there may be little grammar error, if it's so, let me note and I'll edit.
UPDATE
The matching row actually EXISTS in the database
SELECT * FROM FT_TEST_DEVELOPMENT WHERE ID= 4
ID DESCR TIMESTAMP DATE PRICE QTY
4 SECOND NULL 2015-06-01 15 NULL
I mean that actually the OrmLite generated code appears to be bugged.
And yes, the ID column is the table's key.
SECOND UPDATE
I think I've found the cause:
actually in the WHERE clause the NULL fields are assigned in the way
#TIMESTAMP=NULL
but actually the SQL server will not match this statement, because it expects to receive
WHERE [...] AND "TIMESTAMP" IS NULL [...]
The way db.Delete() API works has been updated so that NULL fields are moved out of the parameterized queries and appended to the SQL filter so this should now work from v4.0.37+ that's now available on MyGet.
You can also delete rows in OrmLite by PrimaryKey with:
Db.DeleteById<TestTable>(entity.Id);
For generic methods you can use the T.GetId() extension method to get the value of the Id field, i.e:
Db.DeleteById<TestTable>(entity.GetId());
Or to delete using every non null property in the DELETE WHERE criteria, you can use:
Db.DeleteNonDefaults(entity);
If you execute the same statement in SSMS and nothing gets deleted, it's because no row matches the criteria.
OrmLite expects the primary key of an entity to be named Id (case-sensitive). Your property is named ID and the [PrimaryKey] attribute wasn't specified. In this case OrmLite has to use all available fields in the WHERE clause to find the rows to delete.
AutoIncrement doesn't mean the field is a key, just that its value is auto-generated by the server and comes from an identity column. The same applies with SQL Server - an identity column isn't a primary key, you need to define the primary key separately.
You need to either rename ID to Id or add the [PrimaryKey] attribute to it.
When trying to use this code:
var model = new MasterEntities();
var customer = new Customers();
customer.Sessionid = 25641;
model.Customers.Add(customer);
model.SaveChanges();
I get:
{"Cannot insert the value NULL into column 'Sessionid', table
'master.dbo.Column'; column does not allow nulls. INSERT
fails.\r\nThe statement has been terminated."}
The column "Sessionid" is actually the primary key and is marked with [KEY] like this:
public class Customers
{
[Key]
public long Sessionid { get; set; }
public long? Pers { get; set; }
}
So according to this question, it seems as if when the property is marked with [KEY], EF ignores my own declaration of Sessionid since it expects the database to assign the value.
So how can I solve this? If I remove [KEY] I get the "entity type has no key defined" exception...
I solved it by adding [DatabaseGenerated(DatabaseGeneratedOption.None)] like this:
public class Customers
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Sessionid { get; set; }
public long? Pers { get; set; }
}
You can configure SQL to auto-generate (and auto-increment) the primary key for the table upon inserts. Then just remove the [Key] in C# and you don't need to set the ID in the application manually, the db will generate it for you.
I have encountered this problem multiple times while working with Microsoft SQL Server and I have followed the same way to fix it. To solve this problem, make sure Identity Specification is set to Yes. Here's how it looks like:
In this way the column number auto increments as a primary key normally would.
HOW?: right-click the table that contains the column, choose Design, select the primary key and in Column Properties window find Identity Specification and set it to Yes.