List<> behave different after looking at it in Debug mode - c#

I access data in a database through a Web-Api and send it back as Json string to an UWP-App.
MessageRepository.cs:
public List<Message> GetByRecipientId(int id)
{
var listOfMsg = new List<Message>();
using (var ctx = new ShowcaseContext())
{
var recipientWithId = ctx.Recipients.Where(rec => rec.UserId == id);
foreach (var recipient in recipientWithId.ToList())
{
var msgWithId = ctx.Messages.Where(msg => msg.Id == recipient.MessageId);
listOfMsg.Add(msgWithId.Single());
}
return listOfMsg;
}
}
MessageController.cs:
[Route("api/message/recipient/{id}")]
public IEnumerable<Message> GetByRecipientId(int id)
{
return messageRepository.GetByRecipientId(id);
}
The problem is that I'm getting an
"Newtonsoft.Json.JsonSerializationException" Error
when trying to return listOfMsg to the UWP-App.
But if I look at the List<> and it items in debug mode, or access a specific message in the List<> before returning it, the error disappeares and I get the right data in the UWP-App.
It seems like the List<> has a different format before accessing it specifically which can't be serialized.
Has anyone an idea why?
Edit
Message.cs:
public class Message
{
public int Id { get; set; }
public virtual Collection<Recipient> Recipients { get; set; }
public MessageTrigger Trigger { get; set; }
public string Body { get; set; }
public DateTime CreatedTs { get; set; }
}
The StackTrace is null, but the error message is
The "ObjectContent`1" type failed to serialize the response body for content type "application/json; charset=utf-8".

Most probably you have lazy loading enabled. That means that when you access a navigation property, EF will try to hit the database to get the related entity, and this will work if the DbContext is still "alive".
After the API call returns the data, JSON.NET will visit all the properties to serialize them, including the navigation properties. And, at that point, the DbContext has already been disposed of. That's why you get your error.
To avoid it you can:
disable lay loading for a particular query: Entity Framework: How to disable lazy loading for specific query?
completely disable it, if you're not using it: Disable lazy loading by default in Entity Framework 4
For more info on disabling lazy loading.
Alternative: Json configuration
You can also instruct JSON.NET to skip some properties. But there are mabny ways of doing so, and you have to add attributes to your entities. For example:
JsonObjectAttribute opt-in serialization
JsonIgnoreAttribute
Alternative: projection to anonymus class with .Select()
And I forgot a final, easy option: you can project your query to an anonymous class, getting read of all problems, and returning the JSON that you exactly want to return.
.Select(new { /* your projection */ });
Although this answers are for older versions, it still works in the same way.
NOTE: while you're debugging you're still inside the DbContext using block, and that's why you don't get errors while debugging. If you put this line: return listOfMsg; outside the using block, add a breakpoint in that line, and watch navigation properties, you'll also get errors in the debugger.

Related

Deserialize into nested objects from Cypher Result in c#

