Popcorn API: How to enable expansion on a custom translation? - rest

I'm using Popcorn to allow clients of my API to specify how deeply to unravel the properties of the requested object.
I'm having trouble mapping one of my data model entities to a projection. All the entities are entity framework data models so I'm using 'MapEntityFramework' where I can.
The data model looks like this:
public class DataModel {
public List<Person> Persons { get; set; }
}
public class Person {
public Guid Id { get; set; }
public List<Pet> Pets {get; set;}
public List<Pet> GetLivingPets() {
// Do some computation to get the pets that are alive
}
}
public class Pet {
public Guid Id { get; set; }
public string Name { get; set; }
public bool Alive { get; set; }
}
public PetProjection {
public Guid? Id { get; set; }
public string Name { get; set; }
}
public PersonProjection {
public Guid? Id { get; set; }
public List<PetProjection> LivingPets {get; set;}
}
The request I'd like my clients to be able to make requests that specify the nesting of the dynamically generated list.
http://localhost:5000/api/1/Persons?include=[Id,LivingPets[Name]]
Which would return a list like this:
[
{
"Id": "00000000-0000-0000-0000-000000000000",
"LivingPets": [
{
"Name": "Capt Meowmix"
}
]
}
]
This is the mapping that I am attempting:
popcornConfig
.Map<Person, PersonProjection>(config: (personConfig) =>
{
personConfig.Translate(fp => fp.LivingPets, f => f.GetLivingPets()?.ToList()); // Error: 'Dictionary<string, object>' does not contain a definition for 'GetLivingPets' and no extension method 'GetLivingPets' accepting a first argument of type 'Dictionary<string, object>' could be found (are you missing a using directive or an assembly reference?)
})
.MapEntityFramework<Pet, PetProjection, DataModel>(dbContextOptionsBuilder);
What do I need to do to write a mapping that would make use of another mapping I've already defined? Is this something that can be done with the library? Am I missing something?

There's no need for the Translate on LivingPets. Popcorn automatically looks for same named properties between the mapped entity and its projection. It'll look for matching properties, or failing that matching methods that require no parameters, so if you name your method LivingPets it'll automatically work. However, what you're doing with the Translate should also work.
Regarding the translate function, as long as it returns an object Popcorn has a mapping for, the client should be able to expand it. You were seeing that error because the compiler was picking the wrong version of the Translate function. If you switch
personConfig.Translate(fp => fp.LivingPets, f => f.GetLivingPets()?.ToList());
to
personConfig.Translate(fp => fp.LivingPets, (f,c) => f.GetLivingPets()));
The compilation error should disappear. This is a bug and is being tracked in this issue.

Related

Using OData with model inheritance cause missing property error

I got this error in my OData with asp.net core implementation during the runtime :The EDM instance of type '[XXX.Asset Nullable=True]' is missing the property 'externalId'.
The problem appear when I try to access the odata endpoint with the expand query: "/odata/v1/precinct?$expand=assets". It seems happening because I put the "ExternalId" property in my base class, its not happening if I put that property in the "Asset".
Below is my recent codes:
public abstract class Entity
{
public int Id { get; set; }
public string ExternalId { get; set; }
}
public class Precinct : Entity
{
public string Name { get; set; }
public string Description { get; set; }
public virtual IEnumerable<Asset> Assets { get; set; }
}
public class Asset : Entity
{
public string Name { get; set; }
public string Description { get; set; }
}
and here is my model configuration for ODATA
public class AssetModelConfiguration : IModelConfiguration
{
public void Apply(ODataModelBuilder builder, ApiVersion apiVersion)
{
var org = builder.EntitySet<Asset>("asset").EntityType;
org.HasKey(x => x.ExternalId);
org.Ignore(x => x.Id);
}
}
The strange thing is if I put that ExternalId in "Asset" class, it is working. Id property is the primary key while the "ExternalId" is marked as AlternateKey in the DBModel configuration.
am I missing something in my odata configuration? already tried many things but couldn't find a good answer. Any help would be appreciated!

Entity Framework core 2.0 HasColumnType throw run time exception

I have a custom enum type EmployementState (complex type in EF 6 term, I think)
in OnModelCreating, the following code throw an run time exception.
modelBuilder.Entity<Employee>().Property(e => e.EmployementState.Value).HasColumnType("int");
The exception show below:
Message=The expression 'e => e.EmployementState.Value' is not a valid property expression. The expression should represent a property access: 't => t.MyProperty'.
cannot figure out how to get the syntax right or is there are something else I was missing?
Thank you for your help.
Assuming you have the following model for your EmployementState object that will hold the different states for your employee:
public class EmployementState
{
public int Id { get; set; }
public string Name { get; set; }
}
You can then add a reference like:
public class Employee
{
public int Id { get; set; }
public string FirstName { get; set; }
// All your user properties here
public int EmployementStateId { get; set; }
public virtual EmployementState EmployementState { get; set; }
}
I recommend this because is the best approach when you are working with states describing your objects.
Of course being two separate models they are configurable so configurations like this one below are easy to implement.
modelBuilder.Entity<Employee>().Property(e => e.EmployementStateId)
.HasColumnName("employement_state_ID");

