Adding Entity with child entities to Redis server resulting System.StackOverflowException - c#

I am working on ASP.NET MVC project with EF6 with Database First. I am trying to use Redis server to cache frequently used objects.
But i am getting problem in saving related entities (parent-child). For example following Author and Author_Book classes are parent-child and referencing to each other (Foreign-Key constraint in RDBMS)
public partial class Author
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Author_Book> Author_Book { get; set; }
}
public partial class Author_Book
{
public int Id { get; set; }
public int AuthorId { get; set; }
public string Title { get; set; }
public virtual Author Author { get; set; }
}
public partial class Customer
{
public int ID { get; set; }
public string CustomerName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
}
Querying and trying to store query result to Redis server as below
using (EFTestContext db = new EFTestContext())
{
var data = db.Authors.ToList();
redisClient.Set<List<Author>>("author", data);
}
Above line redisClient.Set.. resulting following Exception
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
However if i store Customer entity (which doesn't have child entities) into Redis server it work fine
using (EFTestContext db = new EFTestContext())
{
var customers = db.Customers.ToList();
redisClient.Set<List<Customer>>("customers", customers);
}
So my question is how to store complete entity (with childs) into redis server?

I followed [DataContract] / [DataMember] approach (As #oferzelig mentioned in comment). It is working fine and no longer raising that exception. I am describing it here so it could help someone else.
EF Database-first by default does not add [DataContract] / [DataMember] attributes, we need to modify T4 template for it. I did following modifications in Model template Model1.tt.
Added [DataContract] attribute before the line
<#=codeStringGenerator.EntityClassOpening(entity)#>
now it looks like
[DataContract]
<#=codeStringGenerator.EntityClassOpening(entity)#>
Added [DataMember] attribute before line
<#=codeStringGenerator.Property(edmProperty)#>
and it looks like
[DataMember]
<#=codeStringGenerator.Property(edmProperty)#>
We also need to generate [DataMember] attribute for one-to-many relationships (e.g. public virtual ICollection<Author_Book> Author_Book { get; set; } in question) but NOT for one-to-one (e.g. public virtual Author Author { get; set; } in question). To achieve it i added a new function in CodeStringGenerator class
public string NavigationProperty_NeedDataMember(NavigationProperty navProp)
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}",
navProp.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ? ("[DataMember]") : ""
);
}
and called it just before the line
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
as below
<#=codeStringGenerator.NavigationProperty_NeedDataMember(navigationProperty)#>
<#=codeStringGenerator.NavigationProperty(navigationProperty)#>
And finally modified UsingDirectives procedure to add System.Runtime.Serialization, as below
public string UsingDirectives(bool inHeader, bool includeCollections = true)
{
return inHeader == string.IsNullOrEmpty(_code.VsNamespaceSuggestion())
? string.Format(
CultureInfo.InvariantCulture,
"{0}using System;{1}" +
"{2}" +
"{3}",
inHeader ? Environment.NewLine : "",
includeCollections ? (Environment.NewLine + "using System.Collections.Generic;") : "",
includeCollections ? (Environment.NewLine + "using System.Runtime.Serialization;") : "",
inHeader ? "" : Environment.NewLine)
: "";
}
That all.
Now it is generating following classes and i no need to edit classes manually after each update.
[DataContract]
public partial class Author
{
[DataMember]
public int Id { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public virtual ICollection<Author_Book> Author_Book { get; set; }
}
[DataContract]
public partial class Author_Book
{
[DataMember]
public int Id { get; set; }
[DataMember]
public int AuthorId { get; set; }
[DataMember]
public string Title { get; set; }
public virtual Author Author { get; set; }
}
Hope it will help someone else.

Related

EntityFrameworkCore using wrong column in query

I am trying to write my first web api with .net core. I am using VS2017 and core 1.1. I've got everything working except for one of my objects (I've tried it with that last line commented and uncommented...it makes no difference):
public class Tag
{
public int ID { get; set; }
public string Name { get; set; }
public bool ShowInFilter { get; set; }
public ICollection<SubscriberTag> SubscriberTags { get; set; }
}
My repository code looks like this:
private SubscriptionContext db;
public TagRepository(SubscriptionContext context) { db = context; }
public Tag Find(int key) => db.Tags.SingleOrDefault(a => a.ID == key);
That is being called from my TagController:
private iTagRepository TagItems { get; set; }
public TagController(iTagRepository tagItems) {TagItems = tagItems; }
[HttpGet("{id}", Name = "GetTag")]
public IActionResult Get(int id) { return new ObjectResult( TagItems.Find(id) ); }
The problem is when I run it, the query that is executed is:
exec sp_executesql
N'SELECT TOP(2) [a].[ID], [a].[Name],
[a].[ShowInFilter], [a].[SubscriberID]
FROM [Tags] AS [a]
WHERE [a].[ID] = #__key_0',N'#__key_0 int',#__key_0=1
which throw and error because Tags doesn't contain a column called SubscriberID.
I've searched all my code and SubscriberID only shows up two places (in other classes which are not being used here). I have no partial classes in my entire project (saw that was an issue on a related question.)
Why is EF adding this column to its query and how do I fix it?
As requested here is the class that contains subscriberID:
public class SubscriberTag
{
public long ID { get; set; }
public long subscriberID { get; set; }
public int tagID { get; set; }
public Subscriber Subscriber { get; set; }
public Tag Tag { get; set; }
}
Subscriber class (lots of irrelevant properties removed):
public class Subscriber
{
public Subscriber()
{
//a few value initalizers/defaults
}
public long ID { get; set; }
[StringLength(200)]
public string FirstName { get; set; }
//.......
public ICollection<Subscribers.Models.Subscription> Subscriptions { get; set; }
public ICollection<Subscribers.Models.Tag> Tags { get; set; }
}
Because of the Property Tags on Subscriber:
public ICollection<Subscribers.Models.Tag> Tags { get; set; }
Entity Framework expects the Tag object to have a foreign-key to Subscriber so it builds a query with it. It looks like to configure the many-to-many relationship needs to change the property to:
public ICollection<Subscribers.Models.SubscriberTag> SubscriberTag{ get; set; }
Configuring a Many-to-Many Relationship.
Thanks for the insight Ivan

