Entity Framework Core - Has Conversion - Support Null Values - entity-framework

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.

Related

Entity Framework Core 5.0.9 => save strings in uppercase

How can I save a string in upper case using entity framework.
I'm not looking to override saveChanges method.
What I want is a way to (if I want) apply a transformation to a string saved in a DB column
I've tried this, but it does not work.
builder.Property(self => self.Series)
.HasMaxLength(2)
.HasConversion(ToUpperCaseConvertor())
.IsRequired();
public static ValueConverter<string, string> ToUpperCaseConvertor()
{
return new ValueConverter<string, string>(
prop => prop,
prop => prop.ToUpper());
}

How to convert a method of a ValueObject to SQL with Entity Framework Core 3.1

I have this value object
public class ProductReference : ValueObject
{
protected ProductReference(){}
public ProductReference(string value){}
public string Value{get; protected set;}
}
I use it in my entity as :
public class Product : Entity<long>
{
protected Product(){}
public ProductReference Reference{get; protected set;}
}
In the OnModelCreating of my DbContext I defined :
modelBuilder.Entity<Product>(entity => {
entity.Property(a => a.Reference)
.HasColumnName("Reference")
.HasConversion(
a => a.Value,
s => new ProductReference (s);
});
When I do :
await dbcontext.Products.Where(p=>p.Reference.Value.Contains("some text")).toArrayAsync();
I get an exception
Expression cannot be converted to a valid SQL statement
I know for sure there is a way to create a custom expression converter, but I cannot find a good, simple and EF Core 3.1 compatible example to deal with my issue and that explain me clearly the concepts I miss.
I found this very interesting project
https://github.com/StevenRasmussen/EFCore.SqlServer.NodaTime
but it is too advanced for me to reproduce it for only my use case.
[EDIT] the ValueObject ans Entity are from
CSharpFunctionalExtensions nuget package, I dont think they are really relevant in my question.
I am not completely sure if i understand correctly what you want to accomplish, but you could try to configure your ProductReference as an Owned Entity Type.
Here you would transform the following code from:
modelBuilder.Entity<Product>(entity => {
entity.Property(a => a.Reference)
.HasColumnName("Reference")
.HasConversion(
a => a.Value,
s => new ProductReference (s);
});
to
modelBuilder.Entity<Product>(entity => {
entity.OwnsOne(a => a.Reference, referenceBuilder => {
referenceBuilder.Property(p => p.Value).HasColumnName("Reference");
});
});
With that your select statement should work.
It could be that you have to play around with the properties of your class ProductReference, or use some modifiers of the fluent API.
So first for some context on what is happening here behind the scenes and why its not gonna work even for build in simple converters like BoolToZeroOneConverter.
The problem here is that you are calling when converting the new ProductReference(s). This is method where you can do whatever you want in it. For example if use it in a Select statement it will again fail. For example:
await dbcontext.Products
.Select(x=>new ProductReference(x.Value))
.toArrayAsync();
The reason is obvious, it won't be able to translate. But why it cant transform it to a query?
Because you are passing a constructor. Inside this constructor you could be doing API calls or using Reflections to set the variables to your object, pretty much anything. That of course is not able to be translated in an SQL query.
Converters are generally used for in memory but they can be used for databse operations as well. This would mean that you will need something like this:
await dbcontext.Products
.Select(x=>new ProductReference() // empty constructor that does nothing
{
Property1 = x.Property1 // I don't know how the constructor maps them
})
.toArrayAsync();
Using this type of expression allow you to actually transalte the expression to an SQL statement and not making the conversion on the SQL DB and not in memory.
Now in your specific case using:
.HasConversion(
a => a.Value,
s => new ProductReference (){};
});
Should fix your issues but I fail to understand why would you want to initialize or convert a ProductReference to a ProductReference.

How to use data from entity Raw SQL query in entity framework

I will state up front I am still very new to asp.net core and entity framework but I am familiar with ADO and recordset which it seems entity framework is built from. I am struggling to use entity framework because I am able to run the query but I am not sure how to use the results and most help docs I see online only discuss the procedures and methods and not how to use the results. I am building login functionality on my site and have developed the following code to query DB in my UserAccount table. FOr this login I really only want the Username, Password, and the ID however I have multiple fields in this table that I dont need for this aspect. I come from using ADO and recordsets so really I just prefer to use raw sql and determine what i want to do with those results and not bind them to some objects which it seems entity framework does for the most part. I do liek the ease of use of the entity framework and using parameters though so I prefer to just go this route (not sure if this is bad practice or not). Everything works great except when I add ToList it starts to say error "InvalidOperationException: The required column 'AccessLevel' was not present in the results of a 'FromSql' operation." I am not even tryign to use that field in the query so not sure why that is even coming up and I am using a rawsql query method so why is it trying to see what I have in the model for that table? Lastly, this shoudl return a single record in which case I want to pull the password value from that record field similar to how i use to do in ADO as you will see in my passwordhash verification but I cannot figure out how to even pull that from the query result. Thanks for any help on this as I am getting a headache trying to learn this stuff!!
var UserResults = _context.UserAccounts.FromSqlInterpolated($"SELECT USERACCOUNT.USERNAME, USERACCOUNT.PASSWORD, USERACCOUNT.ID,USERACCOUNT.ACCESS_LEVEL FROM DBO.USERACCOUNT WHERE USERACCOUNT.USERNAME={UserName}");
if (UserResults.Count() == 1) //if we have more than 1 result we have security issue so do not allow login
{
var passwordHasher = new PasswordHasher<string>();
var hashedPassword = passwordHasher.HashPassword(null, Password);
var testResults = UserResults.ToList();
if (passwordHasher.VerifyHashedPassword(null, hashedPassword, THIS SHOULD BE VALUE FROM DB RIGHT HERE!!!) == PasswordVerificationResult.Success)
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Name, UserName)
};
My DBContext is as follows:
public partial class LoginDBContext : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set; }
public LoginDBContext(DbContextOptions<LoginDBContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<UserAccount>(entity =>
{
//entity.ToTable("UserAccount");
entity.HasNoKey();
//entity.Property(e => e.Id)
// .HasMaxLength(50)
// .IsUnicode(false);
//entity.Property(e => e.Username)
// .HasMaxLength(50)
// .IsUnicode(false);
//entity.Property(e => e.Password)
// .HasMaxLength(50)
// .IsUnicode(false);
});
}
}
If you don't want to load all columns of the user table you can return anonymous object with properties you need or create a class for the columns you need and return it.
var users = _context.UserAccounts
.Where(a => a.UserName == UserName)
.Select(a => new { a.Id, a.UserName, a.Password })
.ToArray();
if (users.Length == 1)
{
var user = users.First();
if (passwordHasher.VerifyHashedPassword(null, hashedPassword, user.Password) == PasswordVerificationResult.Success)
{
// ... your magic
}
}

