Pass a NULL value to DateTime Field in LINQ - c#

My database table is like this
CREATE TABLE MYBUDGET.tbl_CurrentProperty
(
[PropID] INT NOT NULL IDENTITY(1,1),
[UpdatedOn] DATETIME NOT NULL,
[Amount] MONEY NOT NULL,
[Remarks] VARCHAR(100) NOT NULL,
)
ALTER TABLE MYBUDGET.tbl_CurrentProperty ADD CONSTRAINT PK_CurrentProperty_PropID PRIMARY KEY ([PropID])
ALTER TABLE MYBUDGET.tbl_CurrentProperty ADD CONSTRAINT DF_CurrentProperty_UpdatedOn DEFAULT (DATEADD(MINUTE,30,DATEADD(HOUR, 5, GETUTCDATE()))) FOR [UpdatedOn]
ALTER TABLE MYBUDGET.tbl_CurrentProperty ADD CONSTRAINT CK_CurrentProperty_Amount CHECK([Amount] > -1)
GO
I'm using LINQ to SQL. In C# I need to pass only [Amount] and [Remarks] fields and other fields must be used with their default values ([PropID] and [UpdatedOn]).
In C# I create tbl_CurrentProperties object like below,
tbl_CurrentProperties currentProperties = new tbl_CurrentProperties();
currentProperties.Amount = 50.00M;
currentProperties.Remarks = "remarks";
and then submit the object to the data context. But here, Linq assigned '1/1/0001 12:00:00 AM' for UpdatedOn field. But this violate the SQL datetime range 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM and Occurring an exception. Also I can't assign a NULL value manually for a DateTime field since its a not nullable type. Anyhow I need to make this to use its DEFAULT Constraint. How do I do this?
PS: I want to use it like this because, My database is Online and Users are in different locations. So If I used DateTime.Now, the time in the user machine may be wrong, and It insert a wrong value into DB. I need to use SQL server time always.

Andrey's answer is partly right. I just tested this and here's what I found.
In your dbml designer, on your UpdatedOn column set the following:
Auto Generated Value = True
Nullable = False
Then, on an INSERT if you use SQL Server Profiler to look at the generated SQL, you'll see that UpdatedOn is not included in the INSERT. Not even a null value. This is important: for SQL Server to use a default value for that colum, the column must be omitted from the INSERT. If you set Nullable = True on the UpdatedOn, LINQ to SQL might be including the column on the INSERT with a null value.
FYI, immediately after the INSERT there should be a SELECT where LINQ to SQL is retrieving the auto-generated value, so your entity object has the latest value.

I recommend you open your DBML file using XML Editor and change that column's type from DateTime to DateTime? by allowing nulls. Save it and the code will be generated for you.
Whenever LINQ to SQL generates wrong DBML, it is better to edit it yourself.

Open your dbml and turn on
"Auto Generated Value" = true for the fields that are auto generated. You should be all good with passing nulls in

Related

SQL DateTime Vs. C# Datetime MinDate

