I have a problem with the attribute DatabaseGenerate(DatabaseGeneratedOption.Computed), it seems to be the case that it is not respected by the AddOrUpdate() call within the Seed method.
I have made a simple project, to illustrate my issue:
public class EFModel : DbContext
{
public EFModel()
: base("name=EFModel")
{
}
public virtual DbSet<MyEntity> MyEntities { get; set; }
}
public class MyEntity
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
[StringLength(50)]
public string Name { get; set; }
[Required]
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
[StringLength(50)]
public string DefaultName { get; set; }
}
The Seed call
protected override void Seed(DummyEF.EFModel context)
{
context.MyEntities.AddOrUpdate(new MyEntity { Id = 10, Name = "Samual", DefaultName = "Sam" });
context.MyEntities.AddOrUpdate(new MyEntity { Id = 11, Name = "David" });
}
When I run the Update-Database command, with the first Seed row (that is with Samual, and specifying a value for the default name) it works fine. When I run it with the second line (that is with Dadid, and without specifying a value for the DefaultName) it fails:
Validation failed for one or more entities. See
'EntityValidationErrors' property for more details.
The table it self is valid, and has a default constraint, so normal insert into via SQL works.
It just seems to be the case, that the Seed is ignoring the fact that
the entity property is marked with the
DatabaseGeneratedOption.Computed attribute.
Any idea why this is ignored?
I am using EF 6 code first.
When you use DatabaseGeneratedOption.Computed you are telling EF not to add or update the information in the database because the database will generate it as is common for timestamps or computed SQL columns, so it makes no sense to try and seed that column. You are getting the validation error because you have it marked required.
The conclusion must be that the current version of EF 6 does not cater for the combination of the attriburtes Required and DatabaseGenerated(DatabaseGeneratedOption.Computed)
So if one wants to set a column to not be nullable, yet avoid EF from validating it before the save to the database, one cannot use the Required attribute.
I guess that the Fluent API or modification of the migration code needs to be used in stead.
Related
I am trying to insert a new row to my table, using EF Core, SQL Server and C#, but I am having trouble getting EF Core to use the identity column properly.
Here's what I am doing:
I am creating a new object using the Entity Framework generated class (I've included the entity class definition at the end of my post)
EmployeePermissions employee_permission = new EmployeePermissions
{
FkEmployee = PkEmployee,
FkPermission = permission_key
};
Then I call db.EmployeePermissions.add(employee_permission), which works on all of my calls where an object comes from [FromBody] <Entity Class> <object_variable> (albeit using other tables).
But here, when I instantiate the class myself, I get this error:
SqlException (0x80131904): Cannot insert explicit value for identity column in table 'Employee_Permissions' when IDENTITY_INSERT is set to OFF.
I don't understand why this is happening — I want it to auto increment the identity column. I used ObjectDumper to see what is getting passed to .Add(), which is as follows:
properties {
PkEmployeePermissions = 0 [System.Int32] // this is the identity column, of course
FkEmployee = 31 [System.Int32]
FkPermission = 6 [System.Int32]
FkEmployeeNavigation = <null>
FkPermissionNavigation = <null>
}
I have investigated the other calls which are working fine (the ones where [FromBody] creates an object) and the identity column simply equals 0 in those calls too, so I don't understand what is different here.
Have I misconfigured something in the database? I have double checked in the column properties that the column PkEmployeePermissions is indeed an identity column, so it should be auto incremented.
Here's the Entity class if it helps:
public partial class EmployeePermissions
{
public int PkEmployeePermissions { get; set; }
public int FkEmployee { get; set; }
public int FkPermission { get; set; }
public Employee FkEmployeeNavigation { get; set; }
public Permission FkPermissionNavigation { get; set; }
}
It turned out I had updated the primary key column, PkEmployeePermission to be an identity column and forgotten to re-generate the database scaffolding using EF Core.
The root cause of the problem was that inside of the OnModelCreating() method (in the generated database context file) the field PkEmployeePermission had the method .ValueGeneratedNever() called on it, which meant that even though it was a primary key, EF Core did not automatically generate an incremented value for that column. By commenting out that method in the database context file, the code worked properly.
The default convention is for the primary key of a class to be called either Id or (classname)Id - this is not the case in your class.
Also: if you're mapping to an IDENTITY column in SQL Server, you must add a relevant data annotation to the key column.
Try this:
public partial class EmployeePermissions
{
// add the [Key] annotation to indicate the primary key of the class/table
[Key]
// add the [DatabaseGenerated(..)] annotation to indicate an IDENTITY column in the table
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PkEmployeePermissions { get; set; }
public int FkEmployee { get; set; }
public int FkPermission { get; set; }
public Employee FkEmployeeNavigation { get; set; }
public Permission FkPermissionNavigation { get; set; }
}
Now you should be able to insert those objects, and have them stored properly in your SQL Server table.
I am trying to seed an user entity in my database. The User entity has an owend property EmailPermissions.
When I run the command
dotnet ef migrations add Initial;
I get the error
The seed entity for entity type 'User' cannot be added because it has the navigation 'EmailPermissions' set. To seed relationships you need to add the related entity seed to 'EmailPermissions' and specify the foreign key values {'UserId'}.
but since EmailPermissions is an owned entity I didn't give it an explicit UserId property, meaning I can't seed it separately in the database.
the entity
public sealed class User : IdentityUser
{
public User()
{
EmailPermissions = new EmailPermissions();
}
/* [..] */
public string TemporaryEmailBeforeChange { get; set; }
public bool IsEmailAwaitingUpdate { get; set; }
public EmailPermissions EmailPermissions { get; set; }
public ICollection<Registeration> Registerations { get; set; }
/* [..] */
}
[Owned]
public class EmailPermissions
{
/* [..] */
public bool Newsletter { get; set; }
public bool PromotionalOffers { get; set; }
public bool PrestationReminders { get; set; }
public bool PrestationOffers { get; set; }
}
The seeding call
private void SeedUser(ModelBuilder builder)
{
builder.Entity<User>().HasData(
new User
{
Id = "37846734-172e-4149-8cec-6f43d1eb3f60",
Email = "foo#foo.foo",
UserName = "foo#foo.foo",
PasswordHash = "AQAAAAEAACcQAAAAEIytBES+jqKH9jfuY3wzKyduDZruyHMGE6P+ODe1pSKM7BuGjd3AIe6RGRHrXidRsg==",
SecurityStamp = "WR6VVAGISJYOZQ3W7LGB53EGNXCWB5MS",
ConcurrencyStamp = "c470e139-5880-4002-8844-ed72ba7b4b80",
EmailConfirmed = true
});
}
If I remove the instantiation of the EmailPermissions property from the constructor I get the following error instead
The entity of type 'User' is sharing the table 'AspNetUsers' with entities of type 'EmailPermissions', but there is no entity of this type with the same key value that has been marked as 'Added'.
How can I seed a user via the .HasData method when it has an owned property ?
Currently this information is missing from the documentation (tracked by #710: Document how to seed owned types). It's explained by EF Core team (with example) in the #12004: Problem seeding data that contains owned type thread:
Owned types must be seeded with a HasData call after the OwnsOne call. Also, since owned types by convention have a primary key generated in shadow state, and since seed data requires keys to be defined, then this requires use of an anonymous type and setting the key.
which is basically what the exception message is telling you.
Following the advice, you should remove the instantiation of the EmailPermissions property from the constructor and add a seeding code like this:
builder.Entity<User>().OwnsOne(e => e.EmailPermissions).HasData(
new
{
UserId = "37846734-172e-4149-8cec-6f43d1eb3f60",
// other properties ...
}
);
Quite annoying and error prone due to the need to know the shadow PK name and the usage of an anonymous type. As the same member mentioned
Note that this would become easier if navigations were supported for seeding, which is tracked by #10000: Data Seeding: Add support for navigations
Thank Ivan Stoev's answer. i add some more code to easy to imagine.
this is code of seed data function base on example.
First adding data of User.
After that add data of owned object.
Data of owned object have to be anonymous because PK will request. This PK will not appear in database. Name should be entity name + Id
Example: Entity XXX => PK will be XXXId
private void SeedUser(ModelBuilder builder)
{
builder.Entity<User>(b =>
{
b.HasData(new User
{
Id = "37846734-172e-4149-8cec-6f43d1eb3f60",
Email = "foo#foo.foo",
UserName = "foo#foo.foo",
// more properties of User
});
b.OwnsOne(e => e.EmailPermissions).HasData(new
{
UserId = "37846734-172e-4149-8cec-6f43d1eb3f60",
Newsletter = true,
PromotionalOffers = true,
PrestationReminders = true,
PrestationOffers = true
});
});
}
If you want to avoid using an anonymous type to specify the shadow property keys, you can declare them explicitly in your model class and configure them with the Fluent API as keys. This way you don't have to guess the property names and it's less error-prone.
If the name supplied to the Property method matches the name of an existing property (a shadow property or one defined on the entity class), then the code will configure that existing property rather than introducing a new shadow property.
Source
In my scenario I wanted the owned-type property to be auto-initialed in the parent class:
public class User
{
EmailPermissions _EmailPermissions;
public EmailPermissions
{
get => _EmailPermissions ??= new EmailPermissions();
set => _EmailPermissions = value;
}
}
When I tried to add seed data I got that nasty exception.
The solution was to pass the User as anonymous type in its HasData call.
I had the same issue seeded my data at startup.
Here is the link to the github issue.
In my case, I was using anonymous types, but the default class constructor was instantiating an instance of the non-nullable child class, which contained the property in question. The fix was to either not instantiate the child class in the default constructor, or use a different constructor that did not instantiate the child class.
Original
public class BaseDownload {
[Key]
public Guid BaseDownloadId { get; set; }
public Guid DownloadCategoryId { get; set; }
public virtual DownloadCategory DownloadCategory { get; set; }
public BaseDownload()
{
this.BaseDownloadId = Guid.NewGuid();
this.DownloadCategory = new DownloadCategory();
}
}
followed by the seed data:
modelBuilder.Entity<BaseDownload>().HasData(
new BaseDownload()
{
BaseDownloadId = Guid.Parse("9150ebd7-dd84-4c97-bf58-62f1c3611545"),
DownloadCategoryId = Guid.Parse("46b087f9-5c71-401f-a5cf-021274463715"),
}
);
Trying to seed the data gave the error "The seed entity for entity type 'BaseDownload' cannot be added because it has the navigation 'DownloadCategory' set. To seed relationships, add the entity seed to 'BaseDownload' and specify the foreign key values {'DownloadCategoryId'}.".
An instance of the child class (DownloadCategory) definitely exists as it was created using the same set of seed data. So I only needed to use the ID property. Adding a new constructor that did not instantiate the child DownloadCategory() class like below resolve the error.
public BaseDownload(bool isSeedData)
{
}
and
modelBuilder.Entity<BaseDownload>().HasData(
new BaseDownload(true)
{
BaseDownloadId = Guid.Parse("9150ebd7-dd84-4c97-bf58-62f1c3611545"),
DownloadCategoryId = Guid.Parse("46b087f9-5c71-401f-a5cf-021274463715"),
}
);
Working with EF Code First, I'd like to specify a schema (Data) for tables, but let EF use its default convention to name the tables.
I'm using DataAnnotations.Schema TableAnnotations to set schema.
[Table("Customers", Schema = "Data")]
public class Customer
{
public int Id{ get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
But this requires me to set Name manually while I want EF to use its convention to name the tables.
I tried to set the Name with an empty string.
[Table("", Schema = "Data")]
That throws the following - very reasonable, expected - error:
The argument 'name' cannot be null, empty or contain only white space.
Is there any way to let EF default to its table name convention while specifying the schema?
Slava Utesinov suggested using modelBuilder.HasDefaultSchema("Data"); to set the default schma.
This will work when there is only one schema that one wishes to set; however will not work when there is more than one schema to apply, without having to set the Table Name manually.
public class ExampleContext: DbContext
{
public ExampleContext() : base("ExampleContext") { }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("Data");
///...
}
This cannot be done as Table attribute has only one constructor, which requires Name to be specified.
Please refer to MSDN - TableAttribute Constructor
public TableAttribute(
string name
)
I've recently started working with .NET CORE v2.
I'm trying to set up my database by using a code-first approach in my web-api template.
Background: I've previously worked with the Laravel framework and I would like to replicate laravel's timestamp() function in migration files which basically creates two columns: UpdatedAt and CreatedAt in a table.
The values in those columns are populated with the correct values when the ORM's (Elqoquent) functions that INSERT or UPDATE columns are used. It's seamless and you don't need to worry about it.
In my c# code I have the following model
public class A {
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime Created_At { get; set; } = DateTime.UtcNow;
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime Updated_At { get; set; }
// I've tried to add "= DateTime.UtcNow;" at the end of Updated_At as well
}
I also have the following class that is used to seed the database.
public static class DatabaseSeeder
{
private static AppContext _context;
public static void SeedDatabase(this AppContext appContext)
{
_context = appContext;
_context.Database.EnsureCreated();
// Verify if data exist.
if (_context.A.Any())
{
return;
}
SeedAs();
}
private static void SeedAs()
{
var defaults = new A[]
{
new A
{
Name = "Value 1",
},
new A
{
Name = "Value 2",
},
new A
{
Name = "Value 3",
}
};
_context.As.AddRange(defaults);
_context.SaveChanges();
}
}
The SeedDatabase function is called in the Configure function of the Startup class which basically seeds the database should it not contain any data at startup.
Problem: The issue I am encoutering is that when I launch my development server the first time, the application notices that the database does not contain any values so it tries to seed it. The following error is returned:
MySql.Data.MySqlClient.MySqlException (0x80004005): Field 'Updated_At'
doesn't have a default value
I don't seem to understand why this fails because when I ran the same piece of code by removing the Updated_At property and its annotation, no error was returned and the database was seeded as expected with the Created_At field containing the value of DateTime.UtcNow.
The expected behavior of the DatabaseGenerated(DatabaseGeneratedOption.Computed annotation is to have a database generated value based on the data type of the property on insert or update.
Can anyone tell me why, it fails when I try to seed my database.
At first, you have already defined the initial value for Created_At in your class: = DateTime.UtcNow;
That's why it works for Created_At but does not work for Updated_At. Since Updated_At does not have an initial value and the Database does not have any default value defined for this column.
To make it work right, you have to define the default values for your SQL Columns in the DbContext Class.
I guess, with MySQL you should use the function NOW():
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<A>()
.Property(s => s.Created_At )
.HasDefaultValueSql("NOW()");
modelBuilder.Entity<A>()
.Property(s => s.Updated_At )
.HasDefaultValueSql("NOW()");
}
Docs: Data Annotations - DatabaseGenerated Attribute in EF 6 & EF Core
I am not fantastic with EF so maybe it's an easy one.
I Have
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
}
I have checked that it finds correct license, and context is a ninject (one per request) DbContext,
But I get a weird error when I call SaveChanges() on the context after running the function above. I get: "The CustomerName field is required."
Now this is weird because CustomerName is in Account (not Licence) they are linked, but still. So here follows some more:
My Account entity
[Required]
public String CustomerName { get; set; }
public virtual ICollection<License> Licenses { get; set; }
...
My License entity
public virtual Account Account { get; set; }
...
My fluent setup
modelBuilder.Entity<Account>().HasMany(x => x.Licenses)
.WithRequired(x => x.Account).WillCascadeOnDelete(false);
I don't understand, because even if there is a failing restraint then why missing CustomerName. I don't touch CustomerName when I delete a license and the CustomerName is set since before.
Update
So here is some more details from the code. The full execution path as far as I can see is
DeleteLicenseAPI below takes the call, the ID is correct, it passes over to a private function.
The private function calls the DeleteLicense shown close to the top of the question.
The Commit() only calls context.SaveChanges();
public ActionResult DeleteLicenseAPI(int licenseId)
{
if (DeleteLicense(licenseId))
{
return Content("ok");
}
return Content("[[[Failed to delete license]]]");
}
private bool DeleteLicense(int licenseId)
{
//todo: sort out busniess rules for delete, is cascaded?
_accountRepository.DeleteLicense(licenseId);
_accountRepository.Commit();
return true;
}
The _accountRepository looks like this
public class EFAccountRepository : EntityFrameworkRepository<Account>
, IAccountRepository
public EFAccountRepository(EvercateContext context) : base(context)
{
}
And here is the code in Ninject that sets it all up
kernel.Bind<EvercateContext>()
.To<EvercateContext>()
.InRequestScope()
.WithConstructorArgument("connectionStringOrName", "EvercateConnection");
kernel.Bind<IAccountRepository>().To<EFAccountRepository>();
So even tho I use Unit of Work as far as I can see (and it shouldn't) nothing else is called in this request before running SaveChanges.
Is there any way to see what a DbContext will do on SaveChanges, without actually running the method (as it throws DbEntityValidationException)
I can imagine that this weird exception could occur if you are initializing the Account navigation property in the License constructor like so:
public License
{
Account = new Account();
}
The flow when you call...
var entityToDelete = context.Licenses.Find(licenseId);
context.Licenses.Remove(entityToDelete);
...is then probably:
License entity gets loaded (without navigation property Account) and attached to the context (state Unchanged)
The constructor sets the Account navigation property, but it doesn't get attached (state Detached)
When you call Remove for the License entity DetectChanges is called internally by EF. It detects that License.Account is refering to a detached entity and attaches it to the context (in state Added). The state of the License is changed to Deleted.
When you call SaveChanges the change tracker finds two entities: The License in state Deleted and the Account in state Added.
Validation runs and finds that the required property CustomerName for the entity Account that is supposed to be inserted into the database is null (because only the default constructor of Account is called).
The validation exception is thrown.
I'm not sure if the details are right but something like that is probably happening.
In any case you should delete the Account = new Account(); from the License constructor and also check if you initialize other reference navigation properties in entity constructors in your codebase as well. (Initializing empty navigation collections is OK.) This is a common source of notoriously strange problems that are difficult to find and understand.
I tried overriding SaveChanges as recommended.
When I did I found a License about to be deleted (as it should) but I also found an Account about to be created.
I changed the DeleteLicense as displayed below.
public void DeleteLicense(int licenseId)
{
var entityToDelete = context.Licenses.Find(licenseId);
entityToDelete.Account = null;
context.Licenses.Remove(entityToDelete);
}
And right away the code works. The License is removed and the account is still there, but no new account is created.
But why, I do not understand why at all.
Is it something in the relation i set with fluent api?
In my case this happened because my entity had a [Required] property that was of type int? which made it nullable. While inspecting the model that came back from the db I saw the property had a value but the entity that ended up being saved to the database had that value stripped during SaveChanges for some reason. When I switched the value to the expected int type all worked just fine. :shrug:
I had a similar issue and for me, it looked like I hadn't correctly established the relationship between Parent and Child in their respective classes.
My fix was to add the attributes specified below to the Child class, for the property that represented its Parent's Id
public class Child
{
[Key, Column(Order = 1)]
public string Id { get; set; }
[Key, ForeignKey("Parent"), Column(Order = 2)] // adding this line fixed things for me
public string ParentId {get; set;}
}
public class Parent
{
[Key, Column(Order = 1)]
public string Id { get; set; }
...
public virtual ICollection<Child> Children{ get; set; }
}