Setting IsUnicode to false for all types in Entity Framework Core 6 - entity-framework-core

In normal Entity Framework you could use the code
modelBuilder.Properties<string>().Configure(c => c.IsUnicode(false));
to set all created column times to a non-unicode version. I am working in EF Core 6 and trying to do this but modelBuilder does not have a Properties attribute and I am not finding any solutions that parting to EFCore 6 when searching around Has doing this universially been droped and I must specify it for every column now or is there a hidden method some where I am not seeing?

You can set all string properties to be non-unicode by default via pre-convention model configuration:
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.DefaultTypeMapping<string>(
b => b.HasColumnType("varchar(max)").IsUnicode(false));
}
Alternative:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(e => e.GetProperties()
.Where(p => p.ClrType == typeof(string))))
{
property.SetIsUnicode(false);
}
}

Related

How to user entity framework Map() method in Entity Framework Core 6

How to user Map method in Entity Framework Core 6. After upgrading to Entity Framework Core 6 the Map() method no longer works. Is there something similar I can use to Map the columns to a table?
Example of my code below.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var relationship in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
modelBuilder.Entity<RoleGroup>()
.HasMany(c => c.Roles).WithMany(i => i.RoleGroups).Map(t => t.MapLeftKey("RoleGroupId")
.MapRightKey("RoleId")
.ToTable("RoleGroupRole"));
}
Most examples for EF Core have a RoleGroupRole entity defined, but they do support using a Dictionary<string, object> for a shadow entity placeholder for basic joining tables:
modelBuilder.Entity<RoleGroup>
.HasMany(u => u.Roles)
.WithMany(g => g.RoleGroups)
.UsingEntity<Dictionary<string, object>>(
right => right
.HasOne<Role>()
.WithMany(),
left => left
.HasOne<RoleGroup>()
.WithMany(),
join => join
.ToTable("RoleGroupRoles"));
The gotcha with this configuration is that the expressions for the two sides goes "Right" then "Left", it's easy to get them backwards. :)

Ef core abstract HasQueryFilter from DbContext

I'm trying to build some generic authorize stuff ontop of DbContext so that my devs do not need to care about authorization in the repos/domain.
Simple example like
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");
// Configure entity filters
modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}
Which is MS example works. _tenantId will be used to create an expression and for each instance of DbContext it will use correct value of _tenantId.
But I do not want all our authorizen configured from our DB context, I want to inject it. Something like
public class AgreementAuthorization : IEntityAuthorization
{
private readonly string _legalEntityNumber;
public AgreementAuthorization(IAuthScope scope)
{
_legalEntityNumber = scope.LegalEntityNumber;
}
public void Build(ModelBuilder builder)
{
builder
.Entity<Agreement>()
.HasQueryFilter(a => _legalEntityNumber == null || a.LegalEntity.Number == _legalEntityNumber);
}
}
public class MyDbContext : DbContext
{
private IEnumerable<IEntityAuthorization> _entityAuthorization;
MyDbContext(IEnumerable<IEntityAuthorization> entityAuthorization)
{
_entityAuthorization = entityAuthorization;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
builder.ApplyConfigurationsFromAssembly(typeof(MyDbContext).Assembly);
_entityAuthorization.ForEach(a => a.Build(builder));
}
}
This does not work, query willl test against null every time and pass. If I move the code directly to DbContext it will work. Meaning _entityAuthorization lies directly on DbContext.
From https://learn.microsoft.com/en-us/ef/core/querying/filters
Filters cannot contain references to navigation properties.
You are referencing navigation property LegalEntity.
And this could potentially affect you as well as you're passing in an IEnumerable<IEntityAuthorization>:
It is currently not possible to define multiple query filters on the
same entity - only the last one will be applied. However, you can
define a single filter with multiple conditions using the logical AND
operator (&& in C#).
Update:
My guess would then be that you need to reference a field/property of the MyDbContext directly and not within another object. Inject a class/interface that gives access to the values by which to filter then configure the filters at the end of the OnModelCreating method. You can interate over the entity configurations and their properties to apply the desired filter(s) based on the presence of the applicable property.

Entity Framework Core - Has Conversion - Support Null Values

I have an EF model with a notification emails property. The notification emails are saved in the database as string separated by ';'. I added a conversion to retrieve the data as a ICollection in the model. This is working well except one thing: when the string is null the collection is also null, and I want to convert it to an empty collection instead. is it possible?
//This is my code
entity.Property(e => e.NotificationEmails)
.HasConversion(
v => string.Join(",", v.Select(s => s.Trim())),
v => v.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries));
I tried to add String.IsNullOrEmpty(v) but EF ignores it.
Currently, it isn't possible :
https://learn.microsoft.com/en-us/ef/core/modeling/value-conversions#configuring-a-value-converter
A null value will never be passed to a value converter. This makes the implementation of conversions easier and allows them to be shared amongst nullable and non-nullable properties.
It isn't elegant, but you can use a backing field :
public class Notification
{
private List<string> _emails = new List<string>();
public List<string> Emails
{
get => _emails;
set => _emails = value ?? new List<string>();
}
}
public class NotificationContext : DbContext
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Notification>().Property(d => d.Emails).HasConversion(
v => string.Join(",", v.Select(s => s.Trim())),
v => v.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).ToList()
);
modelBuilder.Entity<Notification>()
.Property(b => b.Emails)
.HasField("_emails")
.UsePropertyAccessMode(PropertyAccessMode.Property);
}
}
Note : in where, a empty list will not be translated by null, but by a empty string.
Edit : This feature is available from EF Core 6, but bugged.
See this comment :
For anyone watching this issue: there are significant problems when executing queries that either convert nulls in the database to non-nulls in code or vice-versa. Therefore, we have marked this feature as internal for EF Core 6.0. You can still use it, but you will get a compiler warning. The warning can be disabled with a #pragma.

