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
)
Related
I have added new property in my Entity Model as the new column has got added in DB table. But In that column might or might be there in other client database. So, How handle this? I have tried modelBuilder.Entity<Customer>().Ignore(customer => customer.FullName);
But it's not ignoring the property in entity. Because we have entity mapped class so it is not ignoring it.
Solution please?
If you add the [NotMapped] attribute, entity framework will not create a column for it.
using System.ComponentModel.DataAnnotations.Schema;
namespace DomainModel
{
public partial class Customer
{
public int Id { get; set; }
public string Name { get; set; }
[NotMapped]
public string FullName { get; set; }
}
}
Or if you want to map the column, but in some databases it already exists, here's a migration which will add the column only if it does not exist.
namespace DataAccess.Migrations
{
using System;
using System.Data.Entity.Migrations;
public partial class AddFullNameToCustomer : DbMigration
{
public override void Up()
{
Sql(#"IF COL_LENGTH('Customer', 'FullName') IS NULL
BEGIN
ALTER TABLE Customer
ADD [FullName] varchar(200) null
END");
}
public override void Down()
{
}
}
}
Just stop it. You're creating a world of pain for yourself.
If you want to have a dynamic schema, then you shouldn't be using Entity Framework at all.
Simplify and avoid all this headache by creating a migration that ensures that the field gets created in every single database (so that at runtime, the context will always match the database) and make sure the migration is executed before the app runs.
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"),
}
);
I'm trying to create a generic Add that will return the actual values from the DB, because some of the values might be calculated by SQL.
For example:
public partial class Customer
{
public string ClientNum { get; set; }
public string ClientName { get; set; }
public string CompanyName { get; set; }
public string Adress { get; set; }
public System.Guid SysRowID { get; set; }
}
SysRowId is calulated at SQL as newid(). So, after inserting the new record I want to do a Find. How may I do that in a generic way?
So far I have this:
var newDBRow = CreateDBRow(tableIndex);
FillValues(newDBRow);
Db.Set(newDBRow.GetType()).Add(newDBRow);
Db.SaveChanges();
var entry = Db.Entry(newDBRow); //SysRowID is blank at CurrentValues
newDBRow = Db.Set(newDBRow.GetType()).Find(KeysNeededHere); //Unable to get the entity keys.
I tried to use the entry, but SysRowID is still blank at CurrentValues. Also, I tried to use the Find but it needs keys and I can't add the ClientNum since I want to do it in a generic way for all entities.
DbSet.Find method expect the value of the key. If SysRowID is not defined as your key this method will never return a value even if SysRowID has the correct value.
Database First:
If you are using Database First, then in your EDMX model just right click on your property SysRowID and click on Properties, then change the StoreGeneratedPattern value to Computed.
Code First:
If you are using Code First approach then you must decorate your property SysRowID with DatabaseGenerated attribute and pass DatabaseGeneratedOption.Computedas a parameter to the constructor of the attribute. At the end you will have this code on your property
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public System.Guid? SysRowID { get; set; }
By doing this either you are in Code First or Database First, EF will know that this property is computed by the database and it will retrieve it after insert success.
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.
This question already has an answer here:
How to map inherited entities in EF code-first
(1 answer)
Closed 2 years ago.
I searched the threads on here and found multiple similar posts but no solutions
Assume I have a User table in my db that I've mapped to a simple User entity
public class User{
public int UserId {get;set;}
public string Username {get;set;}
}
I want to create a new class that will encapsulate an ExternalUser which has all the same fields as User but adds a few more fields. The fields for my ExternalUser will be populated from a view in the db that pulls in both the fields from User and the additional fields required for ExternalUser
public class ExternalUser : User{
public int SomeExternalId{get;set;};
public string SomeExternalProp{get;set;};
}
but no matter how I seem to define my mappings for this new object I get the following error:
The property 'UserId' is not a declared property on type 'ExternalUser'. Verify that the property has not been explicitly excluded from the model by using the Ignore method or NotMappedAttribute data annotation. Make sure that it is a valid primitive property.
Can someone share the correct way to map this. Its stuff like this that makes me hate EF, simply inheriting a POCO shouldn't cause it to blow up, especially not when all the fields exist in the underlying view that I'm pointing to. Much thanks!
I am not sure about your use case but if you have the option to make User an abstract base class, then you can use the Table per Concrete Type approach.
You would need to make User an abstract class and call MapInheritedProperties() when creating the model mappings for ExternalUser:
public abstract class User
{
public int UserId { get; set; }
public string Username { get; set; }
}
[Table("ExternalUser")]
public class ExternalUser : User
{
public int SomeExternalId { get; set; }
public string SomeExternalProp { get; set; }
}
Note that I am using [Table] attribute to map the entity name to table name (you can also do this in OnModelCreating method but I find it cleaner to use the [Table] attribute):
And this is OnModelCreating method:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
Database.SetInitializer<ApplicationDbContext>(new CreateDatabaseIfNotExists<ApplicationDbContext>());
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// I believe the inherited properties are mapped by default
modelBuilder.Entity<ExternalUser>().Map(m =>
{
m.MapInheritedProperties();
});
}
Note that I am removing PluralizingTableNameConvention as I don't want plural table names.