Entity Framework Navigation Woes

I have Business and BusinessProgram declared as:
public class Business : DbIdEntity
{
public string Name { get; set; }
public virtual Address PhysicalAddress { get; set; }
public virtual Address PostalAddress { get; set; }
public Guid OwnerKey { get; set; }
public virtual Account Owner { get; set; }
public virtual IEnumerable<BusinessProgram> BusinessPrograms { get; set; }
}
public class BusinessProgram : DbEntity<Guid>
{
public Business Business { get; set; }
public ProgramType ProgramType { get; set; }
public DateTime? EffectiveDate { get; set; }
public DateTime? ExpireDate { get; set; }
}
DbIdEntity and DbEntity are just base classes where the primary key (and an autonumbering Id field are declared.
When I query it using this query
foreach (Data.Business business in context.Businesses.Include(b => b.Owner)
.Include(b => b.PhysicalAddress)
.Include(b => b.Owner)
.Include(b => b.BusinessPrograms)
.OrderBy(b => b.Name))
I'm also using a convention that makes properties ending in "Key" the primary and foreign keys instead of the default "Id".
I get the error:
"A specified Include path is not valid. The EntityType
'Data.Business' does not declare a navigation
property with the name 'BusinessPrograms'."
What am I doing wrong?
UPDATE
I used IEnumerable instead of ICollection. Using the correct navigation property type fixed the issue.
I used IEnumerable for my navigation type instead of ICollection. Changing it to ICollection fixed the issue.
I've had rather limited success with using base classes in the way you describe. I'd try "flattening" your model first, and getting it working like that. Then you could try re-introducing the base classes; you might be able to get that working too.
Here it looks as though BusinessProgram should contain a FK property called BusinessProgram_BusinessId if you want to use the default convention. Alternatively, you could give it a different name and use an attribute to override the default convention:
[ForeignKey("Business")]
public int BusinessId { get; set;}

Value cannot be null. Parameter name: entitySet

I have a fairly standard setup with simply POCO classes
public class Project
{
public int ProjectId { get; set; }
public string Name { get; set; }
public int? ClientId { get; set; }
public virtual Client Clients { get; set; }
}
They use an interface
public interface IProjectRepository
{
IEnumerable<Project> Projects { get; }
}
and are constructed as a repository for ninject to bind to
public class EFProjectRepository : IProjectRepository
{
private EFDbContext context = new EFDbContext();
public IEnumerable<Project> Projects
{
get { return context.Projects; }
}
}
The actual context is a simply DbContext
public class EFDbContext : DbContext
{
public DbSet<Project> Projects { get; set; }
}
When I try and enable code first migrations I get the following error
I have done this exact process with other projects and there as never been an error. This is connecting to a local Sql Server Database. There does not seem to be a problem with the connection string. I have searched for this error online but the solutions seem to answer questions that do not directly relate to my setup.
I had the same issue and the cause was a POCO class that had a property of type Type.
Late to the game...but if it helps...
I had this same problem, everything was working fine, but this issue appeared, I added the following to one of my classes
public HttpPostedFileBase File { get; set; }
which seemed to break it.
I ensured I didn't map this to the database by using the following:
[NotMapped]
public HttpPostedFileBase File { get; set; }
You need to add the following using statement:
using System.ComponentModel.DataAnnotations.Schema;
Hope this helps
This problem can occur if one of the POCO classes was not declared in the DbContext.
I added them and the error went away
I had changed the name of the Task POCO class because of its association with a built in .NET name System.Threading.Tasks. However I had not changed this in the "TaskTimeLog" POCO where there was a relation. When going through the code the "Task" property in the "TaskTimeLog" POCO was not showing an error because it was now attached to that threading keyword and the reason I had changed the name in the first place.
I got this error:
Value cannot be null. Parameter name: entitySet
Turns out I was trying to join data from 2 different DbContexts.
var roles = await _identityDbContext.Roles
.AsNoTracking()
.Take(1000)
.Join(_configurationDbContext.Clients.ToList(),
a => a.ClientId,
b => b.Id,
(a,b) => new {Role = a, Client = b})
.OrderBy(x => x.Role.ClientId).ThenBy(x => x.Role.Name)
.Select(x => new RoleViewModel
{
Id = x.Role.Id,
Name = x.Role.Name,
ClientId = x.Role.ClientId,
ClientName = x.Client.ClientName
})
.ToListAsync();
The fix is to add ToList as shown. Then the join will happen in code instead of the database.
Only do this if you are OK with retrieving the whole table. (I know my "Clients" table will always be relatively small.)
For anyone not finding a resolution in the other answers, I got this error when I created a derived class from a class that had an instance in some model. The exception occurred on the first usage of my context in a request.
This is a stripped-down example that will reproduce the behaviour. Model is a DbSet in my context.
public class Model
{
public int Id { get; set; }
public Duration ExposureDuration { get; set; }
}
public class Duration
{
public int Value { get; set; }
public string Unit { get; set; }
}
//Adding this will cause the exception to occur.
public class DurationExtended : Duration
{ }
This happened during work in progress. When I changed the model property ExposureDuration to type DurationExtended, all was working again.
I had the same issue and it took quite a while to find out the solution.
In our case, we created a seperated project to handle the Entities and even if the default project in the Package Manager Console was the one handling the Entities, I need to set this project as the default project in order to make it work.
I hope this will help somebody else.
I got this error when I declared a variable of type Type - which is probably because is a complex type not supported by the DB.
When I changed it to string, the error went away
public class Sample
{
public int SampleID {get;set;}
public Type TypeInfo {get; set;} //This caused the error,
//because Type is not directly convertible
//in to a SQL datatype
}
I encountered this same issue and resolved like so:
Error in model class:
public class UserInformation
{
public string ID { get; set; }
public string AccountUserName { get; set; }
public HttpPostedFileBase ProfilePic { get; set; }
}
No error in model class
public class UserInformation
{
public string ID { get; set; }
public string AccountUserName { get; set; }
public string ProfilePicName { get; set; }
}
My issue was resolved once i updated the ProfilePic property type from HttpPostedFileBase to string. If you have a property that is not of type string, int, double or some other basic/standard type either replace such property or update to a type which SQL is more likely to accept.
Remove the line <Generator>EntityModelCodeGenerator</Generator> from your project file.
Check out this https://www.c-sharpcorner.com/UploadFile/5d065a/poco-classes-in-entity-framework/
I have some properties in "ExpenseModel", one of this was...
public virtual Type TypeId {get; set;}
which was causes the above same error because of "Type" propertyType,
so I changed "Type" => "ExpenseType" and it worked... :-)
public virtual ExpenseType TypeId {get; set;}
ExpenseModel.cs
public class ExpenseTypes
{
[Key]
public int Id { get; set; }
public string TypeName { get; set; }
public string Description { get; set; }
}
In my case I had to reference another model class called IanaTimeZone, but instead of
public virtual IanaTimeZone Timezone { get; set; }
out of rush I typed this:
public virtual TimeZone Timezone { get; set; }
and it compiled fine because VS thought it was System.TimeZone but EF6 was throwing the error. Stupid thing but took me a while to figure out, so maybe this will help someone.
To anyone else this might be helpful, I had a property TimeZone (the actual .NET TimeZone object) and this gave me the exact same error, not sure why but will dig deeper :)