Loop/reflect through all properties in all EF Models to set Column Type

My client has a standard of storing SQL Server decimals with a decimal(13,4) specification. As a result, in a very large and still-growing schema, I have nearly a hundred statements like these:
builder.Entity<MyObject>()
.Property(x => x.MyField1)
.ForSqlServerHasColumnType("decimal(13,4)");
builder.Entity<MyObject>()
.Property(x => x.MyField2)
.ForSqlServerHasColumnType("decimal(13,4)");
builder.Entity<MyObject2>()
.Property(x => x.MyField1)
.ForSqlServerHasColumnType("decimal(13,4)");
If there is a feature where I can tell EF directly that all decimals should be decimal(13,4) by default, I would like to use that. If not, can I use reflection to loop through every object/property in the model so I can do this in a couple statements?
Something like:
foreach(var efObj in EntityFrameWorkObjects)
{
foreach (var objProperty in efObj)
{
if (objProperty is decimal || objProperty is decimal?)
{
builder.Entity<efObj>()
.Property(x => x.efObj)
.ForSqlServerHasColumnType("decimal(13,4)");
}
}
}
Reflection seems like a great way to go, because then I can implement some of our other conventions where, if an object has a Name and Description, the Name is required and limited to 256 chars.
Update:
I followed the link in Ivan's comment and adapted it to this, which works for me:
foreach (var p in builder.Model
.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p =>
p.ClrType == typeof(decimal) ||
p.ClrType == typeof(decimal?)))
{
p.SqlServer().ColumnType = "decimal(13,4)";
}
Soon after, he provided a full answer, which I changed slightly to work with both decimal and nullable decimal:
foreach (var pb in builder.Model
.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p =>
p.ClrType == typeof(decimal) ||
p.ClrType == typeof(decimal?))
.Select(p =>
builder.Entity(p.DeclaringEntityType.ClrType)
.Property(p.Name)))
{
pb.ForSqlServerHasColumnType("decimal(13,4)");
}
Both approaches work!
Update 2: I had to have my objects declared as DbSet<> in the context for the above to work. This didn't seem to be required when I was setting properties line by line.
In EF Core v1.1.0 you can use something like this:
foreach (var pb in modelBuilder.Model
.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?))
.Select(p => modelBuilder.Entity(p.DeclaringEntityType.ClrType).Property(p.Name)))
{
pb.ForSqlServerHasColumnType("decimal(13,4)");
}
Update (EF Core 2.x): Starting from EF Core 2.0, the model is built separately for each database provider, so HasAbcXyz methods are replaced with common HasXyz. The updated code (which also skips the explicitly configured properties) looks like this:
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
if (property.Relational().ColumnType == null)
property.Relational().ColumnType = "decimal(13,4)";
}
Update (EF Core 3.x): With EF Core 3.0 metadata API changes (Relational() extensions removed, properties replaced with Get / Set method pair), the code is as follows:
foreach (var property in modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetProperties())
.Where(p => p.ClrType == typeof(decimal) || p.ClrType == typeof(decimal?)))
{
if (property.GetColumnType() == null)
property.SetColumnType("decimal(13,4)");
}
Update (Entity Framework Core 6): EF Core 6 includes convention model configuration that can be used to achieve this to all types.
The advantage over looping through entities manually, is that this conventions already ignore certain types (like Ignore(), or properties that have Converters).
public class SomeDbContext : DbContext
{
protected override void ConfigureConventions(
ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<decimal>()
.HavePrecision(19, 4);
}
}
EF Core 6.0
It's now simple to configure defaults for every decimal property (or string, etc.):
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder
.Properties<decimal>()
.HavePrecision(19, 4);
}
More info: pre-convention model configuration
The approach mentioned above does not work when I'm working on EFCore 5.0.1 DB-First. The method below on MS document works:
[Column(TypeName = "decimal(18, 4)")]
public decimal Numeric { get; set; }
New feature will be introduced in EF Core 5.0
modelBuilder
.Entity<Blog>()
.Property(b => b.Numeric)
.HasPrecision(16, 4);
Reference : https://learn.microsoft.com/en-us/ef/core/what-is-new/ef-core-5.0/whatsnew#preview-4

