No suitable constructor found for entity type 'Uri'. - asp.net

I have this model:
public class Book
{
public int Id { get; set; }
public string Name { get; set; }
public Uri Link { get; set; }
}
I added it to fluent-api using the following code:
builder.Entity<Book>(entity => {
entity.HasKey(b => b.Id);
});
when I run this:
add-migration InitialMigration -context MyAppContext
then I get:
No suitable constructor found for entity type 'Uri'. The following parameters could not be bound to properties of the entity: 'uriString', 'uriString', 'dontEscape', 'baseUri', 'relativeUri', 'dontEscape', 'uriString', 'uriKind', 'baseUri', 'relativeUri', 'serializationInfo', 'streamingContext', 'baseUri', 'relativeUri', 'flags', 'uriParser', 'uri'.

You can't persist a Uri directly to the database, as there's no associated SQL type for that. You need to use a string instead. If you're using a view model (as you should be), your view model can have the Uri, and then you simply need to get the string representation during your mapping to your actual entity type.
You can also simply utilize EF Core's value conversion (available in 2.1+). See the docs for more detail. Essentially, in your fluent config:
.HasConversion(
v => v.ToString(),
v => new Uri(v));

A value conversion can be added to your DbContext to convert the type used when reading from or writing to the database. This is done by overriding the OnModelCreating method.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Book>()
.Property(e => e.Link)
.HasConversion(v => v.ToString(), v => new Uri(v));
}

Related

EF Core: Filter query by complex property (which uses custom value converter)

guys.
I use EF Core 3.1 now as my database provider. So let's imagine a simple model of user with different working roles (for example, "worker", "builder", "supported", etc.)
public enum Roles
{
Worker = 1,
Builder = 2,
Supporter = 3
}
public class User
{
public Guid Id { get; set; }
public ICollection<Roles> EmploymentRoles { get; set; }
...
}
with DbContext of
...
DbSet<User> Users { get; set; }
...
modelBuilder.Entity<User>().Property(f => f.EmploymentRoles).HasConversion(new JsonEnumCollectionToStringConverter<Roles>());
public class JsonEnumCollectionToStringConverter<T> : ValueConverter<ICollection<T>, string> where T : Enum
{
public JsonEnumCollectionToStringConverter() :base(entity => JsonConvert.SerializeObject(entity), value => JsonConvert.DeserializeObject<ICollection<T>>(value))
{
}
}
So, It's not possible to write filter query directly like this:
var users = dbContext.Users.Where(e => e.EmploymentRoles.Contains(Roles.Worker)).ToList();
because EF Core can't translate Custom ValueConverter to SQL and execute this on the server-side. There are no warnings still now in 3.1.x, but this code will catch an error at the 5.x .NET.
So, what's the good way to write this filter query correct? I don't want to grab whole Users and filter it on the client-side, it can be worse in performance.
Thank you.

Configure Owned property in EF Core

EDIT: The original problem statement included only one reference to the Owned entity. But it turns out that the problem I encountered happens with multiple references to the Owned entity AND some funky fluent configuration code. I've rewritten this problem statement to explain the details of the problem I ran into.
I started with one EF Core entity, which references an Owned entity:
public class InvoiceItem
{
public SubscriptionPlanDetails SubscriptionPlan { get; set; }
}
[Owned]
public class SubscriptionPlanDetails
{
public string PlanName { get; set; } // Plan name
public decimal Price { get; set; } // Price (USD)
}
When I enter the command "add-migration" in the VS Package Manager Console, scaffolder complains:
No type was specified for the decimal column 'Price' on entity type 'SubscriptionPlanDetails'.
So I added some fluent API configuration:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<SubscriptionPlanDetails>()
.Property(p => p.Price)
.HasColumnType("money");
}
Problem solved... or so I thought. Then I added another entity with a reference to the same Owned entity:
public class Account
{
public int Id { get; set; }
public SubscriptionPlanDetails SubscriptionPlan { get; set; }
// Navigation properties
public virtual ICollection<InvoiceItem> InvoiceItems { get; set; }
}
So, each account has one subscription plan (Account.SubscriptionPlan), and each account has multiple InvoiceItems, each of which contains all of the details of the subscription plan that was in effect at the time the invoice item was created (Account.InvoiceItems.SubscriptionPlan).
Now, when I try to add a migration, it complains
No type was specified for the decimal column 'Price' on entity type 'InvoiceItem.SubscriptionPlan#SubscriptionPlanDetails'.
So I added fluent API code to configure the Owned property. But I neglected to remove the previous code. So, my configuration code looked like this:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// This should have been removed!
builder.Entity<SubscriptionPlanDetails>()
.Property(p => p.Price)
.HasColumnType("money");
builder.Entity<Account>().OwnsOne(m => m.SubscriptionPlan)
.Property(p => p.Price)
.HasColumnType("money");
builder.Entity<InvoiceItem>().OwnsOne(m => m.SubscriptionPlan)
.Property(p => p.Price)
.HasColumnType("money");
}
Attempting to add a migration gets me a NullReferenceException.
If I comment out the offending call to builder.Entity<SubscriptionPlanDetails>() then it works as expected!
I think the best way to override this decimal warning is to annotate the price with the following:
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
You can also use the type "money" instead of "decimal(18,2)"

How to specify default property values for owned entity types in Entity Framework Core 2.0?