Can Fluent NHibernate's AutoMapper handle Interface types?

I typed this simplified example without the benefit of an IDE so forgive any syntax errors. When I try to automap this I get a FluentConfigurationException when I attempt to compile the mappings -
"Association references unmapped class
IEmployee."
I imagine if I were to resolve this I'd get a similar error when it encounters the reference to IEmployer as well. I'm not opposed to creating a ClassMap manually but I prefer AutoMapper doing it instead.
public interface IEmployer
{
int Id{ get; set; }
IList<IEmployee> Employees { get; set; }
}
public class Employer: IEmployer
{
public int Id{ get; set; }
public IList<IEmployer> Employees { get; set; }
public Employer()
{
Employees = new List<IEmployee>();
}
}
public interface IEmployee
{
int Id { get; set; }
IEmployer Employer { get; set; }
}
public class Employee: IEmployee
{
public int Id { get; set;}
public IEmployer Employer { get; set;}
public Employee(IEmployer employer)
{
Employer = employer;
}
}
I've tried using .IncludeBase<IEmployee>() but to no avail. It acts like I never called IncludeBase at all.
Is the only solution to either not use interfaces in my domain entities or fall back on a manually defined ClassMap?
Either option creates a significant problem with the way my application is designed. I ignored persistence until I had finished implementing all the features, a mistake I won't be repeating again :-(
It's not a restriction imposed by Fluent or its AutoMapper, but by NHibernate itself.
I therefore don't think you'd get there with the manual class map. You'll have to lose the interfaces in the property and list definitions. You can keep the interfaces, but mapped properties and collections must use the concrete types of which NHibernate knows.
public class PersonMap : ClassMap<Person>
{
public PersonMap()
{
Id(x => x.Id);
Map<Address>(x => x.Address); // Person.Address is of type IAddress implemented by Address
}
}