Entity Framework relational result Web Api serialization error - c#

I am using entity framwork in my Wep Api project. My model is realational and line this:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Article> Articles { get; set; }
}
public class Article
{
public int Id { get; set; }
public int BlogId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
}
context is like this:
public class BloggingContext: DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Article> Articles { get; set; }
}
And I am using this in my asp.net Web Api controller.
public class BlogController: ApiController{
public IEnumerable<Blog> Get(){
var context = new BloggingContext();
return context.Blogs.ToList();
}
}
My approach is to get data with Lazy Loading, and serialize to JSON data as Web Api response.
context.Blogs.ToList() returns the relational data (I see on breakpoint).
But Web Api result has error.
Exception Message:
The 'ObjectContent`1' type failed to serialize the response body for
content type 'application/json; charset=utf-8
inner exception:
Error getting value from 'Blog' on
'System.Data.Entity.DynamicProxies.Article_D002A1ECE031410435306DCEF780AFF03EBB8BD36DA603662C993107FAEB1917 "ExceptionType": "Newtonsoft.Json.JsonSerializationException
I set my WebApiConfig.cs
var jsonFormatter = config.Formatters.OfType<JsonMediaTypeFormatter>().First();
jsonFormatter.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
jsonFormatter.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);

You might try disabling proxy generation. At the minimum, that may give you a clearer exception if it doesn't fix the issue.
context.Configuration.ProxyCreationEnabled = false;

The old lazy loading questions! Such fun!
So firstly, when you breakpoint and evaluate an entity this by itself triggers lazy loading. Entity Framework is clever like that, but not helpful. When in fact, if you are not debugging, those same properties will be null.
You have 2 options here, 1. Eager Load or 2. As previously mentioned, use a transfer object. But why will they will.
is obvious. by using context.Blogs.Include(x => x... etc) you are telling entity framework just to go ahead and load them because we will need it.
Why does using a transfer object work? Well in order to transfer the data into the new object you must call the Get method on all properties within the old entity, thus triggering lazy loading. For this you can use 3rd party packages such as AutoMapper but they can cause overhead.
For an API I personally suggest Eager loading. The point of an API is that you as the develop design endpoints and you restrict what can and can't be used. If you want lazy loading then I suggest MVC is a better option.

Try using data transfer objects in your controller.In my case this fixed the issue.BlogModel is the name of the new class that you have to add to use as DTO.It has the same properties as your entity class.
public IEnumerable<Blog> Get(){
List<BlogModel> listofModels = new List<BlogModel>();
foreach(var model in whateveristhenameofyourentity.Blogs)
{
BlogModel blogModel = new BlogModel();
blogModel.name = model.name;
.
.
.
listofModels.Add(BlogModel);
}
IEnumerable<BlogModel> models = listofModels.AsIEnumerable();
return models;
}

Related

How to utilise Entity Framework with a database hierarchy from API data