EF Core 2.1 Value Conversion Update Issue

I ran into an issue using value conversion, which is new in EF Core 2.1, for List to string conversion.
Since I did not need to be able to filter the List of Enum values in the Database I decided to map my List of Enum Values to a comma delimited string with the int values.
The Conversion should look like this:
From: List<EnumType>{EnumType.Value1, EnumType.Value2}
To: 1,2
Everything seemed to work fine but EF seems not to notice that the list of Enum values was changed and does not issue an update in the database. Is there a limitation that does not allow value conversion for lists?
The Value Conversion Code looks like this:
private const char ENUM_LIST_DELIMITER = ',';
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Entity>().Property(x => x.Types)
.HasConversion(x => ConvertToString(x), x => ConvertToEnumList<EnumType>(x));
}
private static List<TEnum> ConvertToEnumList<TEnum>(string value) where TEnum : struct, IConvertible
{
return value?.Split(ENUM_LIST_DELIMITER)
.Select(Enum.Parse<TEnum>)
.ToList();
}
private static string ConvertToString<TEnum>(IEnumerable<TEnum> value) where TEnum : struct, IConvertible
{
return string.Join(ENUM_LIST_DELIMITER, value.Select(x => Convert.ToInt32(x)));
}
}
Value conversion on List<T> worked for me when using DbContext.Set<T>.Update(entity) instead of getting the entity from the context, changing some properties and then calling .SaveChangesAsync(). This is probably due to what #Ivan Stoev said regarding equality comparison. Update() marks all properties in an entity as changed. Perhaps not desired in all situations, but can be an quick fix.

Entity Framework WebAPI Model UpSerts

