ServiceStack's Ormlite Delete not working - c#

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.

Related

SQLite seems to have a problem with [DatabaseGenerated(DatabaseGeneratedOption.Identity)]

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

Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 'Database operation expected to affect 1 row(s) but actually affected 2 row(s)

I received this error once I try to update table with the same value (carNumber), my condition is to update where the actual return date field is null.
For some reason it's look like the query return 2 rows, but actually there is only one. I'm using EF. This is the function:
the error - print screen
Code:
public void updateStatus(int carNumber1, string actualDate1)
{
DateTime accReturn = DateTime.Parse(actualDate1);
var orderCar1 = db.CarRentalFields.FirstOrDefault(carNum =>
(carNum.CarNumber == carNumber1 && carNum.ActualReturnDate == null));
orderCar1.ActualReturnDate = accReturn;
db.SaveChanges();
}
The error happens when trying to call db.saveChanges().
the table from the db, the car number is 1000 - print screen
modelBuilder.Entity pic
Please let me know how can I solve this issue.
problem solved by add a new column to the car_rental_fields table, id column that include Identity.
as I understand from here and from the web, there is an issue with complicated pk.
in my solution the id isn't a primary key, but it's make the logic for linq to update the correct column.
thanks' for all the people that involved in this issue.
This error occurs when EF cannot resolve the PK for your entity. In most cases for simple entities, EF conventions can work out the PK, but in your case you are using a composite key so this needs to be configured. Depending on how you are mapping your entities you can do this either in:
an EDMX
in the DbContext.OnModelCreating
using an EntityTypeConfiguration declaration
using attributes within the entity itself
Since we don't know how your entities are configured, you can verify this as the cause by using the attribute approach within your entity as a test. If you are using an EDMX the entity classes will be generated so you will want to replace this with configuration within the EDMX. (Cannot really help you there because I don't use the dang things :D )
You will probably have something like:
public class CarRentalFields
{
[Column("start_day")]
public DateTime StartDay { get; set; }
[Column("return_date")]
public DateTime ReturnDate { get; set; }
[Column("user_id")]
public int UserId { get; set; }
[Column("car_number")]
public DateTime CarNumber { get; set; }
// ... more columns...
}
You may even have a [Key] attribute on one of these fields, such as CarNumber. If there is a PK mapped in the entity the issue is that it isn't specific enough to uniquely identify the row. When EF goes to update one entity, it is checking for, and expecting to update only one row in the table. It's finding more than one row will be affected so it fails.
Append the attributes for the [Key] with the column order so it is recognized as a composite key.
public class CarRentalFields
{
[Key, Column(Name="start_day", Order=1)]
public DateTime StartDay { get; set; }
[Key, Column(Name="return_date", Order=2)]
public DateTime ReturnDate { get; set; }
[Key, Column(Name="user_id", Order=3)]
public int UserId { get; set; }
[Key, Column(Name="car_number", Order=4)]
public DateTime CarNumber { get; set; }
// ... more columns...
}
Provided these 4 columns are guaranteed to be a unique constraint on the table, EF will be satisfied when only one row is updated when it builds it's UPDATE SQL statement.
Note again that if this works and you are using an EDMX, you will need to review and modify your EDMX mapping to make the appropriate changes since that entity class could be regenerated, losing your extra attributes. (I believe the generated entity classes from an EDMX have a comment header warning you that it is a generated class, so that is an indicator to watch out for.)
Update:
My primary suspect in this would be that the table does not actually have a matching PK defined, either running a different PK combination, or more likely no PK given the nature of those fields. EF can operated on tables that have no PK defined, but it does require a Key definition that ensures records can be uniquely identified. The error you are seeing happens when that key definition is not unique enough. (I.e. if you are updating car 1, and selecting a row that has:
car_number = 1, start_day = 2021-11-21, return_day = 2021-11-22, user_id = 0 The issue is that more than one row has that combination in the DB. If the DB you are checking doesn't have more than one matching row then your application is almost certainly pointing at a different database than you are checking.
Things you can do to verify this:
get the runtime connection string and see if it matches the DB you are checking:
Before you run your query, add the following:
// EF6
var connectionString = db.Database.Connection.ConnectionString;
// EF Core 5
var connectionString = db.Database.GetConnectionString();
Have a look at the data you are actually querying:
.
var cars = db.CarRentalFields.Where(carNum =>
(carNum.CarNumber == carNumber1 && carNum.ActualReturnDate == null)).ToList();
While this query might return only 1 record, that is not the cause of the problem. What you want is the CarNumber, StartDate, ReturnDate, and UserId for this record:
var car = db.CarRentalFields
.Where(carNum => carNum.CarNumber == carNumber1
&& carNum.ActualReturnDate == null)
.Select(x => new
{
x.CarNumber,
x.StartDay,
x.ReturnDate,
x.UserId
}).Single(); // Expect our 1 record here...
var cars = db.CarRentalFields
.Where(x => x.CarNumber == car.CarNumber
&& x.StartDay == car.StartDay
&& x.ReturnDate == car.ReturnDate
&& x.UserId == car.UserId)
.ToList(); // Get rows that match our returned Key fields.
These queries select the assumed PK values for the car record you mean to update, then search cars for matching records with the expected Key fields. My money would be on that while the top query returns 1 record, the bottom query returns two rows, meaning while only 1 record has a #null ActualReturnDate value, your Key is not unique enough for the contents of this table.

Configure fk relations with computed columns Entity Framework

I have a DB-model where there are computed columns. Basic idea is that when I insert a new Gauge into the table, a new Reading is automatically inserted into another table first. The table Gauges has a computed column (or several to be exact), that fetches the date of the latest Reading.
This all worked fine when I had a INT field that was used as a foreign key. Now the scope is growing so much that I can't rely on that field being unique anymore, so I needed to change the FK.
First I tried using the Gauge.Id property, which would be ideal, since that's the primary key in the table. Problem is that the Id is generated at the database, so it's not known when the first Reading is inserted. Also tried adding another Guid property to the model and using that as a FK, also didn't get it working. I have also tried checking the largest Id value from the DB before inserting, and assigning it to the new Gauge when creating, with no luck there either. Unfortunately I can't remember all the trial-and-error combinations to better clarify the problems on each try.
I'm using Entity Framework 6 code-first.
Object model is:
public class Gauge
{
public int Id { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CurrentReadingDate { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public decimal CurrentReading { get; set; }
}
public class GaugeReading
{
public int Id { get; set; }
public int GaugeId { get; set; }
}
Database:
[dbo].[Gauges]
[Id] INT IDENTITY (1, 1) NOT NULL,
[CurrentReadingDate] AS ([dbo].[CurrentReadingDate]([Id])),
[CurrentReading] AS ([dbo].[CurrentReading]([Id]))
[dbo].[GaugeReadings]
[Id] INT IDENTITY (1, 1) NOT NULL,
[GaugeId] INT NOT NULL,
[ReadingDate] DATETIME NOT NULL,
[Reading] DECIMAL (18, 2) NOT NULL,
Functions:
CREATE FUNCTION [dbo].[CurrentReadingDate]
(
#id int
)
RETURNS DATE
AS
BEGIN
RETURN (SELECT MAX(GaugeReadings.ReadingDate)
FROM GaugeReadings
WHERE dbo.GaugeReadings.GaugeId = #id)
END
The question is: How to configure Entity Framework (and the database) to be able to insert a Gauge with an initial Reading so that the computed columns work? Or is there maybe a different insert method in the EF that would allow something like this? Sorry for the verbose question.
Edit The error I'm getting while using Gauge.Id as FK:
System.Data.Entity.Core.UpdateException: A null store-generated value
was returned for a non-nullable member 'CurrentReadingDate' of type
'Repository.Gauge'.
I think the problem is in your function. check if no row exists in table, return some default date that is the minimum date allowed by db.

Database default value is not inserted while create record by entity framework

I have a LoginRecord table in sqlserver 2008 with the following column structure-
LoginId - int, identity
UserId - int
LoginDateTime- Allow nulls false,default value getdate()
I am inserting new record by entity framework 6 as below-
db.LoginRecords.Add(new LoginRecord() { UserId = UserId });
db.SaveChanges();
But in LoginDateTime table, null value is being inserted. It supposed to be current datetime.
I am using database first approach.
How can overcome this issue?
Combined my two comments into an answer.
Try setting the "StoredGeneratedPattern" attribute of your datetime in the EDMX file to Computed. From the following thread: http://www.stackoverflow.com/a/4688135/2488939
To do this, go to the edmx file designer by clicking on your edmx file. Then locate your table and the property. Right-click the column in the table that you want to change and click on properties. The property window should then come up and you will see as one of the properties "StoredGeneratedPattern". Change that to computed.
In addition to changing the EDMX file as suggested by Vishwaram Maharaj, you should make the definition of the table match between EF and the DB. The table description of "LoginDateTime- Allow nulls false" is itself false. The field clearly allows NULLs if NULLs are being inserted. Alter the column to not allow NULL if it truly shouldn't have NULL values in it:
ALTER TABLE LoginRecords ALTER COLUMN LoginDateTime DATETIME NOT NULL;
Setting default values in Entity Framework 5 and 6 by changing T4 Template File
Made below changes in .tt(template file)
Example:
red means remove and green means add
This will add constructor in all entity classes with OnCreated method.
Like below
public partial class Category
{
public Category()
{
this.Products = new HashSet<Product>();
OnCreated();
}
partial void OnCreated();
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Then create class file using same namespace that of Entities.
public partial class Category
{
partial void OnCreated()
{
Name = "abc"
}
}
Refer this
https://www.youtube.com/watch?v=i8J2ipImMuU
Helpfull

Cannot insert the value NULL into column in ASP.NET MVC Entity Framework

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.

Categories

Resources