I'm working my way through the book "Mastering Xamarin.Forms" by Ed Snider, which is overall an excellent book. However, I've run into a snag in creating an Azure function app as described in the book. I created the function app (MyTripLogFunctionApp) and a function within it called entry, and I see both of them on portal.azure.com. However, when I try to connect to it from a REST console (I'm using Talend API Tester), I get a 404 error. The URL I'm inputting is, as described in the book, https://mytriplogfunctionapp.azurewebsites.net/api/entry. In Talend API Tester, I set the entry to GET, click on Send, and 404! I've poked around on the web and verified that the version is set to 3, and the function app is set to 64-bit, but no joy. The book is 2 years old, so something may have changed. Anything wrong with how I'm going about this? Here's the code for the entry function:
#r "Newtonsoft.Json"
#r "Microsoft.WindowsAzure.Storage"
using System.Net;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using Newtonsoft.Json;
public static async Task<IActionResult> Run(HttpRequest req, Newtonsoft.Json.Linq.JArray entryTableInput,
IAsyncCollector<Entry> entryTableOutput, ILogger log)
{
//log.LogInformation("C# HTTP trigger function processed a request.");
log.LogInformation(req.Method);
if(req.Method == "GET")
{
return (ActionResult) new OkObjectResult(entryTableInput);
}
var requestBody = await new StreamReader(req.Body).ReadToEndAsync();
var entry = JsonConvert.DeserializeObject<Entry>(requestBody);
if(entry != null)
{
await entryTableOutput.AddAsync(entry);
return (ActionResult) new OkObjectResult(entry);
}
return new BadRequestObjectResult("Invalid entry request.");
}
public class Entry{
public string Id => Guid.NewGuid().ToString("n");
public string Title {get; set;}
public double Latitude {get; set;}
public double Longitude {get; set;}
public DateTime Date {get; set;}
public int Rating {get; set;}
public string Notes {get; set;}
public string PartitionKey => "ENTRY";
public string RowKey => Id;
}
And here's the json:
{
"bindings": [
{
"authLevel": "anonymous",
"name": "req",
"type": "httpTrigger",
"direction": "in",
"methods": [
"get",
"post"
]
},
{
"name": "$return",
"type": "http",
"direction": "out"
},
{
"type": "table",
"name": "entryTableOutput",
"tableName": "entry",
"connection": "AzureWebJobsStorage",
"direction": "out"
},
{
"type": "table",
"name": "entryTableOutput",
"tableName": "entry",
"take": 50,
"connection": "AzureWebJobsStorage",
"direction": "in"
}
]
,
"disabled" : false
}
I am generating a JavaClient using an OpenAPISpec document. I have used swagger-codegen 3.0 to generate the code. The OpenAPISpec version is 3.0.1.
Below is the OpenAPI snippet I am facing problems with:
"RequestWithInsuranceInfo": {
"type": "object",
"description": "This request schema will produce a response containing an out of pocket estimate for the given service using the patient's insurance information.",
"additionalProperties": false,
"properties": {
"insuranceInfo": {
"$ref": "#/components/schemas/InsuranceInfo"
},
"service": {
"type": "object",
"additionalProperties": false,
"description": "Schema to use when the patient's benefit info is not given in the request.",
"properties": {
"codes": {
"type": "array",
"items": {
"$ref": "#/components/schemas/ServiceCode"
}
},
"provider": {
"$ref": "#/components/schemas/Provider"
},
"costs": {
"$ref": "#/components/schemas/ServiceCosts"
}
},
"required": [
"codes",
"provider",
"costs"
]
}
}
},
"InsuranceInfo": {
"description": "Information about the payer, plan, and members.",
"additionalProperties": false,
"oneOf": [
{
"type": "object",
"additionalProperties": false,
"title": "Option 1: Patient Is Policy Holder",
"description": "Schema to use when the patient the primary on the insurance plan.",
"properties": {
"payer": {
"$ref": "#/components/schemas/Payer"
},
"policyHolderInfo": {
"$ref": "#/components/schemas/PolicyHolderInfo"
}
},
"required": [
"payer",
"policyHolderInfo"
]
},
{
"type": "object",
"additionalProperties": false,
"title": "Option 2: Patient Is Dependent",
"description": "Schema to use when the patient is a dependent on the insurance plan.",
"properties": {
"payer": {
"$ref": "#/components/schemas/Payer"
},
"dependentMemberInfo": {
"$ref": "#/components/schemas/DependentMemberInfo"
},
"policyHolderInfo": {
"$ref": "#/components/schemas/PolicyHolderInfo"
}
},
"required": [
"payer",
"dependentMemberInfo",
"policyHolderInfo"
]
}
]
},
Below is the code which gets generated:
public class InsuranceInfo implements OneOfInsuranceInfo {
#Override
public boolean equals(java.lang.Object o) {..}
#Override
public int hashCode() {..}
#Override
public String toString() {..}
private String toIndentedString(java.lang.Object o) {..}
}
public interface OneOfInsuranceInfo {
}
public class RequestWithInsuranceInfo implements OneOfRequest {
#SerializedName("insuranceInfo")
private InsuranceInfo insuranceInfo = null;
#SerializedName("service")
private RequestWithInsuranceInfoService service = null;
..
}
public class Payer {
#SerializedName("id")
private String id = null;
..
}
public class PolicyHolderInfo {
#SerializedName("memberId")
private String memberId = null;
#SerializedName("firstName")
private String firstName = null;
#SerializedName("lastName")
private String lastName = null;
#SerializedName("dateOfBirth")
private LocalDate dateOfBirth = null;
..
}
public class DependentMemberInfo {
#SerializedName("memberId")
private String memberId = null;
#SerializedName("firstName")
private String firstName = null;
#SerializedName("lastName")
private String lastName = null;
#SerializedName("dateOfBirth")
private LocalDate dateOfBirth = null;
..
}
As shown, the InsuranceInfo object implements the OneOfInsuranceInfo interface but has no variables. Payer, PolicyHolderInfo and dependentMemberInfo class are generated but they are not linked to the InsuranceInfo class anyhow. How do I populate the InsuranceInfo class?
The issue is probably that the InsuranceInfo schema
"InsuranceInfo": {
"description": "Information about the payer, plan, and members.",
"additionalProperties": false,
"oneOf": [
{ ... },
{ ... }
]
}
effectively disallows ALL properties. This is because additionalProperties: false only knows about the properties defined directly alongside it and has no visibility into oneOf subschemas.
To resolve the issue, you can rewrite the InsuranceInfo schema without oneOf, as follows. This schema is basically "Option 2" from the original schema, except the dependentMemberInfo property is defined as optional.
"InsuranceInfo": {
"description": "Information about the payer, plan, and members.",
"additionalProperties": false,
"type": "object",
"required": [
"payer",
"policyHolderInfo"
],
"properties": {
"payer": {
"$ref": "#/components/schemas/Payer"
},
"dependentMemberInfo": {
"$ref": "#/components/schemas/DependentMemberInfo"
},
"policyHolderInfo": {
"$ref": "#/components/schemas/PolicyHolderInfo"
}
}
}
I'm trying to build an ASP.NET Core API which returns an object containing some of related, joined, data.
But when I call an Article model using Include(), it's mapped automatically by EF Core. Which is intended, but.. unexpected mapping is occurring.
EF Core produce fully mapped object for all objects so that too much data is generated.
For example, User model has Article property and when I call Article model, User property also contains Article object information. As a result, it returns a lot of duplicated data.
At the first time, I think it's coming from
https://learn.microsoft.com/en-us/ef/core/querying/related-data
Eager loading
Explicit loading
Lazy loading
But it wasn't.
I don't know how to configure EF Core not to map all the objects inside..
*Database Schema & Model
enter image description here
public class Topic
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[HiddenInput(DisplayValue = false)]
public int TopicId { get; set; }
[Required]
[StringLength(20, ErrorMessage = "Title cannot be longer than 20 characters.")]
public string Title { get; set; }
[StringLength(200, ErrorMessage = "Description cannot be longer than 200 characters.")]
public string Description { get; set; }
public byte[] Picture { get; set; }
public string PictureMimeType { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime PostDate { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime ModifyDate { get; set; }
[Display(Name = "Show")]
public bool ShowFlag { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
public class Article
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[HiddenInput(DisplayValue = false)]
public int ArticleId { get; set; }
[Required]
[StringLength(100, ErrorMessage = "Title cannot be longer than 100 characters.")]
public string Title { get; set; }
public string Content { get; set; }
[StringLength(10, ErrorMessage = "Category cannot be longer than 10 characters.")]
public string Category { get; set; } = "Free";
public byte[] Picture { get; set; }
public string PictureMimeType { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime PostDate { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:MM-dd-yyyy}", ApplyFormatInEditMode = true)]
public DateTime ModifyDate { get; set; }
public int ReadCount { get; set; }
[Required]
[Display(Name = "Show")]
public bool ShowFlag { get; set; }
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
}
public class User
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[HiddenInput(DisplayValue = false)]
public int UserId { get; set; }
[StringLength(20, ErrorMessage = "Name cannot be longer than 20 characters.")]
public string Name { get; set; }
[StringLength(255, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
[StringLength(20, ErrorMessage = "Title cannot be longer than 20 characters.")]
public string Title { get; set; }
[StringLength(20, ErrorMessage = "Phone number cannot be longer than 20 characters.")]
[DataType(DataType.PhoneNumber)]
public string Phone { get; set; }
[StringLength(100, ErrorMessage = "Address cannot be longer than 100 characters.")]
public string Address { get; set; }
[StringLength(100, ErrorMessage = "Introduction cannot be longer than 100 characters.")]
public string Introduction { get; set; }
[DataType(DataType.Date)]
public DateTime? Birthdate { get; set; }
public byte[] Picture { get; set; }
public string PictureMimeType { get; set; }
public virtual ICollection<Topic> Topics { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
*Controller Code
var article = await this._context.Articles
.Include(a => a.User)
.Include(a => a.Topic)
.SingleOrDefaultAsync(a => a.ArticleId == id);
*Duplicated Data
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"topic": {
"topicId": 1,
"title": "C#",
"description": "About C#",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-05-28T07:00:00",
"modifyDate": "2018-05-28T23:36:04.701311",
"showFlag": true,
"userId": 1,
"user": {
"userId": 1,
"name": "",
"password": null,
"email": "",
"title": "Junior Programmer!!",
"phone": "",
"address": "",
"introduction": "Hey, I'm Jason, most motivated person to be a fullstack programmer! ",
"birthdate": null,
"picture": null,
"pictureMimeType": null,
"permissionId": 0,
"permission": null,
"topics": [],
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"userId": 1
}
]
},
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"userId": 1,
"user": {
"userId": 1,
"name": "",
"password": null,
"email": "",
"title": "Junior Programmer!!",
"phone": "",
"address": "",
"introduction": "Hey, I'm Jason, most motivated person to be a fullstack programmer! ",
"birthdate": null,
"picture": null,
"pictureMimeType": null,
"topics": [],
"articles": []
}
}
]
},
"userId": 1,
"user": {
"userId": 1,
"name": "",
"password": null,
"email": "",
"title": "Junior Programmer!!",
"phone": "",
"address": "",
"introduction": "Hey, I'm Jason, most motivated person to be a fullstack programmer! ",
"birthdate": null,
"picture": null,
"pictureMimeType": null,
"topics": [
{
"topicId": 1,
"title": "C#",
"description": "About C#",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-05-28T07:00:00",
"modifyDate": "2018-05-28T23:36:04.701311",
"showFlag": true,
"userId": 1,
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"userId": 1
}
]
}
],
"articles": [
{
"articleId": 5,
"title": "Does TaskAll() really wait for all in any case?",
"content": null,
"category": "Threading",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-06-18T07:00:00",
"modifyDate": "2018-06-20T07:47:42.1854485",
"readCount": 101,
"showFlag": true,
"topicId": 1,
"topic": {
"topicId": 1,
"title": "C#",
"description": "About C#",
"picture": null,
"pictureMimeType": null,
"postDate": "2018-05-28T07:00:00",
"modifyDate": "2018-05-28T23:36:04.701311",
"showFlag": true,
"userId": 1,
"articles": []
},
"userId": 1
}
]
}
}
Actually , there's no "duplicated" data .
When the following query executed :
var article = await this._context.Articles
.Include(a => a.User)
.Include(a => a.Topic)
.SingleOrDefaultAsync(a => a.ArticleId == id);
the sql behind the scene will be :
SELECT TOP(1)
[a].[ArticleId], [a].[Category], [a].[Content], [a].[ModifyDate], [a].[Picture], [a].[PictureMimeType], [a].[PostDate],[a].[ReadCount], [a].[ShowFlag], [a].[Title],
[a].[TopicId], [a].[UserId],
[a.Topic].[TopicId], [a.Topic].[Description], [a.Topic].[ModifyDate], [a.Topic].[Picture], [a.Topic].[PictureMimeType], [a.Topic].[PostDate], [a.Topic].[ShowFlag], [a.Topic].[Title],[a.Topic].[UserId],
[a.User].[UserId], [a.User].[Address], [a.User].[Birthdate], [a.User].[Email], [a.User].[Introduction], [a.User].[Name], [a.User].[Password], [a.User].[Phone], [a.User].[Picture], [a.User].[PictureMimeType], [a.User].[Title]
FROM [Article] AS [a]
INNER JOIN [Topics] AS [a.Topic] ON [a].[TopicId] = [a.Topic].[TopicId]
INNER JOIN [Users] AS [a.User] ON [a].[UserId] = [a.User].[UserId]
WHERE [a].[ArticleId] = #__id_0
The SQL will not load any extra records . Let's say a User has two articles :
Article1 : { ArticleId=1, UserId=1}
Article3 : { ArticleId=3, UserId=1}
The query above will load only one article .
When the records loaded into memory from server , EFCore knows there's a relationship of 1-to-many between User and Articles and just begins to set the user object as the navigation property of article . The same happens between User and Topic Entities. All these are finished in memory .
As you see , there's no extra records retrieved from database and no duplicated data in memory .
What makes you confused is the data that serialized to client . However ,it does make sense . As a plain string has no idea of reference / pointer , it's hard to represent the relationship between objects with json .
By the way , there's no need to decorate you Email property of User with two almost same attributes :
[DataType(DataType.EmailAddress)]
[EmailAddress]
public string Email { get; set; }
It will cause an error of "two same custom attributes" when migrating database.
Using the following JSON object I want to create models in EF and their navigation properties also need model.Builder Entity
Also need information on one to many relation using the json specified below and using migrations create database tables.
[{
"Title": "AC",
"IconClass": "ac",
"Departments": "Housekeeping,Maintenance",
"Status": "NotInOrder",
"Parts": [{
"Title": "Power",
"IconClass": "power",
"Departments": "Housekeeping,Maintenance",
"Status": "NotInOrder",
"DependentUpon": "",
"Id": null
}, {
"Title": "Remote",
"IconClass": "remote",
"Departments": "Housekeeping,Maintenance",
"Status": "CleanedMaintained",
"DependentUpon": "Power",
"Id": null
}, {
"Title": "Cooling",
"IconClass": "cooling",
"Departments": "Housekeeping,Maintenance",
"Status": "CleanedMaintained",
"DependentUpon": "Remote",
"Id": null
}],
},
{
"Title": "TV",
"IconClass": "tv",
"Departments": "Housekeeping,Maintenance",
"Status": "CleanedMaintained",
"Parts": [{
"Title": "Power",
"IconClass": "power",
"Departments": "Housekeeping,Maintenance",
"Status": "CleanedMaintained",
"DependentUpon": "",
"Id": null
},
{
"Title": "TV - Remote",
"IconClass": "remote",
"Departments": "Housekeeping,Maintenance",
"Status": "CleanedMaintained",
"DependentUpon": "Power",
"Id": null
}]
]
According to your JSON object, your Entity Framework model classes should be as follows:
public class Product
{
[Key]
public int ProductId {get; set;}
public string Title { get; set; }
public string IconClass { get; set; }
public string Departments { get; set; }
public string Status { get; set; }
public ICollection<Part> Parts { get; set; }
}
public class Part
{
[Key]
public string Id { get; set; }
[ForeignKey("Product")]
public int ProductId {get; set;}
public string Title { get; set; }
public string IconClass { get; set; }
public string Departments { get; set; }
public string Status { get; set; }
public string DependentUpon { get; set; }
public Product Product {get; set;}
}
Then the DbContext:
public class YourDbContext : DbContext
{
public YourDbContext () : base("name=DefaultConnection")
{
}
public static YourDbContext Create()
{
return new YourDbContext ();
}
public DbSet<Product> Products { get; set; }
public DbSet<Part> Parts { get; set; }
}
I use the simple entities below for exploring jpa-rs:
#Entity
public class Employee {
#Id
#GeneratedValue
private int id;
private String name;
#OneToOne
private Address address;
}
#Entity
public class Address {
#Id
#GeneratedValue
private int id;
private String street;
private String city;
#OneToOne(mappedBy = "address")
private Employee employee;
}
Creating an Employee entity with an Address relation works like a charm:
{
"name": "John Doe",
"address": {
"street": "Street No. 1",
"city": "A City"
}
}
But i cannot travers the resulting objects by the _links, like HATEOAS proposes.
The provided students example in the Eclipselink wiki (http://wiki.eclipse.org/EclipseLink/Examples) allows this easily.
As far as i understand, i should be able to do the following:
fetch the employee with id 1: GET url/entity/Employee/1: {
"id": 1,
"name": "John Doe",
"_relationships": [
{
"_link": {
"href": "url/entity/Employee/1/address",
"rel": "address"
}
}
],
"address": {
"_link": {
"href": "url/entity/Address/2",
"method": "GET",
"rel": "self"
}
},
"staff": []
}
fetch the address of the employee: GET url/entity/Address/2:{
"city": "A City",
"id": 2,
"street": "Street No. 1",
"_relationships": [
{
"_link": {
"href": "url/entity/Address/2/employee",
"rel": "employee"
}
}
]
}
Now lets get back to the employee from here: GET url/entity/Address/2/employee -> http status 400
In the mentioned example i can flawlessly navigate using all the links for the relationship.
p.s.: I'd like to tag this JPA-RS, but i don't have enough reputation to create a new tag.