I might not have worded this question in the correct format as it's difficult to explain what the problem is. From the research I've gathered I think database hierarchy is something that I'm trying to utilize when using Entity Framework to create a database, I'll try to explain my problem in the best way possible.
Firstly, I'm running a Visual Studio 2019 project with a console application. This is my Program.cs file which is currently using a get request from an API URL with RESTSHARP then performing deserialization.
static void Main(string[] args)
{
getAPIData()
}
public static void getAPIData()
{
var client = new RestClient(URL);
var request = new RestRequest();
var response = client.Execute(request);
if (response.StatusCode == System.Net.HttpStatusCode.OK)
{
string rawResponse = response.Content;
Requests.Rootobject result = JsonConvert.DeserializeObject<Requests.Rootobject>(rawResponse);
}
}
As you can see above, the raw data that's gathered from the URL (my API) is converted to a JSON format. And when I debug the code, it stores the code perfectly into the Requests DTO, as I'll show below.
public class Requests
{
public int Id { get; set; }
public class Rootobject
{
public Operation operation { get; set; }
}
public class Operation
{
public Result result { get; set; }
public Detail[] details { get; set; }
}
public class Result
{
public string message { get; set; }
public string status { get; set; }
}
public class Detail
{
public string requester { get; set; }
public string workorderid { get; set; }
public string accountname { get; set; }
}
}
The recently converted API data is now stored in this class in this correct format. Which is what's necessary for the API itself since it comes in numerous classes. This object now holds all the relevant data I need, now I can utilize the entity framework to generate an appropriate table that will hold this into an SQL server.
Here is the Entity Framework class.
public class TransitionContext : DbContext
{
private const string connectionString = #"(connection string here)";
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(connectionString);
}
public DbSet<Requests> Requesting { get; set; }
}
Currently, when I run the package manager console commands to build a migration and setup a connection to the server, it works fine (Obviously I've not included my real connection string for privacy reasons). However, when I use the "Update-Database" command from the DbSet which is my DTO for the API data. The only key that is displayed is an ID, nothing else.
SQL requests table
All that's generated here is an Id key from the Requests Dto. Now, on the API itself, there is no ID key, I added it in there because Entity Framework said I had to have an ID key and was displaying errors until I made one. But now that's all it's generating when I "Update-Database".
The reason why I showed as much data as I have done, is because the process is simply exporting the converted JSON data (a recently gathered API) into an SQL table. Or tables so that it supports a hierarchy, anything that will map correctly from the DTO to the database. But as of right now, my only issue with the project is that only an ID is being generated.
So I'm not sure where I'm going wrong here or how to get this working as I intend it to work. Apologies for the really long explanation, I tried to condense it as much as I could, any help would be appreciated and any questions I'm free to answer, thank you!

ASP.NET 3.1 Web API, Entity Framework one to many relationship either not made or ending in cycle

So I am trying to write a server with a database which would host a site similar to reddit: with users, groups, posts and comments.
As this is my first time developing backend, I tried googling and found ASP.NET Core 3.1 Web API with Entity Framework to be "good". (I use SQL Server as the database server if that's relevant)
Now I went with the code first method, and created the tables with a migration.
(ERD)
I used the built in tool to create the controllers.
If I used this JSON to create a post entity (with HTTP POST), the AuthorId and the LocationId stay on NULL (weather I use "" for the numbers or not). I created the Group and the User it would refer to earlier.
The JSON:
{
"title": "Post",
"posttext": "text",
"creatorid": "1",
"locationid": "1",
"timeofpost": "2020-01-12"
}
The Post class:
using System;
using System.Collections.Generic;
namespace CedditBackend.Models
{
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
#nullable enable
public string? PostText { get; set; }
public byte[]? Content { get; set; }
public ICollection<Comment>? Comments { get; set; }
#nullable disable
public User Creator { get; set; }
public Group Location { get; set; }
public DateTime TimeOfPost { get; set; }
}
}
The autogenerated HTTPPOST in the controller:
[HttpPost]
public async Task<ActionResult<Post>> PostPost(Post post)
{
_context.Posts.Add(post);
await _context.SaveChangesAsync();
return CreatedAtAction("GetPost", new { id = post.Id }, post);
}
On the other hand if I try and create my own post class from the JSON (basically deserializing it), I get an error that "a cycle was detected". This I tried googling, but only found that with an earlier version (I guess) I could just turn that off.
Still if I open the database, it shows me that the values of LocationId and AuthorId are not NULL, but if I try to get the posts of the Users (with an HTTP GET), all I get is the same error message.
My rewritten HTTPPOST
[HttpPost]
public async Task<ActionResult<Post>> PostPost(Object obj)
{
Dictionary<string, object> dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(obj.ToString());
Post post = new Post();
post.Title = dict["title"].ToString();
post.PostText = dict["posttext"].ToString();
post.TimeOfPost = DateTime.Parse(dict["timeofpost"].ToString());
post.Location = _context.Groups.FirstOrDefault(x => x.Id == int.Parse(dict["locationid"].ToString()));
post.Creator = _context.Users.FirstOrDefault(x => x.Id == int.Parse(dict["creatorid"].ToString()));
post.Creator.Posts.Add(post);
post.Location.Posts.Add(post);
_context.Posts.Add(post);
await _context.SaveChangesAsync();
return CreatedAtAction("GetPost", new { id = post.Id }, post);
}
I tried to google this whole stuff in several ways, but found nothing. Am I missing something obvious?
Edit: the User class (which I think causes the cycle reference)
using System.Collections.Generic;
namespace CedditBackend.Models
{
public class User
{
public int Id { get; set; }
public string UserName { get; set; }
#nullable enable
public ICollection<Post>? Posts { get; set; } = new List<Post>();
public ICollection<Comment>? Comments { get; set; } = new List<Comment>();
public ICollection<UserGroup>? UserGroups { get; set; }
#nullable disable
}
}
It looks like you're running into the same problem I currently have.
The new JSON library System.Text.JSON is currently unable to handle One-to-Many relationships it seems. They are currently tracking the issue here, but a fix isn't planned until .NET 5. I haven't found a solution at this point but I will update this answer if I do.
UPDATE
Found a workaround, at least for now.
Install Newtonsoft MVC Extensions
PM> Install-Package Microsoft.AspNetCore.Mvc.NewtonsoftJson -Version 3.1.1
Edit ConfigureServices method in Startup.cs
services
.AddControllers()
.AddNewtonsoftJson()
Now configure the loop handling in Newtonsoft
services
.AddControllers()
.AddNewtonsoftJson(options =>
{
options.SerializerSettings.ReferenceLoopHandling =
Newtonsoft.Json.ReferenceLoopHandling.Ignore;
});
That should be enough to get it working. Keep in mind if you have any custom JsonConverters you will have to change to the converters provided in the Newtonsoft library to get them to work.
Try removing these lines.
post.Creator.Posts.Add(post);
post.Location.Posts.Add(post);
You've already defined the Creator and Location of your post. Adding it to their collections would make it a circular reference.

Entity Framework DB first issues loading related entities (ASP.NET web api)

I've been looking for answers for this relatively simple task but with no success. So I thought I'd ask my question here. I have got a simple database with two tables, Books and Authors.
I got my models generated by the ADO.NET entity data model. This is the auto-generated Books model:
public partial class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public int ISBN { get; set; }
public int AuthorID { get; set; }
public virtual Author Author { get; set; }
}
And this is the auto-generated Authors model:
public partial class Author
{
public Author()
{
this.Books = new HashSet<Book>();
}
public int AuthorID { get; set; }
public string Name { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
And this is a part of the controller, the method for getting a list of all the books in JSON format.
// api/books
public IQueryable<Book> GetBooks()
{
// return db.Books.Include(x => x.Authors); Don't work
return db.Books;
}
This is my JS for calling the endpoint:
$.getJSON("api/books")
.done(function (data) {
console.log(data);
})
.fail(function (xhr) { console.log(xhr.responseText) });
Nothing fancy, just trying to make a GET request and receiving a list of all the books and their related authors.
This is a portion of the error message:
{"Message":"An error has occurred.","ExceptionMessage":"The 'ObjectContent`1' type failed to serialize the response body for content type 'application/json; charset=utf-8'.","ExceptionType":"System.InvalidOperationException","StackTrace":null,"InnerException":{"Message":"An error has occurred.","ExceptionMessage":"Self referencing loop detected for property 'Author' with type 'System.Data.Entity.DynamicProxies.Author_5968F94A1BBB745A30D62CD59D0AC5F96A198B3F16F0EA2C2F61575F70034886'. Path '[0].Books[0]'.","ExceptionType":"Newtonsoft.Json.JsonSerializationException","StackTrace":"
I have tried preserving object references in JSON but that mangles the response. Is that is the only option?
If you examine the inner exception it says:
Self referencing loop detected for property 'Author'
This tells you that your Author class references back to the parent (i.e. Books or vice versa).
In your web api config (App_Start/WebApiConfig.cs), add this:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Prevent "Self referencing loop detected" error occurring for recursive objects
var serializerSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
config.Formatters.JsonFormatter.SerializerSettings = serializerSettings;
}
}
This tells JSON.NET to ignore nested objects referring back to the parent object
adaam's answer is 100% correct but I thought I'd chime in with a bit of advice, but it's too long to fit in a comment so here goes.
Directly serialising Entity Framework objects is generally not a great idea; it's better to use simple DTO-style objects for passing data back to clients.
Of course this is just advice and YMMV :)
The benefits of using DTOs include
Proper decoupling from your Entity Framework objects in your Controllers (you could create a Repository abstraction and use that in your controller, meaning your controller is free from a dependency on Entity Framework and thus more testable)
Simpler serialization control - with Entity Framework you will have difficulty trying to control what public properties are send across the wire to clients when the Entity Framework proxy is directly serialized; typically in DB First the declarations for these properties are in auto-generated files that are re-written each time your edmx changes; threfore it becomes painful to have to maintain non-serialization attributes on the properties you don't want sent across the wire (e.g [IgnoreDataMember], etc.)
If you're planning on accepting models via POST, PUT, etc, then you'll rapidly find that it is a pain (if not impossible) to effectively serialize "inward" directly to the Entity Framework proxies so you'll have to write mapping code anyway; by using a DTO approach you accept that you have to map up-front.
Circular references don't happen therefore you never need to worry about it and more importantly, you don't pay the cost of ignoring it (albeit minor, but the serializer has to do some work to avoid these references)
You can easily perform extra transformations, for example flattening, to better suit the client and/or hide details you don't want sent across the wire.
Example
public class BookDTO
{
public int BookID {get;set;}
public string Title {get;set;}
public string Description {get;set;}
public int ISBN {get;set;}
public string AuthorName{get;set;}
}
public HttpResponseMessage GetBooks()
{
//ideally you'd be using a repository abstraction instead of db directly
//but I want to keep this simple.
var books = db.Books.Select(
book=>new BookDTO(){
BookID=book.BookID,
Title=book.Title,
Description=book.Description,
ISBN=book.ISBN,
AuthorName=book.Author.Name //<-flattening
});
return Request.CreateResponse(HttpStatusCode.OK, books);
}
This produces an array of nice, flat objects for the client to consume without having to expose for example the AuthorID which might to be an internal concept you don't particularly want clients to know.
Once you get the hang of it, you can then look at using something like Automapper which will greatly reduce the maintenance burden, and allow you to perform inline projection in your queries.
It is always best to create your own custom models while returning the data but you just want to use Entity Framework classes then along with ignoring refrenceLoopHandeling you would need disable ProxyCreation from you Entity Model.
Follow these steps :
Step 1 : Like adaam described Put his in WebApiConfig register function:
// Prevent "Self referencing loop detected" error occurring for recursive objects
var serializerSettings = new JsonSerializerSettings()
{
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
};
config.Formatters.JsonFormatter.SerializerSettings = serializerSettings;
Step 2 : Which is most important in my case with latest EF to disable Proxy from EF Contaxt.
Goto: [FILE].edmx file then your [FILE].Context.cs
and add the line below in your constructor..
Configuration.ProxyCreationEnabled = false;
Now you won't have related class results any more..

ASP.Net Web API returns null instead of non-simple properties

I've just started to trying to build some test ASP.Net Web API app, and got a problem:
I have models as:
public class Asset
{
public int Id { get; set; }
[ForeignKey("AssetType")]
public int AssetTypeId { get; set; }
public AssetType AssetType { get; set; }
}
public class AssetType
{
public int Id { get; set; }
public string Name { get; set; }
}
and trying to get all records from db via
public IEnumerable<Asset> GetAssets()
{
return db.Assets.AsEnumerable();
}
I get some strange results (atleast that's strange for me). Instead of expanding AssetType - I get nil's:
<Asset>
<AssetTypeId>1</AssetTypeId>
<Id>1</Id>
<AssetType i:nil="true"/>
</Asset>
I've checked my db - it contains correct IDs in both AssetTypeId and AssetType fields, pointing to correct asset type.
The only way I've found to get fields expanded - is by changing GetAssets to:
public HttpResponseMessage GetAssets()
{
return Request.CreateResponse(HttpStatusCode.OK, db.Assets.Include("Type").ToList());
}
But I feel I'm very wrong, because in reality I have dozens of such 'complex' fields with some of them nested, and adding them via ".Include("Type")" - is wrong.
So the question is - where I've made an error? How can I get my data correctly?
This is because of lazy loading.
Lazy loading is a concept where we delay the loading of the object until the point where we need it. Putting in simple words, on demand object loading rather than loading objects unnecessarily.
When you did .include, it actually loaded that data and you are able to get it.
There is no default configuration for eager loading. You must always define Include or create some reusable method which will wrap adding include.
Entity Framework 4.1 default eager loading

ServiceStack C# strongly typed client DTO

Here: Recommended ServiceStack API Structure and here: https://github.com/ServiceStack/ServiceStack/wiki/Physical-project-structure are recommendations for how to structure your projects for C# clients to reuse DTOs.
Apparently this is done by including a dll of the DTO assembly. I have searched the web for one example, just Hello World that uses a separate assembly DTO for a C# client in ServiceStack. Perhaps I should be able to break this out myself but so far it has not proven that easy.
Almost all client descriptions are for generic and non-typed JSON or other non-DTO based clients. No one appears interested in typed C# clients like I am (even the ServiceStack documentation I have found). So I thought this would be a good question even if I figure it out myself in the end.
To be clear, I have built and run the Hello World example server. I have also used a browser to attach to the server and interact with it. I have also created a client empty project that can call
JsonServiceClient client = new JsonServiceClient(myURL);
Then I tried to copy over my DTO definition without the assembly DLL as I don't have one. I get ResponseStatus is undefined.
Clearly there is something missing (it appears to be defined in ServiceStack.Interfaces.dll) and if I could create a dll of the DTO I think it would resolve all references.
Can anyone give insight into how to create the DTO assembly for the simple Hello World?
Edited to add code:
using ServiceStack.ServiceClient.Web;
namespace TestServiceStack
{
class HelloClient
{ public class HelloResponse
{
public string Result { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
//Request DTO
public class Hello
{
public string Name { get; set; }
}
HelloResponse response = client.Get(new Hello { Name = "World!" });
}
}
Where the ResponceStatus is undefined.
I was able to find the missing symbol ResponseStatus by adding:
using ServiceStack.ServiceInterface.ServiceModel;
Here is the full code that built. Keep in mind that I found out something else in the process. Once this built it then failed because I was using a DTO from a .NET 4.0 environment in a .NET 3.5 environment. But that is an unrelated issue. Also note that this test code does nothing with the response, it is just an example to get the build working.
using ServiceStack.ServiceClient;
using ServiceStack.ServiceInterface;
using ServiceStack.Text;
using ServiceStack.Service;
using ServiceStack.ServiceHost;
using ServiceStack.WebHost;
using ServiceStack;
using ServiceStack.ServiceClient.Web;
using RestTestRoot; // This is the name of my DTO assembly. You will need to insert your own here.
using ServiceStack.ServiceInterface.ServiceModel;
namespace WebApplicationRoot
{
class HelloClient
{
JsonServiceClient hello_client;
//Request DTO
public class Hello
{
public string Name { get; set; }
}
//Response DTO
public class HelloResponse
{
public string Result { get; set; }
public ResponseStatus ResponseStatus { get; set; } //Where Exceptions get auto-serialized
}
//Can be called via any endpoint or format, see: http://mono.servicestack.net/ServiceStack.Hello/
public class HelloService : Service
{
public object Any(Hello request)
{
return new HelloResponse { Result = "Hello, " + request.Name };
}
}
//REST Resource DTO
[Route("/todos")]
[Route("/todos/{Ids}")]
public class Todos : IReturn<List<Todo>>
{
public long[] Ids { get; set; }
public Todos(params long[] ids)
{
this.Ids = ids;
}
}
[Route("/todos", "POST")]
[Route("/todos/{Id}", "PUT")]
public class Todo : IReturn<Todo>
{
public long Id { get; set; }
public string Content { get; set; }
public int Order { get; set; }
public bool Done { get; set; }
}
public HelloClient(){
// ServiceStack gateway = new ServiceStack.ClientGateway(
// location.protocol + "//" + location.host + '/ServiceStack.Examples.Host.Web/ServiceStack/');
hello_client = new JsonServiceClient("http://tradetree2.dnsapi.info:8080/");
hello_client.Get<HelloResponse>("/hello/MyTestWorld!");
}
}
}

Categories

Resources