2

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?

SamTech
  • 1,305
  • 2
  • 12
  • 22
  • Try db.Configuration.ProxyCreationEnabled = false; – VahidN Feb 27 '14 at 20:06
  • Could it be a problem with an `Author` having a collection of `Author_Book` and each `Author_Book` also referencing the `Author`? I don´t know how that redis client works, but it could be trying to serialize the object graph and then hitting the stack overflow exception. – Daniel J.G. Feb 27 '14 at 21:02
  • @Daniel i also think that it could be due to that circular reference. But it is generated from EF database-first. If i remove circular reference, EF starts complaining. – SamTech Feb 28 '14 at 03:02
  • possible duplicate of [Preventing StackOverflowException while serializing EF object graph into Json](http://stackoverflow.com/questions/9150920/preventing-stackoverflowexception-while-serializing-ef-object-graph-into-json) – Ofer Zelig Feb 28 '14 at 03:32

1 Answers1

1

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.

SamTech
  • 1,305
  • 2
  • 12
  • 22