EF7 Identity custom table name first seed

I have created a new clean asp.net 5 project (rc1-final), I just need to change default ef identity table name.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
protected override void OnModelCreating(ModelBuilder builder)
{
// On event model creating
base.OnModelCreating(builder);
// Define table name
builder.Entity<IdentityUser>().ToTable("BackEnd_AspNetUsers").Property(p => p.Id).HasColumnName("UserId");
builder.Entity<ApplicationUser>().ToTable("BackEnd_AspNetUsers").Property(p => p.Id).HasColumnName("UserId");
builder.Entity<IdentityUserRole<string>>().ToTable("BackEnd_AspNetUserRoles");
builder.Entity<IdentityUserLogin<string>>().ToTable("BackEnd_AspNetUserLogins");
builder.Entity<IdentityUserClaim<string>>().ToTable("BackEnd_AspNetUserClaims");
builder.Entity<IdentityRole>().ToTable("BackEnd_AspNetRoles");
}
}
I get following error
InvalidOperationException: Cannot use table 'BackEnd_AspNetUsers' in schema '' for entity 'ApplicationUser' since it is being used for another entity.
These lines here show you are trying to setup mappings for both the base identity classes and your application's inherited version of these classes;
builder.Entity<IdentityUser>().ToTable("BackEnd_AspNetUsers").Property(p => p.Id).HasColumnName("UserId");
builder.Entity<ApplicationUser>().ToTable("BackEnd_AspNetUsers").Property(p => p.Id).HasColumnName("UserId");
You don't need both, you should only have the inherited one specified - ApplicationUser.
In your case you should use ForSqlServerToTable("newtablename")
builder.Entity<IdentityUser>().ForSqlServerToTable("BackEnd_AspNetUsers").Property(p => p.Id).HasColumnName("UserId");
builder.Entity<ApplicationUser>().ForSqlServerToTable("BackEnd_AspNetUsers").Property(p => p.Id).HasColumnName("UserId");