Converting infinitely nested objects in .NET Core

EDIT: I originally worded this question very poorly, stating the problem was with JSON serialization. The problem actually happens when I'm converting from my base classes to my returned models using my custom mappings. I apologize for the confusion. :(
I'm using .NET Core 1.1.0, EF Core 1.1.0. I'm querying an interest and want to get its category from my DB. EF is querying the DB properly, no problems there. The issue is that the returned category has a collection with one interest, which has one parent category, which has a collection with one interest, etc. When I attempt to convert this from the base class to my return model, I'm getting a stack overflow because it's attempting to convert the infinite loop of objects. The only way I can get around this is to set that collection to null before I serialize the category.
Interest/category is an example, but this is happening with ALL of the entities I query. Some of them get very messy with the loops to set the relevant properties to null, such as posts/comments.
What is the best way to address this? Right now I'm using custom mappings that I wrote to convert between base classes and the returned models, but I'm open to using any other tools that may be helpful. (I know my custom mappings are the reason for the stack overflow, but surely there must be a more graceful way of handling this than setting everything to null before projecting from base class to model.)
Classes:
public class InterestCategory
{
public long Id { get; set; }
public string Name { get; set; }
public ICollection<Interest> Interests { get; set; }
}
public class Interest
{
public long Id { get; set; }
public string Name { get; set; }
public long InterestCategoryId { get; set; }
public InterestCategory InterestCategory { get; set; }
}
Models:
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public long? InterestCategoryId { get; set; }
}
Mapping functions:
public static InterestCategoryModel ToModel(this InterestCategory category)
{
var m = new InterestCategoryModel
{
Name = category.Name,
Description = category.Description
};
if (category.Interests != null)
m.Interests = category.Interests.Select(i => i.ToModel()).ToList();
return m;
}
public static InterestModel ToModel(this Interest interest)
{
var m = new InterestModel
{
Name = interest.Name,
Description = interest.Description
};
if (interest.InterestCategory != null)
m.InterestCategory = interest.InterestCategory.ToModel();
return m;
}
This is returned by the query. (Sorry, needed to censor some things.)
This is not .NET Core related! JSON.NET is doing the serialization.
To disable it globally, just add this during configuration in Startup
services.AddMvc()
.AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
}));
edit:
Is it an option to remove the circular references form the model and have 2 distinct pair of models, depending on whether you want to show categories or interests?
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
public List<InterestModel> Interests { get; set; }
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
public class InterestModel
{
public long Id { get; set; }
public string Name { get; set; }
public InterestCategoryModel InterestCategory { get; set; }
public class InterestCategoryModel
{
public long Id { get; set; }
public string Name { get; set; }
}
}
Note that each of the models has a nested class for it's child objects, but they have their back references removed, so there would be no infinite reference during deserialization?

Reusing C# class in MVC C#

