System.DateTime can take a wider range of values than SQL Server's DateTime. Hence there is class System.Data.SqlTypes.SqlDateTime which mimics the later.
Consequently I would have expected Entity Framework to choose SqlDateTime, but it didn't.
So my questions are...
What are the best practices to insure that your DateTime values will not cause problems when you try to save them to your database?
Is there any way of forcing EF to use SqlDateTime?
There's a number of things you can do:
if you're using SQL Server 2008 or newer, you can use the DATE or DATETIME2 data types on the database which offer the same date range as .NET's DateTime
if you can't use those new data types, it will be up to you to handle some checking / validation on your date fields before things are being stored into the persistent store. EF EntityObject offers lots of ways to tap into the process of validating and saving objects - pick one approach that works for you
More specifically, try this: http://www.vfstech.com/?p=111
Maybe this is an old thread but I will post my findings on this for the others :
Let say that we have dev env : EF 5, CodeFirst, SqlCE 4.0 :
public abstract class Entity : IEntity, IEquatable<Entity>
{
public virtual int Id { get; protected set; }
public virtual DateTime LastModified { get; set; }
[DataType(DataType.Date)]
public virtual DateTime CreatedOn { get; set; }
[DataType(DataType.DateTime)]
public virtual DateTime CreatedOn2 { get; set; }
[DataType(DataType.Time)]
public virtual DateTime CreatedOn3 { get; set; }
public virtual DateTime CreatedOn4 { get; set; }
}
with such a custom mapping :
public EntityMapping()
{
HasKey(e => e.Id);
Property(e => e.Id);
Property(e => e.LastModified).IsRequired().IsConcurrencyToken();
Property(e => e.CreatedOn).IsRequired();
Property(e => e.CreatedOn2).IsRequired();
Property(e => e.CreatedOn3).IsRequired();
Property(e => e.CreatedOn4).IsRequired();
}
This produces this, which means that we will have the overflow exception.
Changing the mappings to this while still working with SQL CE 4.0 :
Property(e => e.CreatedOn).IsRequired().HasColumnType("datetime2");
Property(e => e.CreatedOn2).IsRequired().HasColumnType("date");
Property(e => e.CreatedOn3).IsRequired().HasColumnType("date");
Property(e => e.CreatedOn4).IsRequired().HasColumnType("datetime2");
Gives this error.
Switching to SQL Server Standart 2012 seems to solve the problem ( That is not a solution for sure - just for the experiment ). The created SQL Server schema is this.
I am not an expert in Sql but it looks like to me that SQL CE does not support these dates. the problem with development env. remains. DateTime can be substituted but can bring a lot of refactoring here and tehere.
Remember also that SqlDateTime and DateTime are very different.
The solution that I find good - for the code and for the project lifecycle - is to switch between LocalDb and SQL standart as suggested by one of the links above from stackoverflow combined with custom fluentApi mapping settings to equalize model creation or both.
Introducing custom convention in EF as a safety net looks good too.
If anybody has a better all-round solution, for code and for dev-production both, post it.
Related
I HAVE to be overthinking this because it's a super common workflow just not finding the right answer. Using Fluent NHibernate over postgres and given the following simple class and mapping, how can I set the audit data in code, then secure/lock it during subsequent update calls.
public class RecordEntity
{
public virtual int Id {get;set;}
public virtual DateTime CreatedOn {get;set;}
public virtual DateTime UpdatedOn {get;set;}
}
And Mapping
public class RecordEntityMapping:ClassMap<RecordEntity>
{
public RecordEntityMapping()
{
Id(x => x.Id).GeneratedBy.Increment();
Map(x => x.CreatedOn);
Map(x => x.UpdatedOn);
}
}
then I want to be able to do something like this in my repository
...
var record = new RecordEntity();
record.CreatedOn = record.UpdatedOn = DateTime.UtcNow;
await _session.SaveAsync(record); // Created on should be properly persisted.
...
Then an update...
//record passed in from somewhere else...
record.UpdatedOn = DateTime.UtcNow;
await _session.UpdateAsync(record); // CreatedOn here will be who knows what.
...
Ok options.
I could put in a postgres rule that sets a date on insert, then set the property to be generated.insert() and .ReadOnly().
I could set up some kind of interceptor that checks whether I'm doing an update or insert, and react accordingly.
is there a way in nhibernate or postgres that says ignore if default then make sure it's always default on updates.
I know I can always make an extra call to get the data out of the db and merge the records. kind of defeats the purpose.
There's got to be an easier way. What am I missing? What other options do I have?
I am using EF Core code first approach and trying to configure signature strength of the column type ltree using the below snippet.
public class Node
{
public Guid Id { get; set; }
public LTree Path { get; set; }
public string Name { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("ltree");
modelBuilder.Entity<Node>().HasIndex(x => x.Path).HasMethod("gist");
}
This just creates an index with the default signature length of 8 bytes.
I want to override this signature length by explicitly specifying the length like as we do in the postgres query editor
CREATE INDEX "IX_Nodes_Path" ON public."Nodes" USING gist ("Path" gist_ltree_ops (siglen='100'))
Any pointers to achieve this will be of immense help.
I am using posgres version 14.0, EF Core 6 rc, npgsql 6 rc.
Unfortunately this isn't supported by the Fluent API - you'll have to edit your scaffolded migration and use raw SQL to express this.
The provider does have a Fluent API for specify the operator class, but not for specifying options (e.g. siglen).
I am trying to create an EF6 database where two tables, Addresses and Visits, share the same values as primary keys. Visits, conceptually, is an extension of Addresses. I'm splitting the tables because most of the records in Addresses don't require the fields contained in Visits.
I'm using the code first approach. Here's the relevant code for the Addresses:
public class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
[ForeignKey( "ID" )]
public virtual Visit Visit { get; set; }
and for Visits:
public class Visit
{
[Key]
[DatabaseGenerated( DatabaseGeneratedOption.Identity )]
public int ID { get; set; }
[ForeignKey("ID")]
public virtual Address Address { get; set; }
Based on my research, I also needed to include the following in my datacontext's OnModelCreating method:
modelBuilder.Entity<Visit>()
.HasOptional( v => v.Address )
.WithRequired();
Unfortunately, this doesn't work. I can update the database alright, after eliminating scaffolding calls to drop the primary index from Addresses (probably because the add-migration code thinks the primary key is "merely" a foreign key field). But when I run the application I get the following error:
Invalid column name 'Address_ID'.
Invalid column name 'Address_ID'.
From my limited experience with EF6 this looks like someplace deep inside the framework it's expecting there to be fields named 'Address_ID', probably in the Visits table (based on the 'table name'_'field name' naming structure I've seen for other implicitly added fields).
Is what I'm trying to do possible? If so, what am I missing in the configuration?
Additional Info
In trying out bubi's proposed solution, which unfortunately still generates the same error, that I could eliminate the OnModelCreating code and still get functional migration code generated.
Resolution
I finally did what I should've done earlier, which is examine the actual T-SQL code generated by the query which was blowing up. It turns out the problem was not in the Visit/Address linkage, but in a completely separate relationship involving another table. Apparently, somewhere along the way I did something to cause EF to think that other table (Voters) had an Address_ID foreign key field. In reality, the Address/Voter relationship should've been, and originally was, tied to a Voter.AddressID field.
Rather than try to unwind a large number of migrations I opted to blow away the database, blow away the migrations and start from scratch. After recreating the database -- but using bubi's suggestion -- I reloaded the data from backup and, voila, I was back in business.
For the sake of completeness, here's the code I ended up having to put into the OnModelCreating method call to get the Address/Visit relationship to work correctly:
modelBuilder.Entity<Visit>()
.HasRequired( v => v.Address )
.WithRequiredDependent( a => a.Visit );
modelBuilder.Entity<Address>()
.HasRequired( a => a.Visit )
.WithRequiredPrincipal( v => v.Address );
I am a little confused about why I have to use HasRequired in order to be able to use WithRequiredPrincipal/WithRequiredDependent, since not every entry in the Address table has an entry in the Visit table. That would seem to be "optional", not "required". But it appears to work, and maybe the "required" part is just internal to EF's model of the database, not the database itself.
There are 2 problems in the model:
- Only one of the Keys can be autonumbering, the other must get the same Id (this independently by EF).
- A mapping problem.
This model should work.
public class Address
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Description { get; set; }
public virtual Visit Visit { get; set; }
}
public class Visit
{
public Visit()
{
Address = new Address();
}
[Key]
[ForeignKey("Address")]
public int Id { get; set; }
public string Description { get; set; }
public virtual Address Address { get; set; }
}
Example of use
var visit = new Visit
{
Description = "Visit",
Address = {Description = "AddressDescription"}
};
db.Visits.Add(visit);
db.SaveChanges();
In addition to what bubi mentioned, your modelBuilder statement contradicts the model in that it doesn't mention Address.Visit as the inverse property. So it thinks that the property represents a separate relationship and tries to create the Address_ID column for that relationship.
You need to have
modelBuilder.Entity<Visit>()
// from your description sounds like every Visit needs an Address
.HasRequired(v => v.Address )
// need to mention the inverse property here if you have one
.WithOptional(a => a.Visit);
...or just remove the statement completely since you're already using attributes, and EF should be able to figure it out by convention.
I'm using ASP.NET WebAPI and ran into a problem with a nested model that should be communicated via a WebAPI Controller:
The entities "bond, stock etc." each have a list of entities "price". Server-side, I use the following class to match this requirement..
public class Bond : BaseAsset
{
public int ID { get; set; }
public string Name { get; set; }
public virtual List<Price> Prices { get; set; }
}
This leads to the table "Price" having a column for bond, stock etc. and, in case a price is attached to a bond, an entry in its column for bond foreign key.
The error I initially got was
There is already an open DataReader associated with this Command
I fixed that by altering the Connection String to allow MultipleActiveResultSets.
However, I feel there must be better options or at least alternatives when handling nested models. Is it, e.g., a sign for bad model design when one runs into such a problem? Would eager loading change anything?
One alternative to mars is to disable lazy loading
In your DbContext
Configuration.LazyLoadingEnabled = false;
plus when you are loading your data you can explicit load your child tables
context.Bonds.Include(b => b.Prices)
I am using Entity Framework 5 with migrations. My model looks like this:
UserRegistration
Sessions
For each registration there can be many sessions. I was having a problem with a SQL cascading delete error when trying to run migrations, so I attempted to remove the cascade delete by adding the following code:
modelBuilder.Entity<UserRegistration>()
.HasOptional(x => x.Sessions)
.WithMany()
.WillCascadeOnDelete(false);
Now if you know EF you can see an error with that code. I thought it was giving me a 1 to many relationship from UserRegistrations to Sessions, when in fact this code says "Each UserRegistration has an optional Session, each Session has many UserRegistrations". So my 1 to many relationship was going the wrong way. I believe this should have been the code I added.
modelBuilder.Entity<UserRegistration>()
.HasMany(x => x.Sessions)
.WithRequired(x => x.UserRegistration)
.WillCascadeOnDelete(false);
However, when I remove the bad code (first block above), and run add-migration, I get this:
public override void Up()
{
DropForeignKey("dbo.UserRegistrations", "Sessions_Id", "dbo.UserRegistrationSessions");
DropIndex("dbo.UserRegistrations", new[] { "Sessions_Id" });
RenameColumn(table: "dbo.UserRegistrationSessions", name: "Sessions_Id", newName: "UserRegistration_Id");
}
The first two lines (DropForeignKey and DropIndex) looks good, this is removing the foreign key from the UserRegistrations table. The 3rd line, however, comes out of nowhere. There is no Sessions_Id column in the UserRegistrationSessions table. When I run it, I expectedly get an error:
Either the parameter #objname is ambiguous or the claimed #objtype (COLUMN) is wrong.
I'm thinking something is out of whack between my model and my modelbuilder code. Maybe I need to try to make my model look incorrect, but match the modelbuilder code, before moving forward with the fix.
Here is the relevant code in my models:
public class UserRegistration
{
public int Id { get; set; }
public virtual ICollection<UserRegistrationSession> Sessions { get; set; }
}
public class UserRegistrationSession
{
public int Id { get; set; }
public virtual UserRegistration UserRegistration { get; set; }
}
This is more of an answer to the question in the comment, which is 'where is Entity Framework getting its information from'. The answer is probably the serialized version of the model in the resource file attached to the migration.
If you haven't yet deployed the migrations out in the wild, is it possible to remove the offending migration and roll back the DB to before the migration was created?