I'm having problems with duplicate data during migration with Code First.
A new foreign key record is duplicated each time the migration creates the master record.
The schemas in the database are being created correctly. Namely the Primary Keys and Foreign Key values (the latter being automatically generated)
Can someone please advise thanks about how I detach the foreign key record during migration to prevent it recreating the record or any configuration I need to implement? I've tried updating the state of the foreign key obects before inserting master data. to both modified and detached.
For example I see multi records for the same priority where there should only be 3.
I'm using Entity Framework 6.0.
public class VeloPointDbConfiguration : DbMigrationsConfiguration<VeloPointDbContext>
{
public VeloPointDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(VeloPointDbContext context)
{
context.TaskPriorities.AddOrUpdate(EventTaskPriority.Migrations.All());
context.TaskStatuses.AddOrUpdate(TaskStatus.Migrations.All());
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.Priority).State == EntityState.Modified);
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.TaskStatus).State == EntityState.Modified);
context.Tasks.AddOrUpdate(EventOrganiserTask.Migrations.All());
}
}
The following examples of the instances i'm using for the data.
I create the following methods for the foreign key objects
public static EventTaskType[] All()
{
return new[]
{
GetDeadline(),
GetEmail(),
GetTelephone(),
GetAppointment(),
GetSnailMail(),
};
}
internal static EventTaskType GetDeadline()
{
return new EventTaskType("09974722-D03E-4CA3-BF3A-0AF7F6CA1B67", 1, "Deadline")
{
Icon = ""
}
}
I call the following methods the create the master data.
public static EventOrganiserTask[] All()
{
return new EventOrganiserTask[]
{
GetBookHQ(1, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetFindSponsor(2, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetRegisterEvent(3, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetBookFirstAid(4, Event.Migrations.EventDate.AddMonths(-6))
};
}
NOTE: When creating the master record, I call the method in the foreign key classes each time - which is the crux of the problem where I need to instruct the migration to detach this item.
public static EventOrganiserTask GetRegisterEvent(int id, DateTime date)
{
return new EventOrganiserTask
{
id = id,
Title = "Register event",
Summary = "Register the road race with the region",
DueDate = date,
Priority = EventTaskPriority.Migrations.GetHighPriority(),
Person = Person.Migrations.GetRaceOrganiser(1),
TaskType = EventTaskType.Migrations.GetDefault(),
TaskStatus = TaskStatus.Migrations.GetDefault(),
};
}
NOTE: When I do make changes to the data from the application, the foreign keys are not being updated. This must be related and indicates my entities are not configured correctly.
LATEST:
I'm still tearing my hair out. I've investigated this further and read about the migrations being multi threaded (It was another thread on stackoverflow but I can't find it again). Indeed running the Seed method I supposed is what it says on the tin and is purley for seeding data, so the data is only being added (regardless of AddOrUpdate - what's that all about then) So I've looked at the behaviour regarding the records being created. First of all I called context.SaveChanges() after creating the look up tables. At this point it doesn't created any duplicates as the items are only referenced once. I then let the seed method run the master data - but argggh - I see duplicates (when the instances are called on the master data). But this did flag something up with regard to the order in which it creates the records.
My next step was to create two migrations, but without any success.
I'm hoping somebody picks up this thread soon. I'm tearing my hair out.
Ok so i've finally found my answer. It was clever enough to create the foreign key relationships from the model, but i needed to be explicitly set the foreign key id field. I chose the Fluent API to explicitly set the relationships and I set the value of the id field in the mapping of the object.
modelBuilder.Entity<Task>()
.HasRequired(x => x.Priority)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.Priority_id);
Here it is in the seed method
public class VeloPointDbConfiguration : DbMigrationsConfiguration<VeloPointDbContext>
{
public VeloPointDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(VeloPointDbContext context)
{
context.TaskPriorities.AddOrUpdate(EventTaskPriority.Migrations.All());
context.TaskStatuses.AddOrUpdate(TaskStatus.Migrations.All());
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.Priority).State == EntityState.Modified);
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.TaskStatus).State == EntityState.Modified);
context.Tasks.AddOrUpdate(EventOrganiserTask.Migrations.All());
// Foreign Key relationships
modelBuilder.Entity<EventOrganiserTask>()
.HasRequired(x => x.TaskStatus)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.TaskStatus_id);
modelBuilder.Entity<Task>()
.HasRequired(x => x.TaskType)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.TaskType_id);
modelBuilder.Entity<Task>()
.HasRequired(x => x.Priority)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.Priority_id);
}
}