This question already has an answer here:
EF Core 2.1.0 set default string length and column type
(1 answer)
Closed last month.
In EntityFramework 6 I could do something like the following to change the defaults for a particular data type:
protected override void OnModelCreating(
DbModelBuilder mb
) {
mb
.Properties<DateTime>()
.Configure(config =>
config
.HasColumnType("datetime2")
.HasPrecision(7)
);
This saves having to specify these details for every single DateTime property in the DB.
Is there an equivalent way of changing property defaults in EF Core? There is no such Properties member on the EF Core ModelBuilder type.
I have created an extension method for this.
public static class ModelBuilderExtensions
{
public static void Properties<TPropertyType>(
this ModelBuilder modelBuilder,
Action<IMutableProperty> configureAction
)
{
modelBuilder
.Model
.GetEntityTypes()
.SelectMany(entityType => entityType.GetProperties())
.Where(entityProperty => typeof(TPropertyType) == entityProperty.ClrType)
.ToList()
.ForEach(configureAction);
}
}
Then I can do something like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Properties<DateTimeOffset>(
property =>
{
if (property.Name == nameof(Entity.CreatedAtUtc))
{
property.SetDefaultValueSql("CURRENT_TIMESTAMP AT TIME ZONE 'UTC'");
}
}
);
}
Related
This question already has an answer here:
Entity Framework Core 2.1 - owned types and nested value objects
(1 answer)
Closed 2 years ago.
I want to use a class within my EF entity and save the value of a property to the DB.
I have an example class like this:
public class MyClass
{
public string MyProperty { get; set; }
}
And an Entity Framework Entity like this:
public class MyEntity
{
public MyClass Item { get; set; }
...
}
And my context is like this:
public class MyDbContext : DbContext
{
public MyDbContext()
: base(connectionStringName)
{
}
public virtual DbSet<MyEntity> MyEntity { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>()
.Property(x => x.Item.MyProperty)
.HasColumnName("NewColumnName");
}
}
}
All the above is valid in Entity Framework 6 but the same code doesn't work in EF core.
I receive the following error when I try to create a migration:
The expression 'x => x.Item.MyProperty' is not a valid property
expression. The expression should represent a simple property access:
't => t.MyProperty'. (Parameter 'propertyAccessExpression')
So, is what I'm trying to do possible in Entity Framework Core or am I stuck using EF6?
If you are trying to change the column MyProperty from MyClass, that means that MyClass is a table. why would you access that property from a related table MyEntity
Try the following code instead:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<**MyClass**>()
.Property(x => x.**MyProperty**)
.HasColumnName("NewColumnName");
}
In my DbContext class I have the following:
void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<string>().Configure(c => { c.HasMaxLength(250); });
}
but also inside the EntityTypeConfiguration class for a specific model I have:
{
Property(p => p.Name).HasMaxLength(500);
}
The problem is that setting the property to 500 inside the Configuration class is not taking effect and EF is still validating the max to be 250.
How can I have a general setting of 250 but override by fluent api as needed inside each class?
#Lutti Coelho: My mistake
Add code below in OnModelCreating method
modelBuilder.Entity<Student>()
.Property(p => p.StudentName)
.MaxLength(500);
If you want to give a string field 500 characters.
You can do this by using the "MaxLength" attribute.
I am creating a sample table like the following.
My Context Configuration.
void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Properties<string>().Configure(c => { c.HasMaxLength(250); });
}
I am creating a table named FooEntity.
using System;
using System.ComponentModel.DataAnnotations;
namespace Foo.Model
{
public class FooEntity
{
[MaxLength(500)]
public string FooName { get; set; }
}
}
After adding this attribute, the migration output is as follows.
Even if a 250 character limit is given, it will be passed as 500 characters in SQL.
CreateTable(
"dbo.FooEntity",
c => new
{
FooName = c.String(maxLength: 500)
});
I was wondering if there is any way to set a value to an entity onsave?
Because I'm working on a multi tenant web application and I would like to set the the current tenant ID (through simple DI service).
I tried using HasDefaultValue() in Fluent API, however this will try to convert to a SQL function. So this doesn't work for me.
builder.Entity<Order>( )
.HasQueryFilter(p => p.TenantId == _tenantProvider.GetTenantId())
.Property(p => p.TenantId)
.HasDefaultValue(_tenantProvider.GetTenantId());
Any suggestions are greatly appreciated.
You could override the DbContext.SaveChanges() method and iterate the ChangeTracker entries:
public override int SaveChanges()
{
foreach (var entityEntry in ChangeTracker.Entries()) // Iterate all made changes
{
if (entityEntry.Entity is Order order)
{
if (entityEntry.State == EntityState.Added) // If you want to update TenantId when Order is added
{
order.TenantId = _tenantProvider.GetTenantId();
}
else if (entityEntry.State == EntityState.Modified) // If you want to update TenantId when Order is modified
{
order.TenantId = _tenantProvider.GetTenantId();
}
}
}
return base.SaveChanges();
}
Of course, this needs the tenant provider to be injected into your context.
EF Core value generation on add with custom ValueGenerator
Generates values for properties when an entity is added to a context.
could be utilized to assign TenantId to the new entities. Inside the Next method you could obtain the TenantId from the context (or some service).
Taking your sample, the value generator could be a nested class inside your DbContext like this:
class TenantIdValueGenerator : ValueGenerator<int>
{
public override bool GeneratesTemporaryValues => false;
public override int Next(EntityEntry entry) => GetTenantId(entry.Context);
int GetTenantId(DbContext context) => ((YourDbContext)context)._tenantProvider.GetTenantId();
}
The all you need is to assign the generator to TenantId property using some of the HasValueGenerator fluent API.
The only problem is that by design the value generators are called only if the property does not have explicitly set value (for int property - if the value is 0).
So the better approach it to abstract (and fully control) the TenantId property by removing it from entity models and replacing it with shadow property.
Hence my suggestion is, remove the TenantId from entity classes and call the following method inside your OnModelCreating for each entity that needs TenantId column:
void ConfigureTenant<TEntity>(ModelBuilder modelBuilder) where TEntity : class
{
modelBuilder.Entity<TEntity>(builder =>
{
builder.Property<int>("TenantId")
.HasValueGenerator<TenantIdValueGenerator>();
builder.HasQueryFilter(e => EF.Property<int>(e, "TenantId") == _tenantProvider.GetTenantId());
});
}
If you are using EF Core 5+, then you have the option of using the SavingChanges event. This will allow you to set your custom logic for both SaveChanges and SaveChangesAsync without having to override both methods.
Example:
public MyDbContext(DbContextOptions<MyDbContext> dbContextOptions, ITenantProvider tenantProvider) : base(dbContextOptions)
{
SavingChanges += (sender, args) =>
{
foreach (var orderEntity in ChangeTracker.Entries<Order>())
{
if (orderEntity.State == EntityState.Added)
{
orderEntity.Entity.TenantId = tenantProvider.GetTenantId();
}
}
};
}
I have a nullable varchar(max) column in SQL Server that I'm mapping to a Guid? in EF code-first. However, this property is actually in a base class that many other entities derive from.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Model1>().Property(e => e.Property1).HasConversion(p => p.ToString(), p => (Guid?)Guid.Parse(p));
}
The above line is repeated many times for each table. Is there a way to tell EF that this is a base class property so the mapping can be declared only once?
Sure it is possible. With the lack of custom conventions, it is achieved with the "typical" modelBuilder.Model.GetEntityTypes() loop. Something like this (just change the base class and the property names):
var entityTypes = modelBuilder.Model.GetEntityTypes()
.Where(t => t.ClrType.IsSubclassOf(typeof(BaseClass)));
var valueConverter = new ValueConverter<Guid, string>(
v => v.ToString(), v => (Guid?)Guid.Parse(v));
foreach (var entityType in entityTypes)
entityType.FindProperty(nameof(BaseClass.Property1)).SetValueConverter(valueConverter);
You may also consider using the EF Core provided out of the box Guid to String converter:
var valueConverter = new GuidToStringConverter();
Better to make next calculation property:
[Column("Property1")]
public string Property1Raw { get; set; }
[IgnoreDataMember]
public Guid? Property1
{
get => Guid.TryParse(Property1AsString, out Guid result) ? result : (Guid?)null;
set => Property1Raw = value?.ToString();
}
Another way to do is it to have a matching base class IEntityTypeConfiguration:
internal class EntityConfiguration<T> : IEntityTypeConfiguration<T> where T : Entity
{
public virtual void Configure(EntityTypeBuilder<T> builder)
{
builder.Property(e => e.Property1).HasConversion(p => p.ToString(), p => (Guid?)Guid.Parse(p));
// ... Other base-specific config here
}
}
(Assuming here your base class is called Entity - change as needed).
This works better when you use the pattern of factoring out your entity configurations, so yours might be like this:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new Model1EntityConfiguration());
modelBuilder.ApplyConfiguration(new Model2EntityConfiguration());
// ...
}
...
internal sealed class Model1EntityConfiguration : EntityConfiguration<Model1>
{
public override void Configure(EntityTypeBuilder<Model1> builder)
{
base.Configure(builder); // <-- here's the key bit
// ...; e.g.
builder.Property(c => c.Name).HasMaxLength(80).IsRequired();
}
}
I'm using EF5 Code First, I have entities named XXXEntity which is based on Entity class, in Entity class there is Id property.
Here is the problem, normaly EF will create table for each entity named XXXEntities and having Id field. What I want is the table should named XXX (without Entities) and the Id should be XXXId. How to do that at once by using convention. I know I can override the table name, and Id name one by one. But it is a bit boring and not reusable, is there any better way to do that using convention or something on EF 5?
UPDATE
i read about custom code first conventions but not sure is this an out dated page or a non implemented feature. because i couldn't found the Properties<T>() method on EF 5
No, you can't do that with EF 5, your link is a future feature for EF 6 see here
but you can do that with reflection easily, for reusability you can make it as an extension method of DbModelBuilder, a bit slow but it solve your case. here what you can do:
public static class MyCustomNamingConvention
{
public static void ToMyDatabaseNamingConvention(
this DbModelBuilder mb, IEnumerable<Type> entities)
{
foreach (var entity in entities)
{
var mi = typeof(MyCustomNamingConvention)
.GetMethod("MyNamingConvention")
.MakeGenericMethod(entity);
mi.Invoke(null, new object[] { mb });
}
}
public static void MyNamingConvention<T>
(DbModelBuilder mb) where T : Entity
{
var tableName = typeof(T).Name.Replace("Entity", "");
mb.Entity<T>().HasKey(x => x.Id);
mb.Entity<T>().Property(x => x.Id).HasColumnName(tableName + "Id");
mb.Entity<T>().ToTable(tableName);
}
}
Simply use it on your DBContext on OnModelCreating method
protected override void OnModelCreating(DbModelBuilder mb)
{
base.OnModelCreating(mb);
mb.ToMyDatabaseNamingConvention(GetEntities());
}
Note:
Remember to add appropriate namespace of the
MyCustomNamingConvention.
GetEntities() is a method to iterate your entity (remember to skip
the Entity base class)
Use this to remove table pluralizing:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
I cant seem to find a convention that you can disabled for the column Id name. Have a look at http://msdn.microsoft.com/en-us/library/gg696316(v=vs.103).aspx