I've been wanting to deserialize automatically from a Cypher Result into their corresponding objects via neo4jclient.
These are the classes I want it to deserialize into
public class QuestionHub
{
public string Id { get; set; }
public string Title { get; set; }
public ICollection<Question> Questions {get;set;}
}
public class Question
{
public string Id { get; set; }
public string Title { get; set; }
public ICollection<Answer> Answers { get;set; }
}
public class Answer
{
public string Id { get; set; }
public string Value { get; set; }
}
I know for a fact that this code will put the corresponding QuestionHubs into a list of QuestionHubs. This is exactly what I want, but the problem is that those property that navigate to other classes, are not included.
var hubQuery = graphClient.Cypher
.Match("(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.ReturnDistinct<QuestionHub>("qh");
This is the
result
As you can see, the questions are not included.
Whenever I do this
var hubQuery = graphClient.Cypher
.Match("(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.ReturnDistinct<QuestionHub>("*");
I get the error
System.ArgumentException: 'Neo4j returned a valid response, however Neo4jClient was unable to deserialize into the object structure you supplied.
First, try and review the exception below to work out what broke.
If it's not obvious, you can ask for help at http://stackoverflow.com/questions/tagged/neo4jclient
Include the full text of this exception, including this message, the stack trace, and all of the inner exception details.
Include the full type definition of Neo4JTests.QuestionHub.
Include this raw JSON, with any sensitive values replaced with non-sensitive equivalents:
{ "columns":["a","q","qh"], "data":[[ {"data":{ "Value":"answer9aaf5134-9e73-4681-ba2f-e8224242ff19","Id":"9aaf5134-9e73-4681-ba2f-e8224242ff19" }},{"data":{ "Title":"questiond287a365-364a-4de0-b9f2-574893c1eaaa","Id":"d287a365-364a-4de0-b9f2-574893c1eaaa" }},{"data":{ "Title":"questionHub222a2fbe-6644-491a-b0a1-66df59f05f11","Id":"222a2fbe-6644-491a-b0a1-66df59f05f11" }} ]] } Arg_ParamName_Nam'
Inner Exception
InvalidOperationException: The deserializer is running in single column mode, but the response included multiple columns which indicates a projection instead. If using the fluent Cypher interface, use the overload of Return that takes a lambda or object instead of single string. (The overload with a single string is for an identity, not raw query text: we can't map the columns back out if you just supply raw query text.)
This error is probably because the cypher result gives multiple columns instead of one.
This is what I want to get
If I do this
var hubQuery = graphClient.Cypher
.Match("(u:User)-[:CREATOR_HUB]-(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.With("u, qh, q, COLLECT({Id: a.Id, Value: a.Value}) as answers")
.With("u, qh, COLLECT({Id: q.Id, Title: q.Title, Answers:answers}) as questions")
.With("{Creator: {Id:u.Id, FirstName: u.FirstName, LastName: u.LastName}," +
"Id: qh.Id, Title: qh.Title, Questions: questions} as result")
.ReturnDistinct<string>("result");
var hubQueryRes = await hubQuery .ResultsAsync;
List<QuestionHub> hubList = new List<QuestionHub>();
foreach (var hub in hubQueryRes )
{
hubList .Add(JsonConvert.DeserializeObject<QuestionHub>(hub));
}
I get what I want, but I needed to write all those .With
I want a way to automatically do that without all the .With writing.
I'm looking for a way to automatically deserialize the Cypher result into their corresponding objects with nested objects included.
Is there a way to do this?
Thanks in advance!
You're 100% correct that the reason it didn't create your QuestionHub instance is because the return was entirely in the wrong format for the client to cope with.
Unfortunately - your with workaround is about the only way - As you're using it to return the output of Cypher into a format that the Json Deserializer can handle.
The best I can see to do would be this:
var query = gc.Cypher
.Match("(qh:QuestionHub)-[:PART_OF]-(q:Question)-[:ANSWER]-(a:Answer)")
.With("qh, q{.*, Answers: COLLECT(a)} AS qAndA")
.With("qh{.*, Questions: COLLECT(qAndA)} AS result")
.Return(result => result.As<QuestionHub>());
Bear in mind, you would also need your ICollection to be either List or IEnumerable to deserialize properly - deserializing into ICollection isn't supported.

How to save/pass MongoDB UpdateDefinition for logging and later use?

I am stumped on how to save/pass MongoDB UpdateDefinition for logging and later use
I have created general functions for MongoDB in Azure use on a collection for get, insert, delete, update that work well.
The purpose is to be able to have a standard, pre-configured way to interact with the collection. For update especially, the goal is to be able to flexibly pass in an appropriate UpdateDefinition where that business logic is done elsewhere and passed in.
I can create/update/set/combine the UpdateDefinition itself, but when i try to log it by serializing it, it shows null:
JsonConvert.SerializeObject(updateDef)
When I try to log it, save it to another a class or pass it to another function it displays null:
public class Account
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
}
var updateBuilder = Builders<Account>.Update;
var updates = new List<UpdateDefinition<Account>>();
//just using one update here for brevity - purpose is there could be 1:many depending on fields updated
updates.Add(updateBuilder.Set(a => a.Email, email));
//Once all the logic and field update determinations are made
var updateDef = updateBuilder.Combine(updates);
//The updateDef does not serialize to string, it displays null when logging.
_logger.LogInformation("{0} - Update Definition: {1}", actionName, JsonConvert.SerializeObject(updateDef));
//Class Created for passing the Account Update Information for Use by update function
public class AccountUpdateInfo
{
[BsonElement("AccountId")]
public int AccountId { get; set; }
[BsonElement("Update")]
public UpdateDefinition<Account> UpdateDef { get; set; }
}
var acct = new AccountUpdateInfo();
acctInfo.UpdateDef = updateDef
//This also logs a null value for the Update Definition field when the class is serialized.
_logger.LogInformation("{0} - AccountUpdateInfo: {1}", actionName, JsonConvert.SerializeObject(acct));
Any thoughts or ideas on what is happening? I am stumped on why I cannot serialize for logging or pass the value in a class around like I would expect
give this a try:
var json = updateDef.Render(
BsonSerializer.SerializerRegistry.GetSerializer<Account>(),
BsonSerializer.SerializerRegistry)
.AsBsonDocument
.ToString();
and to turn a json string back to an update definition (using implicit operator), you can do:
UpdateDefinition<Account> updateDef = json;
this is off the top of my head and untested. the only thing i'm unsure of (without an IDE) is the .Document.ToString() part above.

Entity Framework include only returning part of the database data

I am very new to asp.net and C# so bear with me. I am trying to return data from a database using the entity framework .include() method so that I can get the foreign key information from another table. However, what is being returned is only part of the data. It seems to be cut off before everything is returned.
"[{"id":11,"name":"Mr. Not-so-Nice","heroType":3,"heroTypeNavigation":{"id":3,"type":"Villian","heroes":["
Which gives me the error: SyntaxError: Unexpected end of JSON input.
Please seem below for the model classes and the GET section of the controller where this is being returned. If I remove the "include()" method it returns all the heroes from the main table just fine.
public partial class Hero
{
public int Id { get; set; }
public string Name { get; set; }
public int? HeroType { get; set; }
public virtual HeroTypes HeroTypeNavigation { get; set; }
}
{
public partial class HeroTypes
{
public HeroTypes()
{
Heroes = new HashSet<Hero>();
}
public int Id { get; set; }
public string Type { get; set; }
public virtual ICollection<Hero> Heroes { get; set; }
}
// GET: api/Heroes
[HttpGet]
public async Task<ActionResult<IEnumerable<Hero>>> GetHeroesTable()
{
return await _context.HeroesTable.Include(hero => hero.HeroTypeNavigation).ToListAsync();
}
Serializer recursion rules will be tripping this up. Basically as jonsca mentions, you have a circular reference between hero, and hero type. The serializer will start with the hero, then go to serialize the hero type which it will find the Hero's collection and expect to serialize, which each would reference a hero type, with collections of Heros.. The serializer bails when it sees this.
I would recommend avoiding passing back Entity classes to your view to avoid issues with EF and lazy loading. Serialization will iterate over properties, and this will trigger lazy loads. To avoid this, construct a view model for the details your view needs, flatten as necessary.
For example if you want to display a list of Heroes with their Type:
public class HeroViewModel
{
public int HeroId { get; set; }
public string Name { get; set; }
public string HeroType { get; set; }
}
to load:
var heroes = await _context.HeroesTable.Select(x => new HeroViewModel
{
HeroId = x.HeroId,
Name = x.Name,
HeroType = x.HeroType.Type
}).ToListAsync();
You can utilize Automapper for example to help translate entities to view models without that explicit code using ProjectTo<TEntity> which can work with EF's IQueryable implementation.
With larger realistic domains your client likely won't need everything in the object graph.
You won't expose more information than you need to. (I.e. visible via debugging tools)
You'll get a performance boost from not loading the entire graph or triggering
lazy load calls, and it's less data across the wire.
The last point is a rather important one as with complex object graphs, SQL can do a lot of the lifting resulting in a much more efficient query than loading "everything". Lazy hits to the database can easily add several seconds to each and every call from a client, and loading large graphs has a memory implication on the servers as well.

Entity Framework Code First using virtual property internally, null reference when called

I am completely new to Entity Framework. I have created a model to watch services on remote machines. Currently it looks like this:
Note: I have done my best to use understandable generic names since this is from a corporate environment.
public class RemoteService
{
[Key, Column(Order = 0)]
public string Name { get; set; }
[Key, Column(Order = 1)]
public string MachineName { get; set; }
[ForeignKey("MachineName")]
public virtual RemoteEnvironment RunningAt { get; set; }
}
public class RemoteEnvironment
{
[Key]
public string MachineName { get; set; }
public string Configuration { get; set; }
public string EnvironmentNr { get; set; }
public virtual ICollection<RemoteService> Services { get; set; }
}
With context class:
public class MyDBContext : DbContext
{
public DbSet<RemoteEnvironment> RemoteEnvironments { get; set; }
public DbSet<RemoteService> RemoteServices { get; set; }
}
This model works fine when creating the database and seeding it. The RemoteService class also contains this Version property, to get the version from the Octopus Deploy API.
private string _version = "N/A";
public string Version
{
get
{
var item = GetItemFromOctopus(Name, RunningAt.EnvironmentNr);
if (item != null)
{
_version = item.ReleaseVersion;
}
return _version;
}
set
{
}
}
This also works fine when creating and seeding the database. My issue appears when I try to grab data from the database. Something like this:
using (MyDBContext context = new MyDBContext())
{
var serviceNames = new List<string>();
foreach (RemoteService service in context.RemoteServices)
{
serviceNames.Add(service.Name);
}
}
While executing the above code I get a NullReferenceException, because RunningAt is null.
From debugging I have gathered that it is when I grab service from context.RemoteServices that it fetches all the properties. First Name, then MachineName, skips RunningAt (why? Lazy Loading?) and then tries to get Version, but since RunningAt is null, it can't.
I have tried defining the RunningAt get and set methods using
private RemoteEnvironment _runningAt
but the issue stays the same, both RunningAt and _runningAt are null while fetching data from the database.
It seems that I somehow need to load the reference to the RemoteEnvironment from the database in the get of Version. But creating a context here seems a bit much.
What else can I do? I would really like to keep Version as is. If it can't be done, I will have to make a workaround. Checking version separately, actively getting and setting it instead.
Edit 1:
I have now tried three different ways to "pre-load" the RemoteEnvironments.
First:
"Pre-loading" by getting all RemoteEnvironments from database.
using (MyDBContext context = new MyDBContext())
{
var serviceNames = new List<string>();
List<M3BEnvironment> activateEnv = db.M3BEnvvironments.ToList(); //added line
foreach (RemoteService service in context.RemoteServices)
{
serviceNames.Add(service.Name);
}
}
Still getting NullReferenceException since RunningAt is null.
Second:
Removing the virtual keyword.
[ForeignKey("MachineName")]
public RemoteEnvironment RunningAt { get; set; } // virtual keyword removed
Still getting NullReferenceException since RunningAt is null.
Third:
Using Include to force eagerly loading.
using (MyDBContext context = new MyDBContext())
{
var serviceNames = new List<string>();
var services = context.RemoteServices.Include(s => s.RunningAt).ToList();
foreach (RemoteService service in services)
{
serviceNames.Add(service.Name);
}
}
Still getting NullReferenceException since RunningAt is null.
I have even tried all combinations of the above, including all three at the same time. Still getting NullReferenceException since RunningAt is null.
I also tried going the other way, through RemoteEnvironments and including Services:
using (MyDBContext context = new MyDBContext())
{
var environments = context.RemoteEnvironments.Include(env => env.Services).ToList(); // NullRefereceException happens at this call
var serviceNames = new List<string>();
var services = context.RemoteServices.Include(s => s.RunningAt).ToList();
foreach (RemoteService service in services)
{
serviceNames.Add(service.Name);
}
}
Still getting NullReferenceException since RunningAt is null. Only this time it happens while the environments call is trying to include them.
What I want to do does not seem possible. Using the referenced RunningAt property within the Version property, because it is never loaded from the database. Not even when I force it to load the RemoteEnvironments before the call to the RemoteService and subsequently the call to Version.
What I am trying to do does not seem possible. The core issue is having a navigation property and then referencing it in another property's get or set method. This does not seem to work. I have tried turning off lazy loading, forcing loading with Include and also pre-loading the DbSet which the navigation property points to. In each and every case the navigation property reference, in my case RunningAt, has been null inside the other property's get/set method.
The Entity Framework does not seem to support referencing a navigation property inside another property. If anyone knows otherwise please tell me how. In the mean time, this will be the accepted answer.

Dealing with Object Graphs - Web API

I recently encountered a (hopefully) small issue when toying around with a Web API project that involves returning object graphs so that they can be read as JSON.
Example of Task Object (generated through EF) :
//A Task Object (Parent) can consist of many Activities (Child Objects)
public partial class Task
{
public Task()
{
this.Activities = new HashSet<Activity>();
}
public int TaskId { get; set; }
public string TaskSummary { get; set; }
public string TaskDetail { get; set; }
public virtual ICollection<Activity> Activities { get; set; }
}
within my ApiController, I am requested a specific Task (by Id) along with all of it's associated Activities, via:
Example of Single Task Request
//Simple example of pulling an object along with the associated activities.
return repository.Single(t => t.Id == id).Include("Activities");
Everything appears to be working fine - however when I attempt to navigate to a URL to access this, such as /api/tasks/1, the method executes as it should, but no object is returned (just a simple cannot find that page).
If I request an Task that contains no activities - everything works as expected and it returns the proper JSON object with Activities : [].
I'm sure there are many way to tackle this issue - I just thought I would get some insight as to what people consider the best method of handling this.
Considered Methods (so far):
Using an alternative JSON Parser (such as Newtonsoft.JSON) which fixed the issue but appended $id and $refs throughout the return data, which could make parsing for Knockout difficult I believe.
Using projection and leveraging anonymous types to return the data. (Untested so far)
Removing the Include entirely and simply accessing the Child Data through another request.
Any and all suggestions would be greatly appreciated.
I had a similar issue with EF types and Web API recently. Depending on how your generated EF models are setup, the navigation properties may result in circular dependencies. So if your generated Activity class has a Task reference the serializer will try to walk the object graph and get thrown in a little nasty cycle.
One solution would be to create a simple view model to get the serializer working
public class TaskViewModel {
public TaskViewModel ()
{
this.Activities = new List<ActivityViewModel>();
}
public int TaskId { get; set; }
public string TaskSummary { get; set; }
public string TaskDetail { get; set; }
public virtual IList<ActivityViewModel> Activities { get; set; }
}
public class ActivityViewModel{
public ActivityViewModel()
{
}
//Activity stuff goes here
//No reference to Tasks here!!
}
Depending on what you're doing, you may even be able to create a flatter model than this but removing the Task reference will help the serialization. That's probably why it worked when Activities was empty

Categories