I have a SQL Server database, one of my tables LatestData has a column which is a non-nullable DATETIME, with a default value of 01/01/1970. See code below the the exact T-SQL code for this column.
[MyDateTime] DATETIME DEFAULT (CONVERT([DATETIME], CONVERT([DATE], '1970/01/01 00:00AM', (0)), (0))) NOT NULL
When this table is added into our server code (C#) via an .EDMX data model, the field looks like the following:
public System.DateTime MyDateTime { get; set; }
I add new data to this table through C#, but at the time of adding the row, my MyDateTime column does not have any data.
As the column is Not Null, my MyDateTime field is set to 01/01/0001 automatically.
Trying to add this date to my DateTime column throws the following error:
System.Data.SqlClient.SqlException: The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value.
After doing some research I found that C# Datetime MinDate is 01/01/0001, whereas SQL Server DATETIME's mindate is 01/01/1753 and this is causing the error.
The C# code is passing the 'empty' field as 01/01/0001 to the database which is then trying to convert it. This is obviously unsuccessful.
Is there any way for the database to know to revert to the default value instead of trying to convert the DATETIME firstly, or do so if a conversion fails?
I know that I could set the column in the Datatable to be a DateTime2 or specify a date for this column before adding it via C#, but that doesn't seem like the best way to go about it?
Thanks in advance for any help.
If 1970-01-01 is an acceptable no date set to you, then put it in the C#:
public System.DateTime MyDateTime { get; set; } = new DateTime(1970, 1, 1);
I do think you should consider allowing nulls in the column though, and make it:
public System.DateTime? MyDateTime { get; set; }
You asked for a SQLS-only fix; the only one I can think of right now is to use a stored procedure to do your insert:
--set up an example table with a datetime
create table a(a datetime);
--test that an insert doesnt work out
insert into a(a) values(cast('0001-01-01' as datetime2)); --error!
go
--make a procedure to do the insert logic/conversion
CREATE PROCEDURE a_ins(#a DATETIME2)
AS
BEGIN
INSERT INTO a (a)
--if the date is less than the column will support, default it
SELECT case when #a < CAST('1753-01-01' as datetime2) THEN cast('1970-01-01' as datetime) else cast(#a as datetime) end
END
GO
--quick run the procedure to test
DECLARE #dt2 DATETIME2 = cast('0001-01-01' as datetime2);
EXEC a_ins #dt2 --inserts 1970
You ca choose what range of dates you want to insert - maybe anything before 1970 will become 1970, maybe anything before 1753, maybe only 0001-01-01 .. you choose in the "case when" logic
This is yet another reason why you should prefer DateTime2 over DateTime - The DateTime2 data type supports the same date range as the .Net framework's DateTime struct - from January 1st 0001 to December 31 9999.
Change the MyDateTime data type to DateTime2, and the default value to 0001-01-01:
ALTER TABLE MyTable
ALTER COLUMN MyDateTime DateTime2 NOT NULL
To change the default value constraint you need drop it and re-create it.
This can be easily done using SSMS (just find it, right-click and drop) but using T-SQL you will have use one of the answers from this post since you didn't specify it's name. Once you've done that, you can add it (with a proper name this time):
ALTER TABLE MyTable
ADD CONSTRAINT DF_MyDateTime DEFAULT ('0001-01-01') FOR MyDateTime

SqlBulkCopy into table that Default column values fails when source DataTable row has DBNull.Value

Update: Here is my solution
I have a table defined as:
CREATE TABLE [dbo].[csvrf_References]
(
[Ident] [int] IDENTITY(1,1) NOT NULL,
[ReferenceID] [uniqueidentifier] NOT NULL DEFAULT (newsequentialid()),
[Type] [nvarchar](255) NOT NULL,
[Location] [nvarchar](1000) NULL,
[Description] [nvarchar](2000) NULL,
[CreatedOn] [datetime] NOT NULL DEFAULT (getdate()),
[LastUpdatedOn] [datetime] NOT NULL DEFAULT (getdate()),
[LastUpdatedUser] [nvarchar](100) NOT NULL DEFAULT (suser_sname()),
CONSTRAINT [PK_References] PRIMARY KEY NONCLUSTERED ([ReferenceID] ASC)
) ON [PRIMARY]
I have a DataTable with columns that match the table column names and data types. The DataTable is filled out with DBNull.Value in CreatedOn, LastUpdatedOn and LastUpdatedUser. ReferenceID is already generated. When I call the following code I get the error below.
Code:
SqlBulkCopy bulkCopy = new SqlBulkCopy(conn, SqlBulkCopyOptions.Default, bulkCopyTran);
bulkCopy.DestinationTableName = table.TableName;
bulkCopy.ColumnMappings.Clear();
foreach (DataColumn col in table.Columns) bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
bulkCopy.WriteToServer(table);
Error:
Error trying to BulkCopy table csvrf_References
System.InvalidOperationException: Column 'CreatedOn' does not allow DBNull.Value.
at System.Data.SqlClient.SqlBulkCopy.ConvertValue(Object value, _SqlMetaData metadata, Boolean isNull, Boolean& isSqlType, Boolean& coercedToDataFeed)
I have looked all over and I can't seem to find an answer for this. The SqlBulkCopy class seems not to honor default values even though it says it does. What am I doing wrong here?
For part 1, "field that is NOT NULL with a DEFAULT", you should not be sending the field in the first place. It should not be mapped. There is no need to change that field to accept NULLs just for this.
For part 2, "field that is NULL with a DEFAULT", that will work to get the default value when passing in DbNull.Value, as long as you don't have the SqlBulkCopyOptions set to KeepNulls, else it will insert an actual database NULL.
Since there is some confusion about the SqlBulkCopyOption of KeepNulls, let's look at its definition:
Preserve null values in the destination table regardless of the settings for default values. When not specified, null values are replaced by default values where applicable.
This means that a DataColumn set to DbNull.Value will be inserted as a database NULL, even if the column has a DEFAULT CONSTRAINT, if the KeepNulls option is specified. It is not specified in your code. Which leads to the second part that says DbNull.Value values are replaced by "default values" where applicable. Here "applicable" means that the column has a DEFAULT CONSTRAINT defined on it. Hence, when a DEFAULT CONSTRAINT exists, a non-DbNull.Value value will be sent in as is while DbNull.Value should translate to the SQL keyword DEFAULT. This keyword is interpreted in an INSERT statement as taking the value of the DEFAULT constraint. Of course, it is also possible that SqlBulkCopy, if issuing individual INSERT statements, could simply leave that field out of the column list if set to NULL for that row, which would pick up the default value. In either case, the end result is that it works as you expected. And my testing shows that it does indeed work in this manner.
To be clear about the distinction:
If a field in the database is set to NOT NULL and has a DEFAULT CONSTRAINT defined on it, your options are:
Pass in the field (i.e. it will not pick up the DEFAULT value), in which case it can never be set to DbNull.Value
Do not pass in the field at all (i.e. it will pick up the DEFAULT value), which can be accomplished by either:
Do not have it in the DataTable or query or DataReader or whatever is being sent in as the source, in which case you might not need to specify the ColumnMappings collection at all
If the field is in the source, then you must specify the ColumnMappings collection so that you can leave that field out of the mappings.
Setting, or not setting, KeepNulls does not change the above noted behavior.
If a field in the database is set to NULL and has a DEFAULT CONSTRAINT defined on it, your options are:
Do not pass in the field at all (i.e. it will pick up the DEFAULT value), which can be accomplished by either:
Do not have it in the DataTable or query or DataReader or whatever is being sent in as the source, in which case you might not need to specify the ColumnMappings collection at all
If the field is in the source, then you must specify the ColumnMappings collection so that you can leave that field out of the mappings.
Pass in the field set to a value that is not DbNull.Value, in which case it will be set to this value and not pick up the DEFAULT value
Pass in the field as DbNull.Value, in which case the effect is determined by whether or not SqlBulkCopyOptions is being passed in and has been set to KeepNulls:
KeepNulls is not set will pick up the DEFAULT value
KeepNulls is set will leave the field set to NULL
Here is a simple test to see how the DEFAULT keyword works:
--DROP TABLE ##DefaultTest;
CREATE TABLE ##DefaultTest
(
Col1 INT,
[CreatedOn] [datetime] NOT NULL DEFAULT (GETDATE()),
[LastUpdatedOn] [datetime] NULL DEFAULT (GETDATE())
);
INSERT INTO ##DefaultTest (Col1, CreatedOn) VALUES (1, DEFAULT);
INSERT INTO ##DefaultTest (Col1, LastUpdatedOn) VALUES (2, DEFAULT);
INSERT INTO ##DefaultTest (Col1, LastUpdatedOn) VALUES (3, NULL);
INSERT INTO ##DefaultTest (Col1, LastUpdatedOn) VALUES (4, '3333-11-22');
SELECT * FROM ##DefaultTest ORDER BY Col1 ASC;
Results:
Col1 CreatedOn LastUpdatedOn
1 2014-11-20 12:34:31.610 2014-11-20 12:34:31.610
2 2014-11-20 12:34:31.610 2014-11-20 12:34:31.610
3 2014-11-20 12:34:31.610 NULL
4 2014-11-20 12:34:31.613 3333-11-22 00:00:00.000
“SQLBulkCopy column does not allow DbNull.value” error is due to source and destination table has different column order.
Reading the documentation regarding SqlBulkCopy, particularly SqlBulkCopyOptions, I would draw the same conclusion that you did: SQL Server should be "smart" enough to use the default constraint where applicable, especially since you are not using the SqlBulkCopyOptions.KeepNulls attribute.
However, in this case I suspect the documentation is subtly incorrect; if not incorrect it is certainly misleading.
As you have observed, with a non-nullable field with a default constraint (in this case GetDate()) the SqlBulkCopy fails with the aforementioned error.
As a test, try creating a second table that mimics the first, but this time make the CreatedOn and LastUpdatedOn fields nullable. In my tests, using the default options (SqlBulkCopyOptions.Default) the process works without error and CreatedOn and LastUpdatedOn both have the correct DateTime value populated in the table despite the fact that the DataTable's values for those fields were DBNull.Value.
As yet another test, using the same (nullable fields) table, perform the SqlBulkCopy only this time use the SqlBulkCopyOptions.KeepNulls attribute. I suspect you will see the same results I did, that is, CreatedOn and LastUpdatedOn are both null in the table.
This behavior is similar to executing a "vanilla" T-SQL statement to insert data into the table.
Using the original table (non-nullable fields) as an example, if you execute
INSERT INTO csvrf_References ([Type], [Location], [Description], [CreatedOn], [LastUpdatedOn], [LastUpdatedUser])
VALUES ('test', 'test', 'test', null, null, null)
you will receive a similar error regarding null values not being allowed in the table.
However, if you omit the non-nullable fields from the statement SQL Server uses the Default Constraints for those fields:
INSERT INTO csvrf_References ([Type], [Location], [Description]
VALUES ('test', 'test', 'still testing')
Based on this, I would suggest either making the fields nullable in the table (not really a great option in my opinion) OR using a "staging" table for the SqlBulkCopy process (where the fields are nullable and have a similar default constraint in place). Once the data is in the staging table execute a second statement to move the data into the actual final destination table.

getdate() not working with Entity Framework

So I have a column named Timestamp in my database with a default value of GetDate().
Whenever I use Entity Framework to insert a record, Timestamp is null.
Why is this the case?
edit your EDM, make the following change:
Type="datetime"
Nullable="false"
StoreGeneratedPattern ="Computed"
ref:
http://bibby.be/2009/07/entity-framework-sql.html
Try changing type to datetime2. See this article for more information. Also try using Calculated storegeneratedpattern
try changing to datetime2 and then workaround by adding Trigger
SET NOCOUNT ON;
UPDATE ASBLAH
SET YourFieldChangeTime = getdate()
WHERE YOURId IN(SELECT AS_ID FROM INSERTED)
If you need to read data back of this date you can do this after context.SaveChanges()
context.Refresh(System.Data.Objects.RefreshMode.StoreWins, p);
Entity Framework is probably creating an insert statement that includes all fields, with null being the value for Timestamp, since you did not provide one.
If null is allowed for that column, then that is what will be inserted. If null is not allowed, then the default value will be used, which will resolve to GetDate().
Does null need to be allowed for your table?

Entity Model - How to use Database's Default Column Values while inserting

I am using SQLServer 2008, WebForms, C#, Frameowrk 3.5, Entity Framework 1
I generated entity model from DB.
My DB has various tables & lets take example of one table user
It has fields id, name, email, created_on, is_active, is_on_leave
default values for is_active and on_leave properties are default to 0 ( zero ) in db
But When I try to insert a record in this table using entity model, it saves NULL in these fields. How to avoid this? I don't want to set a value for them from my page and want to use the one mentioned in DB.
How can I achieve that?
This columns can be nullable. Right click property in designer, choose properties and set StoreGeneratedPatern for property to computed in the property window.
I found similar question:
Set default value in EF designer datetime
The is_active and is_on_leave columns in your database are nullable. If you make them non-nullable the default values should be used when you do an insert.
ALTER TABLE [user] ALTER COLUMN [is_active] DEFAULT(0) NOT NULL
ALTER TABLE [user] ALTER COLUMN [is_on_leave] DEFAULT(0) NOT NULL
GO

Error "The primary key column of type 'DateTime' cannot be generated by the server."

I have a composite primary key (Int and DateTime) on one of my tables. When I try to add a new record using LINQ (with the DateTime field set to "AutoGeneratedValue = true", and getdate() as default value on the server), I get the following errors:
The primary key column of type 'DateTime' cannot be generated by the server.
Any idea why this is? I believe it has worked before but I can't make out what would cause it to stop working. Unfortunately there is little specific information about this kind of error.
Also, manually committing a row through SQL Server Management Studio works fine and is correctly autogenerating the datetime value.
Edit - Got it working - Must be a bug in LINQ to SQL: I got it working if I set "IsPrimaryKey" for the DateTime to false within the DBML while keeping the composite key on the database side.
So this works:
SQL Server: Composite Primary Key (Id INT, MyDate DATETIME - AUTOGENERATED)
DBML: Mark only Id as Primary Key. Mark MyDate as Autogenerated, but not as Primary Key.
I think that's a bug in LINQ to SQL.
You need to set the default of the field in SQL Server to self-generate. For DateTime fields this is normally GETDATE().
Edit:
Apart from AutoGeneratedValue = true you also need to set Auto-Sync to OnInsert.

Categories

Resources