Fluent NHibernate mapping for DateTime with default value - c#

I know that there are many threads about this and I have read most of them. However for me a couple of things remain unclear and still do not work.
If I have on my database schema a field of type DateTime and I like to assign it a default value I would do something like this:
create table [mySchema].[MyTable](
ObjectGuid uniqueidentifier CONSTRAINT Id PRIMARY KEY,
SomeTextToStore nvarchar(128) NULL,
CDate datetime NOT NULL DEFAULT GETDATE(),
CUser nvarchar(64) DEFAULT CURRENT_USER
);
GO
(Don't know if it is important: I am using SQL Server Express 2014. Fluent configuration is for SQL Server 2012.)
This works fine when doing an INSERT from ISQL, inserts a timestamp of the moment when the record was added.
Using fluent I would write something like this:
Domain:
public class MyObject
{
public virtual Guid Id {get; set}
public virtual string SomeTextToStore {get; set;}
public virtual DateTime? CDate {get; set;}
public virtual string CUser {get; set;}
}
NOTE: I made CDate nullable!
And a mapping class like this:
class MyObjectMap : ClassMap<MyObject>
{
public MyObjectMap()
{
Table("MySchema.MyTable");
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.SomeTextToStore).Length(128).Nullable();
Map(x => x.CDate).Not.Nullable().Default("getdate()");
Map(x => x.CUser).Not.Nullable().Default("CURRENT_USER);
}
}
In the program (in my case this is a library that can be called from several type of programs) I do something like:
public void EnterSomeText()
{
using (var session = sessionManager.OpenSession())
{
using (var transaction = session.BeginTransaction())
{
var myObj = new MyObject();
myObj.SomeTextToStore("bla bla bla");
session.SaveOrUpdate(myObj);
transaction.Commit();
}
session.Close();
}
}
This ends always in a DateTime overflow exception! (SqlDateTime overflow. Must be between 1/1/1753 12:00:00 AM and 12/31/9999 11:59:59 PM)
It looks like not passing a value to the file CDate is causing the problem. When I add the default in my library like such it works:
...
myObj.SomeTextToStore("bla bla bla");
myObj.CDate = DateTime.Now; // <---- Set the date here
session.SaveOrUpdate(myObj);
...
But this is not really the solution....
Questions:
What am I doing wrong / missing ?
What is the correct strategy when
defining defaults? Doing it on the database or just in code? (When
starting a plain new vanilla project, I would prefer doing
everything from C# code, even create and later update the schema...)
Regarding domain classes: Is it wise to create constructors,
that fill fields with defaults?
I do this for the field CUser because in CUser I like to add the current user context
public MyObject()
{
var o = System.Security.Principal.WindowsIdentity.GetCurrent();
if (o != null)
{
CUser = o.Name;
}
}
Instead of filling the CDate field with the current date in my DB-access layer library I could do it also in the constructor of the domain-class like such:
public MyObject()
{
var o = System.Security.Principal.WindowsIdentity.GetCurrent();
if (o != null)
{
CUser = o.Name;
}
CDate = DateTime.Now;
}
Many thanks for your help and comments!

Usually for this type of mapping I do the following in my mapping:
DynamicInsert();
DynamicUpdate();
This way if you have nullable types in C# and you don't set them to anything nhibernate will not include them in the insert or update statement. I never really like it when nhibernate is updating columns that weren't changed in the code anyway.
Furthermore when you specify .Not.Nullable(); and .Default("getdate()") in the mapping all this is used for is schema generation. It's not actually used by nhibernate to do any validation or defaulting. That is left up to the database.

You have defined this column as NOT NULL in the database and Nullable() in the mapping. FluentNHibernate is sending DbNull to the database, which is getting rejected, since the database column is not nullable.
You should decide where your logic resides. Is this your code or the database who is the master?
Setting default value in FlientNHibernate was discussed, for example, in this question.
Suggested code from there is:
Map(x => x.SubmitionDate).Default("getdate()").Not.Nullable();
In general, I would always advise to use NHibernate Profiler (paid tool) to see what queries go to the database and why they fail. Invaluable for optimisation, you are using ORM, be careful now!

Related

Specify EF Core column/field as read only

I have a SQL Server table with certain fields that are set by the database via default values that, once saved, should never been modified again (e.g. DateCreated).
In the Entity Framework Core 2.1 model builder or classes, how do we "mark" a field as essentially read-only? In other words, I don't want any code to be able to set or overwrite these fields.
Based on my searching, would I add .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity) at the end of .Property()?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Doohicky>(entity =>
{
... // other fields
entity.Property(e => e.DateCreated).HasDefaultValueSql("(getdate())");
... // other fields
});
}
Or do I add a [DatabaseGenerated(DatabaseGeneratedOption.Identity)] annotation to the DateCreated field?
public class Doohicky
{
public DateTime DateCreated {get; set;}
}
Or is there another way entirely?
I want it such that in the future, if anybody decides to write something like this, an error would be thrown.
model.DateCreated = new DateTime();
dbContext.SaveChanges() // errors out
Any insight would be greatly appreciated.
The EF Core intended way is to set AfterSaveBehavior property to value other than the default Save:
Gets a value indicating whether or not this property can be modified after the entity is saved to the database.
If Throw, then an exception will be thrown if a new value is assigned to this property after the entity exists in the database.
If Ignore, then any modification to the property value of an entity that already exists in the database will be ignored.
There is no dedicated fluent API yet, so you need to set it directly through mutable property metadata like this:
entity.Property(e => e.DateCreated)
.HasDefaultValueSql("(getdate())")
.Metadata.AfterSaveBehavior = PropertySaveBehavior.Throw; // <--
Update (EF Core 3.x): Starting with EF Core 3.0, many properties like this have been replaced with Get / Set extension method pairs, so the relevant code now is as follows:
.Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Throw);
[Required, DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated {get; set;}
I've done this in the past with auditable properties such as DateCreated, DateModified, etc. This solution probably isn't ideal for excluding specific properties in various objects (although you could probably do something with a custom attribute, etc.).
I override SaveChanges/Async(), then loop through all the changed objects that the context is tracking. All of my objects use the same base class so I can achieve this through the following:
var changes = ChangeTracker.Entries<BaseEntity>().Where(x => x.State == EntityState.Added || x.State == EntityState.Modified);
With those objects, I loop over them and set some auditable properties, or ignore certain properties if the object isn't new. First, I have a collection of strings which represent property names that I want to exclude. I then loop over the collection and ignore the properties where the property name matches that of the excluded collection. See below:
// A collection of property names which should not be updated
var excludedProperties = new[] { "CreatedBy", "CreatedDateUtc" };
foreach (var change in changes)
{
// If new, do as you'd like
// If used, ignore date created
Array.ForEach(excludedProperties, prop =>
{
change.Property(prop).IsModified = false;
});
}

Sequence not working with LINQ [duplicate]

SQL server 2005 database table has a column 'createdon' for which default value set to getdate(). I am trying to add a record using entity framework. 'createdon' column is not getting updated.
Did I miss any property in Entity framework, please suggest.
This is one of the few issues that are problematic with Entity Framework. Say you have a class that looks like this:
public class MyEntity
{
// Id is a PK on the table with Auto-Increment
public int Id { get; set; }
// CreatedOn is a datetime, with a default value
public DateTime CreatedOn { get; set; }
}
Now, you want to insert a new element:
using(var context = new YourContext())
{
context.MyEntities.Add(new MyEntity())
}
Entity Framework knows how to handle an auto-increment primary key because of the definition in the EDMX. It will not try to insert a value for the Id property. However, as far as Entity Framework is concerned, CreatedOn has a value: the default DateTime. Because Entity Framework cannot say "well, it has a value but I should ignore it", it will actively insert the record with the CreatedOn property value, bypassing the default value on your column definition on your table.
There is no easy way to do this. You can either actively set the CreatedOn property to DateTime.Now when you insert that item. Or you can create an interface and an extension method pair:
public interface ICreatedOn
{
public DateTime CreatedOn { get; set; }
}
public partial class MyEntity : ICreatedOn
{
}
public static TEntity AsNew<TEntity>(this TEntity entity) where TEntity : ICreatedOn
{
if(entity != null)
entity.CreatedOn = DateTime.Now;
return entity;
}
using(var context = new YourContext())
{
context.MyEntities.Add(new MyEntity().AsNew())
}
Edit: To expand on this point, the reason why this is an unresolvable issue is because of the meaning behind an autoincrement field and a field with a default value constraint. An auto-increment field should, by definition, always be handle by the server, using a seed and all that jazz. You cannot specify a value for an auto-increment field on an insert unless you have used SET IDENTITY INSERT ON. A default value, however, is just a hint that say "if I don't specify any value, use this". Because value types in .NET cannot be null, there will always be a value and Entity Framework cannot infer that the default value for that field, at that time, means that you want it to be defaulted on the SQL server.
Next to using the designer and some more nifty stuff shown already, you can also mark the columns as being calculated by simply setting the DatabaseGenerated attribute on the field:
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime CreatedOn { get; set; }
You can set StoreGeneratedPattern to Computed (as Malcolm suggested) in the GUI of the entity data model as well.
Open your .edmx file in Visual Studio
Open the properties of the field (click on the field -> hit
F4 or right click->properties)
Set StoreGeneratedPattern to Computed in the properties window
as shown below:
I've got around this issue by telling EF that the column is 'computed', and should therefore be left alone for inserts.
If you look in the configuration for the generated entity
namespace Data.Context
{
// Table
internal partial class MyTableConfiguration : EntityTypeConfiguration<MyTable>
{
public MyTableConfiguration(string schema = "dbo")
{
ToTable(schema + ".MyTable");
HasKey(x => x.Id);
Property(x => x.ColumnName).HasColumnName("ColumnName").IsOptional().HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
....

Optimistic Concurrency

I have an Entity Framework Project with several linked entities. Since it is utilized by multiple users at once I've set up a RowVersion-Field for entities which are likely to be edited by several users at once. Unfortunately I now get an OptimisticConecurrencyException every time I try to save a new entity, which is linked to an already existing entity.
Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=472540 for information on understanding and handling optimistic concurrency exceptions.
The problem is now that this error doesn't really give any pointers as to where the error really lies. It could either be the underlying model that is modified in the meantime, there could be a validation error on the new model or something else.
The code I use to add the new entity is as follows:
using (ctx = new DbContext())
{
try
{
ctx.Samples.Add(model);
ctx.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
LogManager.HandleException(ex.InnerException);
}
}
model is the model i want to add to the database
Edit: As seen above i modified the code to ignore the update of an underlying model. Furthermore i have verified through:
ctx.Database.log = s => Debug.Write(s);
That only an insert statement is sent to the database and not an additional update statement.
INSERT [dbo].[Samples]([IDSample], [ModificationDate], [IDUser])
VALUES (#0, #1, #2)
SELECT [RowVersion]
FROM [dbo].[Samples]
WHERE ##ROWCOUNT > 0 AND [IDSample] = #0 AND [ModificationDate] = #1
I would understand the exception if i would update an entity and the rowversion column wouldn't match, but in this case it's a completely new entity. Is there a way to see if one of the properties is malformed?
Edit2:
Instead of just trimming the milliseconds i now used DateTime.Today instead of DateTime.Now which works. Seemingly there is some problem with datetime2(4) on ModificationDate. I already made sure that ModificationDate is truncated to 4 milliseconds so there should be no parse error.
Edit3:
After switching back to DateTime.Now and trimming the milliseconds it stopped working and the entities are not longer inserted into the database. Could this be caused by the fact that the sql server has problems matching the entities based on millisecond values. I executed the EF generated SQL as seen above with some fictional values and it went through although on some occasions the query didn't return a rowversion-value. In terms of the entity framework, the client would interpret this as a return value of 0 lines and therefore call an concurrency-exception. (It should also be of note that the ModificationDate together with the IDSample is the primary key of the entity.)
Edit4:
I'm now using DateTime.Today and then add the needed precision, which works for me. This can be flagged as solved. (Altough i would have expected that EF can take care of datetime-format-conversion by itself :/)
The question I have is where are/were you adding the DateTime? You are creating too many steps to hammer out this problem. Creating a datetime, modifying it, etc.
If you're entity is inheriting from a base class with mapped properties do your concurrency add/update in the DbContext override of SaveChanges().
Here's an example: (written without optimized syntax)
public abstract class EntityBase
{
public int Id {get; set;}
public DateTime CreationDate {get; set;}
public DateTime? ModifyDate {get; set;}
public string VersionHash {get; set;}
}
public static class EntityBaseExtensions
{
public static void MyBaseEntityMapping<T>(this EntityTypeConfiguration<T> configuration) where T : EntityBase
{
configuration.HasKey(x => x.Id);
configuration.Property(x => x.CreationDate)
.IsRequired();
configuration.Property(x => x.ModifyDate)
.IsOptional();
configuration.Property(x => x.VersionHash).IsConcurrencyToken();
}
}
public class MyEntity : EntityBase
{
public string MyProperty {get; set;}
}
public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
public MyEntityMapping()
{
this.MyBaseEntityMapping();
Property(x=>x.MyProperty).IsRequired();
}
}
public class MyContext : DbContext
{
....
public override int SaveChanges()
{
this.ChangeTracker.DetectChanges(); //this forces EF to compare changes to originals including references and one to many relationships, I'm in the habit of doing this.
var context = ((IObjectContextAdapter)this).ObjectContext; //grab the underlying context
var ostateEntries = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added); // grab the entity entries (add/remove, queried) in the current context
var stateEntries = ostateEntries.Where(x => x.IsRelationship == false && x.Entity is EntityBase); // don't care about relationships, but has to inherit from EntityBase
var time = DateTime.Now; //getting a date for our auditing dates
foreach (var entry in stateEntries)
{
var entity = entry.Entity as EntityBase;
if (entity != null) //redundant, but resharper still yells at you :)
{
if (entry.State == EntityState.Added) //could also look at Id field > 0, but this is safe enough
{
entity.CreationDate = time;
}
entity.ModifyDate = time;
entity.VersionHash = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10); //this an example of a simple random configuration of letters/numbers.. since the query on sql server is primarily using the primary key index, you can use whatever you want without worrying about query execution.. just don't query on the version itself!
}
}
return base.SaveChanges();
}
....
}

DbContext Entity framework Datetime.Now fields

Have two fields with data type datetime.
Added
Modified
When inserting new record values for both fields must be System.DateTime.Now;
but when updating only Modified needs to be changed.
I can set StoreGeneratedPattern to Computed and handle Modified field with GETDATE() in database but problem is field Added.
My guess is that I have to override SavingChanges() or something similar but don't know how.
EDIT : What I have try so far
Added another class in my project with fallowing code
namespace Winpro
{
public partial class Customer
{
public Customer()
{
this.Added = DateTime.UtcNow;
}
}
}
but then cannot build solution
Type 'Winpro.Customer' already defines a member called 'Customer' with the same parameter types
One option is to define a constructor for the type that sets the field.
Big important note: unless you know exactly what you're doing, always store dates and times in a database in UTC. DateTime.Now is the computer's local time which can vary according to daylight savings, timezone changes (brought about by political/legislative reasons), and can end up rendering date information useless. Use DateTime.UtcNow.
public partial class MyEntity {
public MyEntity() {
this.Added = DateTime.UtcNow;
}
}
We did something quite similar in the past.
There was the need to store both Date and Time and the responsible for creating the record. Also, on every change, dispite if there's an audit record or not, the base record should also get a Date and Time and the user responsible for the changes.
Here's what we have done:
Interfaces
To add some standard behavior and make things more extensible, we've created two interfaces, as follows:
public interface IAuditCreated
{
DateTime CreatedDateTime { get; set; }
string CreationUser { get; set; }
}
public interface IAuditChanged
{
DateTime LastChangeDateTime { get; set; }
string LastChangeUser { get; set; }
}
Override SaveChanges() to add some automatic control
public class WhateverContext : DbContext
{
// Some behavior and all...
public override int SaveChanges()
{
// Added ones...
var _entitiesAdded = ChangeTracker.Entries()
.Where(_e => _e.State == EntityState.Added)
.Where(_e => _e.Entity.GetType().GetInterfaces().Any(_i => _i == typeof(IAuditCreated)))
.Select(_e => _e.Entity);
foreach(var _entity in _entitiesAdded) { /* Set date and user */ }
// Changed ones...
var _entitiesChanged = ChangeTracker.Entries()
.Where(_e => _e.State == EntityState.Modified)
.Where(_e => _e.Entity.GetType().GetInterfaces().Any(_i => _i == typeof(IAuditChanged)))
.Select(_e => _e.Entity);
foreach(var _entity in _entitiesChanged) { /* Set date and user */ }
// Save...
return base.SaveChanges();
}
}
Do not simply copy and paste!
This code was written a few years ago, on the age of EntityFramework v4. It assumes that you have already detected changes (ChangeTracker available) and some other.
Also, we have absolutely no idea of how this code impacts performance on any way. That's because the usage of this system is much or related to viewing than updating and also because it's a desktop application, so we have plenty available memory and processing time to waste.
You should take that into account and you might find a better way to implement this. But the whole idea is the same: filter which entities are being updated and which are being added to properly handle that.
Another approach
There are many approaches to this. One other that might be better for performance on some cases (but also more complex) is to have some sort of proxy, similar to an EF proxy, handling that.
Again, even with an empty interface, it's good to have one to clearly distinguish between auditable records and regular ones.
If possible to force all of them having the same property name and type, do it.

Fluent NHibernate SqlDateTime overflow exception

I'm mapping a very simple Users table, and i have a column named 'LastLoginDate' which is defined as nullable in sql server.
My mapping looks like this :
public Users {
Id(x => x.UserId);
Map(x => x.UserName);
...
...
Map(x => x.LastLoginDate).Nullable();
}
But everytime I try to save this entity programatically, i always get the SqlDateTime overflow exception.
If i try to enter a manual sql statement with 'null' in this column it works.
If i comment out just this property, it will work as well.
What can be the problem ???
Thanks in advance!
Your entity should look like this:
public class User
{
public virtual DateTime? LastLoginDate {get;set;}
// etc
}
Then, your map should work properly.
edit: The ? after DateTime specifies that it is Nullable, and is a short form for Nullable<DateTime>. If this isn't the cause of your error, you may want to check that Fluently.Configure specifies the correct version of SqlServer.

Categories