Configure fk relations with computed columns Entity Framework - c#

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.

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

EF6 Unrecognized Guid format. while trying to find in set

I'm facing problem with 'Unrecognized Guid format.' while trying to find the data in the set.
Application work with .Net 4.5 and EF6. I'm facing this issue since we switch from MsSql to MySql Db.
Entities look like:
public abstract class DomainEntity(){
public Guid Id { get; protected set; };
public bool IsActive { get; protected set; }
public DateTime CreateTime { get; set; }
public DateTime ModifyTime { get; set; }
}
The DB tables look like:
CREATE TABLE IF NOT EXISTS `MssDocumentsDb`.`dbo_Versionings` (
`Id` CHAR(36) UNIQUE NOT NULL,
`IsActive` TINYINT(1) NOT NULL DEFAULT 1,
`CreateTime` DATETIME(6) NOT NULL,
`ModifyTime` DATETIME(6) NOT NULL,
PRIMARY KEY (`Id`));
And getting most data working properly, but sometimes I'm facing the issue with 'Unrecognized Guid format.'
The places where mostly that error occurs looks like:
public virtual TEntity Get(Guid Id)
=> Id != Guid.Empty ? GetSet().Find(Id) : null;
Create set:
public IDbSet<TEntity> CreateSet<TEntity>()
where TEntity : class => base.Set<TEntity>();
I'm tried different solutions to solve this issue.
Tried to switch Id in db from CHAR(36) to varchar(64), passing Id.ToSting() or adding 'Old Guids=true' to the connection string, but without results.
Thanks for any help.
Many thanks to #mjwills for the help with figuring out what's going wrong.
My main issue was the data of Ids. Some Ids after migrating from MsSql to Mysql were imported as an empty string otherwise in mssql they were stored as null. After updating data for ids (set null where id = '') all work properly.
Check your entire row record if contains another guid columns which are empty. Sadly the error is not very informative, for example which column is empty but it is supposed to be a guid.

CsvHelper and Primary Key with Entity Framework

I'm using Entity Framework 6.0.0.0 with Josh Close's great CsvHelper tool.
All was going great BEFORE I added EF to the mix, as I was dealing primarily just with classes, no database, therefore NO PRIMARY KEYS!
So now I have confirmed that my primary keys are setup properly in the DB as auto incrementing identities.
And my class looks something like this:
[Table("tbl_P")]
public class P // PData File
{
// Column P1
public string StrTrId { get; set; }
// NOTE COLUMNS P2-P99 GO HERE
// Column P99
public string StrCo { get; set; }
// Column P100
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int pk_PropertyId { get; set; }
}
Note, I put the Primary Key at the end, hoping that this would help the mapping work without having to somehow shift the columns all around.
I'm reading into the above class like this:
var csv = new CsvReader(reader);
csv.Configuration.HasHeaderRecord = false;
csv.Configuration.WillThrowOnMissingField = false;
csv.Configuration.TrimFields = true;
while (csv.Read())
{
var pr = csv.GetRecord<P>();
}
So I'm not sure if the issue has to do with me putting the Primary Key at the end of the class, or if it's b/c Entity Framework requires the Primary Key to have a default value. Either way, perhaps there's a way to ignore a column in CsvHelper when I process the data?
The errors I'm getting are:
the conversion cannot be performed
"Row: '1' (1 based) Type: 'P' Field Index: '99' (0 based)"
There is no value in the CSV file for this field, which I imagine is the problem since it's an int not an int? but if it's a Primary Key, I can't have it as nullable, right?
Any help would be great here...
So I was able to get it to work.
The error had to do with the empty Primary Key in the class with the non-nullable integer.
So I made the following change:
// Column P100
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int? pk_PropertyId { get; set; } = null
And now it's working fine.
So nothing to do with CsvHelper, that part's working great. It was an Entity Framework thing just having to do with my inexperience using the tool.
Maybe this will help someone else.
I guess it turns out that you can set the Primary Key to any value and the DB will basically ignore it and assign its own value anyways.

ServiceStack's Ormlite Delete not working

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.

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