I have a simple POCO type, say something like
public class OwnedEntity {
public string stringProperty { get; set; }
public decimal decimalProperty { get; set; }
public bool boolProperty { get; set; }
public int intProperty { get; set; }
}
and an actual entity with an OwnedEntity reference
public class SomeEntity {
public string Id { get; set; }
public OwnedEntity OwnedEntity { get; set; }
}
I set up the relationship like described in the documentation using EF Core's Fluent API:
protected override void OnModelCreating (ModelBuilder builder) {
base.OnModelCreating (builder);
builder.Entity<SomeEntity> ().OwnsOne (e => e.OwnedEntity);
}
I can't find anything on how to define default-values for all the properties of OwnedEntity. I tried to initialize the properties like this:
public class OwnedEntity {
public string stringProperty { get; set; } = "initial"
public decimal decimalProperty { get; set; } = -1M;
public bool boolProperty { get; set; } = false;
public int intProperty { get; set; } = -1;
}
but with no effect. Same goes with the [DefaultValueAttribute] (but that was to expect since it's explicitly mentioned).
There's a bit of information on how to handle initial values for regular entities:
modelBuilder.Entity<SomeOtherEntity>()
.Property(e => e.SomeIntProperty)
.HasDefaultValue(3);
But since I'm facing an Owned Entity Type, I can't access the type via Entity<T>.
Is there a way of doing what I'm looking for?
Some things worth mentioning:
I have a solid amount of specific entities where most of them are using the OwnsOne relation
Declaring all OwnedEntity-properties in a base class is not an option since not all the entities have those properties
I`m using EF Core 2.0.3 and ASP.NET Core MVC 2.0.4
Edit:
Originally, I wanted to have newly created SomeEntity instances to come with preset properties for all of the 'embedded' SomeEntity.OwnedEntity properties.
But looking at how my associated controller works, it all makes sense... I have the following methods for the 'Create' operation:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create));
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Create (SomeEntity model) {
context.Add (model);
await context.SaveChangesAsync ();
// redirect etc.
}
Which means that no object is created for the [HttGet] overload of Create and all the HTML inputs linked to properties (via asp-for) are initially empty. Okay. So I guess the proper way of doing this is to manually create a new instance of SomeEntity and pass it to the Create view like this:
[HttpGet]
public IActionResult Create () {
return View (nameof (Create), new SomeEntity());
}
Is this the right approach then or are there some more things to keep in mind?
Assuming you understand what EF Core Default Values are for, and just looking for equivalent of Entity<T>().Property(...) equivalent.
The owned entities are always configured for each owner type by using the ReferenceOwnershipBuilder<TEntity,TRelatedEntity> class methods. To access this class you either use the result of OwnsOne method, or use the OwnsOne overload taking second argument of type Action<ReferenceOwnershipBuilder<TEntity,TRelatedEntity>>.
For instance, using the second approach:
builder.Entity<SomeEntity>().OwnsOne(e => e.OwnedEntity, ob =>
{
ob.Property(e => e.stringProperty)
.HasDefaultValue("initial");
ob.Property(e => e.decimalProperty)
.HasDefaultValue(-1M);
// etc.
});

Entity Relationships Not Defined

In my ASP.NET Core Web API application, I am having trouble relating entities in Entity Framework Core. When I send a GET request to the Controller through Postman all of the Model's records are returned, but the navigation properties for their related entities come back null. Unfortunately, there are no errors or exceptions thrown either. The (simplified) code is as follows:
//DBContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Location>();
modelBuilder.Entity<Person>(entity =>
{
entity.HasOne(p => p.Location)
.WithMany(l => l.People)
.HasForeignKey(p => p.Location_ID);
});
}
//Person Model
public partial class Person
{
[Key]
public int People_ID { get; set; }
public int? Location_ID { get; set; }
public Location Location { get; set; }
}
//Location Model
public partial class Location
{
public Location()
{
People = new HashSet<Person>();
}
[Key]
public int Location_ID { get; set; }
public ICollection<Person> People { get; set; }
}
As far as I understand it, because I dictated the two Models are related using the Fluent API in the OnModelCreating method, the properties should be 'Eagerly Loaded'. Why is this not happening?
As far as I understand it, because I dictated the two Models are related using the Fluent API in the OnModelCreating method, the properties should be 'Eagerly Loaded'
You understand it wrong. The Eager Loading behavior is not implicit (like lazy loading), you have to explicitly request it using Include / ThenInclude methods.
For instance, the following will return the Person entities with Location property populated:
return db.Set<Person>().Include(p => p.Location);

AutoMapper and reflection

My shared hosting company doesn't allow Reflection.
How can I use AutoMapper?
Do I have to specify for each property a .ForMember?
Mapper.CreateMap<Person, PersonData>()
.ForMember(dest => dest.Name, o => o.MapFrom(src => src.Name))
.ForMember(dest => dest.Address, o => o.MapFrom(src => src.Address));
thanks,
Filip
Automapper uses reflection.emit, are you sure you can use Automapper?
[Edit]
Dont know of any that uses without reflection, even the one I had created XmlDataMapper on CodePlex uses reflection. It would difficult to design one without reflection or reflection.emit
The simplest and basic way to do this would be this, you can use any of the two or both techniques.
public class ConversionHelper
{
public static ClassB Convert(ClassA item)
{
return new ClassB() { Id = item.Id, Name = item.Name };
}
public static List<ClassB> Convert(List<ClassA> list)
{
return list.Select(o => new ClassB() { Id = o.Id, Name = o.Name }).ToList();
}
}
public class ClassA
{
public int Id { get; set; }
public string Name { get; set; }
}
public class ClassB
{
public int Id { get; set; }
public string Name { get; set; }
}
From the sample you have given where you are anyways trying to map property one by one, this is on the same lines, but with lesser code.
You cannot use Automapper or any other mapping architecture that I know of without reflection. This is logically obvious. How could you map two unknown entities to one another without using any of their reflected properties? Your only option in this case is to create a custom package to convert one object into another.
Not at all. AutoMapper did a great job on intelligent mapping. If the property name of your source and destination object is the same, AutoMapper will map this proprties automatically for you.

Resources