I have an application that can contain a minimum of one "person" up to a maximum of fours "persons". I am using view models and manually mapping in the controller to the domain model.
I am completely lost as to how to include more than one "person" in the app. I've read up on using for but can't wrap my head around it.
Right now, I am just adding the data from the "person" class manually such that
Code:
public class SomeClass
{
public Guid SomeClassId {get; set;}
public string BorrowerFirst { get; set; }
public string BorrowerMI { get; set; }
public string BorrowerLast { get; set; }
public Suffix? BorrowerSuffix { get; set; }
... some more fields ...
}
and so on in the master class.
What I'd like to do is use a class such as:
Code:
public class Applicant
{
public string BorrowerFirst { get; set; }
public string BorrowerMI { get; set; }
public string BorrowerLast { get; set; }
public Suffix? BorrowerSuffix { get; set; }
}
can be reused in another class multiple times.
How can I separate that to strip that and instead use a named class consisting of first, middle and last names and allowing up to four "person" instances in my master class?
Have you tried inheritance?
public class Person
{
public Guid SomeClassId {get; set;}
public string BorrowerFirst { get; set; }
public string BorrowerMI { get; set; }
public string BorrowerLast { get; set; }
public Suffix? BorrowerSuffix { get; set; }
}
and the Applicant class:
public class Applicant : Person
{
//Only extra properties and methods here.
public string FullName
{
get
{
return this.BorrowerFirst + " " + this.BorrowerMI + " " + this.BorrowerLast;
}
}
}
You can then have a vendor as well:
public class Vendor: Person
{
//Only extra properties and methods here.
}

How to Add Attributes to .cs file generated by Entity Framework

I am creating a ASP.net MVC web app and trying to add validation attributes to a ClientsTbl.cs file I have generated from a SQL server using entity framework. I have done scaffolding and created the view and the controller for the Table(Model), but for some reason the scaffold did not recognize the primary key column as a primary key. On top of that it will not let me add attributes in []. I can't even change the field description that appears above the views form text box. How do I do the above in the following class?
namespace Testit.Models
{
using System;
using System.Collections.Generic;
public partial class ClientsTbl
{
public ClientsTbl()
{
this.ProgramClientTbls = new HashSet<ProgramClientTbl>();
}
public int id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int CenterId { get; set; }
public string MiddleName1 { get; set; }
public string MiddleName2 { get; set; }
public System.DateTime DateAdded { get; set; }
public virtual CenterTbl CenterTbl { get; set; }
public virtual ICollection<ProgramClientTbl> ProgramClientTbls { get; set; }
}
}
You can create a metadata class to do that. For example, if Id, FirstName, and LastName are required fields, you can create a new class like below.
public class ClientsTblMetadata
{
[Required()]
public int Id { get; set; }
[Required()]
public string FirstName { get; set; }
[Required()]
public string LastName { get; set; }
}
Then you need to add a new partial class with a MetadataType attribute. Please make sure that this class is located under the same namespace, otherwise it won't work.
namespace Testit.Models
{
[MetadataType(typeof(ClientsTblMetadata))] // you need this line of code.
public partial class ClientsTbl

Is it possible to associate a POCO entity with a standard entity?

Let's say I have to entities a POCO entity named SalesOrders and a regular entity generated from a SQL database called SalesOrderLines. I would like to create an association from SalesOrders to SalesOrderLines as in the following code but I keep getting the exception below the code. Does anyone know if this is possible?
[DataServiceKey("SalesOrderNumber")]
public class SalesOrder
{
[Key]
public int SalesOrderNumber { get; set; }
public string StockCode { get; set; }
public string Description { get; set; }
[Include]
[Association("SalesOrder_SalesOrderLine", "SalesOrderNumber", "SalesOrderNumber")]
public IQueryable<SalesOrderLine> SalesOrderLines
{
get
{
SalesOrderLineEntities oSalesOrderLineEntities = new SalesOrderLineEntities();
var soLines = from line in oSalesOrderLineEntities.SalesOrderLines
where line.OrderNumber == SalesOrderNumber.ToString() &&
line.StockCode == StockCode
select line;
return soLines;
}
set
{
}
}
}
'The property 'SalesOrderLines' on type 'ServiceName.NameSpace.SalesOrder' is not a valid property. Properties whose types are collection of primitives or complex types are not supported.'
It turns out that all I needed to to do was add the [Key] attribute to the generated metadata class.
[MetadataTypeAttribute(typeof(SalesOrderLines.SalesOrderLinesMetadata))]
public partial class SalesOrderLines
{
internal sealed class SalesOrderLinesMetadata
{
private SalesOrderLinesMetadata()
{
}
public DateTime DateStamp { get; set; }
public string OrderNumber { get; set; }
[Key]
public int SalesOrderLineID { get; set; }
public string SerialNumber { get; set; }
public string StockCode { get; set; }
}
}

Categories