I'm trying to map from Role to RoleModel including the Feature/FeatureModel, but I haven't been able to make it work properly.
It returns the role but does not include the features:
{
"name": "Backend",
"description": "Backend role",
"roleFeatures": [],
"roleUsers": [],
"updatedDate": "2023-02-03T16:14:54.036441",
"deletedDate": null,
"id": "82a443bd-81d3-4460-8a67-08db0601365e",
"createdDate": "2023-02-03T16:14:54.036441",
"deleted": false
}
This is the map I created:
CreateMap<Role, RoleModel>()
.ForMember(rm => rm.RoleFeatures, opt => opt
.MapFrom(r => r.RoleFeatures.Select(y => y.Feature).ToList()))
.MaxDepth(1);
These are the classes:
Role
public class Role : BaseRecord
{
public string Name { get; set; }
public string Description { get; set; }
public ICollection<RoleFeature> RoleFeatures { get; set; }
public ICollection<RoleUser> RoleUsers { get; set; }
}
RoleModel
public class RoleModel : BaseRecordModel
{
public string Name { get; set; }
public string Description { get; set; }
public ICollection<RoleFeatureModel> RoleFeatures { get; set; }
public ICollection<RoleUserModel> RoleUsers { get; set; }
}
RoleFeature
public class RoleFeature
{
public bool Read { get; set; }
public bool Write { get; set; }
public bool Edit { get; set; }
public bool Delete { get; set; }
public Guid RoleId { get; set; }
public Role Role { get; set; }
public Guid FeatureId { get; set; }
public Feature Feature { get; set; }
}
RoleFeatureModel
public class RoleFeatureModel
{
public Guid RoleId { get; set; }
public Guid FeatureId { get; set; }
public bool Read { get; set; }
public bool Write { get; set; }
public bool Edit { get; set; }
public bool Delete { get; set; }
public RoleModel Role { get; set; }
public FeatureModel Feature { get; set; }
}
Previously I was creating a simple map:
CreateMap<Role, RoleModel>();
But I was getting an object cycle error, now I get the roleModel but the roleFeature list is empty.
I'm gonna listen to what my guts tell me - you are querying for the roles in lazy loading manner (default), aren't you?
You have to include related members explicitly, for example:
var resultSet = await _context.Roles.Include(r => r.RoleFeatures).ToListAsync();
Include() method enforces so called eager loading, providing you not only with requested data, but also its related members.
It also can be chained e.g.:
var resultSet = _context.Roles.Include(r => r.RoleFeatures)
.Include(r => r.RoleUsers)
.ToListAsync();
OR
If you are already using eager loading.
Instead of constraining your mapper with MaxDepth(1), go to your ConfigureServices() (in your Startup.cs or Program.cs) method, and configure JsonSerializer in a way that suits you the best, for example:
services.AddNewtonsoftJson(options =>
{
options.SerializerSettings.NullValueHandling = Newtonsoft.Json.NullValueHandling.Ignore;
options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented;
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize;
options.SerializerSettings.PreserveReferencesHandling = PreserveReferencesHandling.All;
});
Above example uses Newtonsoft.Json, but you can achieve the same effect reffering ReferenceLoopHandling with System.Text.Json serializer.
Related
In my web API when I run project to get data from the database got this error
.net core 3.1
JsonException: A possible object cycle was detected which is not supported. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32.
These are my codes:
my Model
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
my productCategory class is:
public class ProductCategory
{
public int Id { get; set; }
public string Name { get; set; }
public string CatText { get; set; }
public string ImagePath { get; set; }
public int Priority { get; set; }
public int Viewd { get; set; }
public string Description { get; set; }
public bool Active { get; set; }
public DateTime CreateDate { get; set; }
public DateTime ModifyDate { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
my repo is
public async Task<IList<Product>> GetAllProductAsync()
{
return await _context.Products.Include(p => p.ProductCategory).ToListAsync();
}
my interface
public interface IProductRepository
{
...
Task<IList<Product>> GetAllProductAsync();
...
}
and this is my controller in api project
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProductRepository _productRepository;
public ProductsController(IProductRepository productRepository)
{
_productRepository = productRepository;
}
[HttpGet]
public ActionResult Get()
{
return Ok(_productRepository.GetAllProduct());
}
}
When I run API project and put this URL: https://localhost:44397/api/products
I got that error,
I can't resolve it
this is happening because your data have a reference loop.
e.g
// this example creates a reference loop
var p = new Product()
{
ProductCategory = new ProductCategory()
{ products = new List<Product>() }
};
p.ProductCategory.products.Add(p); // <- this create the loop
var x = JsonSerializer.Serialize(p); // A possible object cycle was detected ...
You can not handle the reference loop situation in the new System.Text.Json yet (netcore 3.1.1) unless you completely ignore a reference and its not a good idea always. (using [JsonIgnore] attribute)
but you have two options to fix this.
you can use Newtonsoft.Json in your project instead of System.Text.Json (i linked an article for you)
Download the System.Text.Json preview package version 5.0.0-alpha.1.20071.1 from dotnet5 gallery (through Visual Studio's NuGet client):
option 1 usage:
services.AddMvc()
.AddNewtonsoftJson(
options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
// if you not using .AddMvc use these methods instead
//services.AddControllers().AddNewtonsoftJson(...);
//services.AddControllersWithViews().AddNewtonsoftJson(...);
//services.AddRazorPages().AddNewtonsoftJson(...);
option 2 usage:
// for manual serializer
var options = new JsonSerializerOptions
{
ReferenceHandling = ReferenceHandling.Preserve
};
string json = JsonSerializer.Serialize(objectWithLoops, options);
// -----------------------------------------
// for asp.net core 3.1 (globaly)
services.AddMvc()
.AddJsonOptions(o => {
o.JsonSerializerOptions
.ReferenceHandling = ReferenceHandling.Preserve
});
these serializers have ReferenceLoopHandling feature.
Edit : ReferenceHandling changed to ReferenceHandler in DotNet 5
but if you decide to just ignore one reference use [JsonIgnore] on one of these properties. but it causes null result on your API response for that field even when you don't have a reference loop.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
public int ProductCategoryId { get; set; }
// [JsonIgnore] HERE or
public virtual ProductCategory ProductCategory { get; set; }
}
public class ProductCategory
{
public int Id { get; set; }
// [JsonIgnore] or HERE
public ICollection<Product> products {get;set;}
}
.NET 5 Web API
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
services.AddControllers()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
}
I have the same issue, my fix was to add async and await keyword since I am calling an async method on my business logic.
Here is my original code:
[HttpGet]
public IActionResult Get()
{
//This is async method and I am not using await and async feature .NET which triggers the error
var results = _repository.GetAllDataAsync();
return Ok(results);
}
To this one:
HttpGet]
public async Task<IActionResult> Get()
{
var results = await _repository.GetAllDataAsync();
return Ok(results);
}
In .Net 6, you can use System.Text.Json to initialize a startup action with AddControllersWithViews like this in Program.cs,
using System.Text.Json.Serialization;
builder.Services.AddControllersWithViews()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
also you can use AddMvc like this,
builder.Services.AddMvc()
.AddJsonOptions(x => x.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.IgnoreCycles);
but quote from Ryan
asp.net core 3.0+ template use these new
methodsAddControllersWithViews,AddRazorPages,AddControllers instead of
AddMvc.
I will recommend to use the first solution.
Ensure you have [JsonIgnore] on the correct fields to avoid a circular reference.
In this case you will need
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string ProductText { get; set; }
[JsonIgnore]
public virtual ProductCategory ProductCategory { get; set; }
}
You probably don't need the ProductCategoryId field (depends if you are using EF and code first to define your DB)
Edit - In answer to noruk
There is often confusion in connected objects and navigation properties. You can get the data you want in JSON but also define the EF structures to get the correct DB structure (foreign keys, indexes, etc).
Take this simple example. A Product (for example a T-Shirt) has many sizes or SKUs (e.g. Small, Large, etc)
public class Product
{
[Key]
[MaxLength(50)]
public string Style { get; set; }
[MaxLength(255)]
public string Description { get; set; }
public List<Sku> Skus { get; set; }
}
public class Sku
{
[Key]
[MaxLength(50)]
public string Sku { get; set; }
[MaxLength(50)]
public string Barcode { get; set; }
public string Size { get; set; }
public decimal Price { get; set; }
// One to Many for Product
[JsonIgnore]
public Product Product { get; set; }
}
Here you can serialise a Product and the JSON data will include the SKUs. This is the normal way of doing things.
However if you serialise a SKU you will NOT get it's parent product. Including the navigation property will send you into the dreaded loop and throw the "object cycle was detected" error.
I know this is limiting in some use cases but I would suggest you follow this pattern and if you want the parent object available you fetch it separately based on the child.
var parent = dbContext.SKUs.Include(p => p.Product).First(s => s.Sku == "MY SKU").Product
I fixed my API Core Net6.0 adding [JsonIgnore]:
public class SubCategoryDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Image { get; set; }
public int CategoryId { get; set; }
[JsonIgnore]
public Category Category { get; set; }
}
For net core 3.1 you have to add in Startup.cs:
services.AddMvc.AddJsonOptions(o => {
o.JsonSerializerOptions.ReferenceHandler = ReferenceHandler.Preserve;
o.JsonSerializerOptions.MaxDepth = 0;
})
and import at least this package using nuget.org include prerelease:
<PackageReference Include="System.Text.Json" Version="5.0.0-rc.1.20451.14" />
following code is working for me in dotnet 5.0 :
services.AddControllersWithViews()
.AddJsonOptions(o => o.JsonSerializerOptions
.ReferenceHandler = ReferenceHandler.Preserve);
Finally fixed mine with System.Text.Json not NewtonSoft.Json using
var options = new JsonSerializerOptions()
{
MaxDepth = 0,
IgnoreNullValues = true,
IgnoreReadOnlyProperties = true
};
Using options to serialize
objstr = JsonSerializer.Serialize(obj,options);
My project built with a similar error.
Here's the code before
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
public PrimaryClass primaryClass { get; set; }
}
I took away the PrimaryClass object from the DependentClass model.
Code after
public class PrimaryClass {
public int PrimaryClassId
public ICollection<DependentClass> DependentClasses { get; set; }
}
public class DependentClass {
public int DependentClassId { get; set; }
public int PrimaryClassId { get; set; }
}
I also had to adjust the OnModelCreating method from
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses).WithOne(d => d.primaryClass).HasForeignKey(d => d.PrimaryClassId);
to
modelBuilder.Entity<PrimaryClass>().HasMany(p => p.DependentClasses);
The DbSet query that's running is
public async Task<List<DependentClass>> GetPrimaryClassDependentClasses(PrimaryClass p)
{
return await _dbContext.DependentClass.Where(dep => dep.PrimaryClassId == p.PrimaryClassId).ToListAsync();
}
The error could have been with any of these 3 sections of code, but removing the primary object reference from the dependent class and adjusting the OnModelCreating resolved the error, I'm just not sure why that would cause a cycle.
In my case the problem was when creating the entity relationships. I linked the main entity using a foreign key inside the dependent entity like this
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
also I referred the dipendend entity inside the main entity as well.
public List<Device> devices { get; set; }
which created a cycle.
Dependent Entity
public class Device
{
[Key]
public int id { get; set; }
public int asset_number { get; set; }
public string brand { get; set; }
public string model_name { get; set; }
public string model_no { get; set; }
public string serial_no { get; set; }
public string os { get; set; }
public string os_version { get; set; }
public string note { get; set; }
public bool shared { get; set; }
public int week_limit { get; set; }
public bool auto_acceptance { get; set; }
public bool booking_availability { get; set; }
public bool hide_device { get; set; }
public bool last_booked_id { get; set; }
//getting the relationships category 1 to many
public int category_id { get; set; }
[ForeignKey("category_id")]
public Device_Category Device_Category { get; set; }
public List<Booking> Bookings { get; set; }
}
Main Entity
public class Device_Category
{
public int id { get; set; }
public string name { get; set; }
public List<Device> devices { get; set; }
}
}
So I commented the
public List<Device> devices { get; set; }
inside main entity (Device_Category) and problem solved
I am following Microsoft's many-to-many ef core example at
https://learn.microsoft.com/en-us/ef/core/modeling/relationships#many-to-many
But get a self referencing loop error.
Here are my entities:
public class Card
{
public Guid Id { get; set; }
public string CardNumber { get; set; }
public CardType CardType { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int PassCode { get; set; }
public List<CardSet> CardSets { get; set; }
public Card()
{
CardSets = new List<CardSet>();
}
}
public class Set
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<CardSet> CardSets { get; set; }
public Set()
{
CardSets = new List<CardSet>();
}
}
// join entity
public class CardSet
{
public Guid SetId { get; set; }
public Set Set { get; set; }
public Guid CardId { get; set; }
public Card Card { get; set; }
}
Here is my OnModelCreating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CardSet>().HasKey(cs => new {cs.CardId, cs.SetId});
modelBuilder.Entity<CardSet>()
.HasOne(cs => cs.Card)
.WithMany(c => c.CardSets)
.HasForeignKey(cs => cs.CardId);
modelBuilder.Entity<CardSet>()
.HasOne(cs => cs.Set)
.WithMany(s => s.CardSets)
.HasForeignKey(cs => cs.SetId);
}
Here is the call to get the Set with its Cards:
public Set GetSetWithCards(Guid setId)
{
return context
.Sets
.Include(s => s.CardSets)
.ThenInclude(cs => cs.Card)
.FirstOrDefault(s => s.Id == setId);
}
The error:
Newtonsoft.Json.JsonSerializationException: Self referencing loop
detected for property 'set' with type
'Tools.Entities.Set'. Path 'cardSets[0]'.
All of your entity configurations are correct, and, based on the error message, it appears that the issue is happening when you try to serialize the resulting data to JSON.
Check out this answer for details: JSON.NET Error Self referencing loop detected for type
By some reason EF wont load the included list properly so it ends up being null all the time.
Here is the entities i'm using:
[Table("searchprofilepush")]
public class SearchProfilePush
{
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
public SearchProfilePush SearchProfilePush { get; set; }
}
Then when i'm trying to do this:
var searchProfilePush = _dataContext.SearchProfilePush.Include(w => w.SearchProfileMediaTypePush).FirstOrDefault(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId);
My included list is always null.
I guess it's some obvious reason why this doesn't work but i just can't figure it out.
Thanks!
EDIT:
Here is the sql query:
SELECT \"Extent1\".\"id\", \"Extent1\".\"accountid\", \"Extent1\".\"push\", \"Extent1\".\"userpushid\", \"Extent1\".\"searchprofileid\" FROM \"public\".\"searchprofilepush\" AS \"Extent1\" WHERE \"Extent1\".\"accountid\" = #p__linq__0 AND #p__linq__0 IS NOT NULL AND (\"Extent1\".\"searchprofileid\" = #p__linq__1 AND #p__linq__1 IS NOT NULL) LIMIT 1
EDIT 2:
I have now mapped my entities both way and the list is still always null.
Edit 3:
This is how i created my database tables.
The documentation I read for loading related entities has some differences with the sample code and your code. https://msdn.microsoft.com/en-us/library/jj574232(v=vs.113).aspx
First, when you define your ICollection, there is no keyword virtual:
public virtual ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
Next, in the example close to yours, where they load related items using a query, the first or default is not using a boolean expression. The selective expression is in a where clause:
// Load one blogs and its related posts
var blog1 = context.Blogs
.Where(b => b.Name == "ADO.NET Blog")
.Include(b => b.Posts)
.FirstOrDefault();
So you can try:
var searchProfilePush = _dataContext.SearchProfilePush
.Where(w => w.AccountId == accountId && w.SearchProfileId == searchProfileId)
.Include(w => w.SearchProfileMediaTypePush)
.FirstOrDefault();
Can you make these two changes and try again?
A few things will be an issue here. You have no keys defined or FKs for the relationship:
[Table("searchprofilepush")]
public class SearchProfilePush
{
[Key]
public int Id { get; set; }
public int AccountId { get; set; }
public bool Push { get; set; }
public int UserPushId { get; set; }
public UserPush UserPush { get; set; }
public int SearchProfileId { get; set; }
public SearchProfile SearchProfile { get; set; }
public ICollection<SearchProfileMediaTypePush> SearchProfileMediaTypePush { get; set; }
}
[Table("searchprofilemediatypepush")]
public class SearchProfileMediaTypePush
{
[Key]
public int Id { get; set; }
public MediaTypeType MediaType { get; set; }
public bool Push { get; set; }
public int SearchProfilePushId { get; set; }
[ForeignKey("SearchProfilePushId")]
public SearchProfilePush SearchProfilePush { get; set; }
}
Personally I prefer to explicitly map out the relationships using EntityTypeConfiguration classes, but alternatively they can be set up in the Context's OnModelCreating. As a starting point have a look at http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx for basic EF relationship configuration.
for a SearchProfilePush configuration:
modelBuilder.Entity<SearchProfilePush>()
.HasMany(x => x.SearchProfileMediaTypePush)
.WithRequired(x => x.SearchProfilePush)
.HasForeignKey(x => x.SearchProfilePushId);
I am defining a class based on a cataloguing Authority entry, which has a number of self referencing children, as follows:
public class Authority
{
public long ID { get; set; }
public string Term { get; set; }
public string Language { get; set; }
public bool PreferredTerm { get; set; }
public TermStatus TermStatus { get; set; }
public Authority Use { get; set; }
public List<Authority> UsedFor { get; set; }
public List<Authority> Equivalent { get; set; }
public List<Authority> Broader { get; set; }
public List<Authority> Narrower { get; set; }
}
When the columns are created in the Authority table in the underlying SQL database, the column names for each of the List properties are Authority_ID, Authority_ID1, Authority_ID2 and Authority_ID3.
I would rather the column names to be 'UsedFor', 'Equivalent', 'Broader' and 'Narrower'. I have tried using the [Column("name")] attribute but it does not work. How can I do this in Code First?
Try this. I am not sure if the definitions of the foreign keys must be in the POCO class (you can try to omit them).
public class Authority
{
[Key()]
public long ID { get; set; }
public string Term { get; set; }
public string Language { get; set; }
public bool PreferredTerm { get; set; }
public TermStatus TermStatus { get; set; }
public Authority Use { get; set; }
[Required]
public long Authority_ID { get; set; }
[Required]
public long Authority_ID1 { get; set; }
[Required]
public long Authority_ID2 { get; set; }
[Required]
public long Authority_ID3 { get; set; }
[ForeignKey("Authority_ID")]
public ICollection<Authority> UsedFor { get; set; }
[ForeignKey("Authority_ID1")]
public ICollection<Authority> Equivalent { get; set; }
[ForeignKey("Authority_ID2")]
public ICollection<Authority> Broader { get; set; }
[ForeignKey("Authority_ID3")]
public ICollection<Authority> Narrower { get; set; }
}
You can use [InverseProperty("name")] for your list. After that, your column names will be "UsedFor_ID", "Equilavent_ID", etc in the database (not quite corresponding to your question, sorry!).
public class Authority
{
[InverseProperty("UsedFor")]
public List<Authority> UsedFor { get; set; }
[InverseProperty("Equivalent")]
public List<Authority> Equivalent { get; set; }
}
See more at:
https://msdn.microsoft.com/en-us/data/gg193958
http://www.entityframeworktutorial.net/code-first/inverseproperty-dataannotations-attribute-in-code-first.aspx
Thanks for the suggestions. What worked was to use [ForeignKey] for the link and [Column] to rename the column, i.e.
[Column("Use")]
public long? UseID { get; set; }
[ForeignKey("UseID")]
public List<Authority> Use { get; set; }
However I have also made a critical mistake in the definition because even though I defined the column as a List, the code above ends up with a 1-to-0/1 key. What I really needed to do was to add in a child table to accept the many values.
My final code looks like this, and the underlying column names are readable instead of Authority_ID, Authority_ID1, Authority_ID2 and Authority_ID3:
public class Authority
{
public long ID { get; set; }
public string Term { get; set; }
public string Language { get; set; }
public bool PreferredTerm { get; set; }
public TermStatus TermStatus { get; set; }
//Establish 1-to-0/1 self-referencing key
[Column("Use")]
public long? UseID { get; set; }
[ForeignKey("UseID")]
public List<Authority> Use { get; set; }
//Establis 1-many foreign keys
[ForeignKey("UsedFor")]
public List<AuthorityList> UsedFor { get; set; }
[ForeignKey("Equivalent")]
public List<AuthorityList> Equivalent { get; set; }
[ForeignKey("Broader")]
public List<AuthorityList> Broader { get; set; }
[ForeignKey("Narrower")]
public List<AuthorityList> Narrower { get; set; }
}
public class AuthorityList
{
public long ID { get; set; }
public long AuthorityID { get; set; }
public long? UsedFor { get; set; }
public long? Equivalent { get; set; }
public long? Broader { get; set; }
public long? Narrower { get; set; }
}
In order to prevent cascading deletes getting in the way I have also added the following into my database context (affects entire DB not just these tables):
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Updated answer:
Using the above gave me the underlying table structure I wanted but not the functionality I required since EF decided that under that configuration it was going to map a 1-1 relationship instead of 1-M. The answer lay in understanding how EntityFramework manages self-referencing Many-to-Many relationships. Even this can be configured multiple ways depending on whether you only want two 1-M relationships or more. I want six.
In the end, this configuration gave me the functionality I wanted, to the expense of having a less than ideal database structure.
public class Tag
{
public int ID { get; set; }
public string Term { get; set; }
public virtual ICollection<Tag> Broader { get; set; }
public virtual ICollection<Tag> Narrower { get; set; }
public virtual ICollection<Tag> Equivalent { get; set; }
public virtual ICollection<Tag> Related { get; set; }
public virtual ICollection<Tag> Use { get; set; }
public virtual ICollection<Tag> Usefor { get; set; }
public Tag()
{
Broader = new HashSet<Tag>();
Narrower = new HashSet<Tag>();
Equivalent = new HashSet<Tag>();
Related = new HashSet<Tag>();
Use = new HashSet<Tag>();
Usefor = new HashSet<Tag>();
}
}
I also needed to add the following entries into the 'OnModelCreating' procedure of the database context:
modelBuilder.Entity<Tag>()
.HasMany(x => x.Broader)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("BroaderID").ToTable("TagBroader"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Equivalent)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("EquivalentID").ToTable("TagEquivalent"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Narrower)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("NarrowerID").ToTable("TagNarrower"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Related)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("RelatedID").ToTable("TagRelated"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Use)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("UsedID").ToTable("TagUse"));
modelBuilder.Entity<Tag>()
.HasMany(x => x.Usedfor)
.WithMany()
.Map(x => x.MapLeftKey("TagID").MapRightKey("UsedforID").ToTable("TagUsedfor"));
To test, I used the following:
//Broader/Narrower example
var music = new Tag{ Term = "Music"};
var jazz = new Tag{ Term = "Jazz Music" };
var classical = new Tag{ Term = "Classical Music" };
music.Narrower.Add(jazz);
music.Narrower.Add(classical);
jazz.Broader.Add(music);
classical.Broader.Add(music);
//Equivalent example
var zucchini = new Tag{ Term = "Zucchini" };
var courgette = new Tag{ Term = "Courgette" };
zucchini.Equivalent.Add(courgette);
courgette.Equivalent.Add(zucchini);
context.Tags.Add(music);
context.Tags.Add(jazz);
context.Tags.Add(classical);
context.Tags.Add(zucchini);
context.Tags.Add(courgette);
context.SaveChanges();
EF 6
I have the following POCO's
_
public class StructureEntity : EmEntityBase
{
[ForeignKey("ParentStructureId")]
public virtual StructureEntity ParentStructure { get; set; }
public long? ParentStructureId { get; set; }
[ForeignKey("SiteId")]
public virtual SiteEntity Site { get; set; }
[Required(ErrorMessage = "Required.")]
public long SiteId { get; set; }
[Required(ErrorMessage = "Required.")]
public string Name { get; set; }
}
public class SiteEntity : EmEntityBase
{
[ForeignKey("ParentSiteId")]
public virtual SiteEntity ParentSite { get; set; }
public long? ParentSiteId { get; set; }
[Required(ErrorMessage = "Required.")]
public long ClientId { get; set; }
[ForeignKey("ClientId")]
public ClientEntity Client { get; set; }
[Required(ErrorMessage = "Required.")]
public string Name { get; set; }
}
public class ClientEntity : EmEntityBase
{
public long? ParentClientId { get; set; }
[ForeignKey("ParentClientId")]
public virtual ClientEntity ParentClient { get; set; }
public string Name { get; set; }
}
Now when I want to eagerly load all the referenced entities. To do this I have:
public IQueryable<StructureDivisionEntity> GetAllWithInclude()
{
return GetAll()
.Include(e => e.Structure)
.Include(e => e.ParentStructureDivision.Structure)
.Include(e => e.Structure.Site.Client);
}
I was wondering if there was a dynamic way to do this without explicitly having to do the .Include(Structure) etc. Something along the lines of:
MyEntity.IncludeAllReferenced() where IncludeAllReferenced used reflection or similair to traverse MyEntity and do all the includes?
UPDATE: Alternative Approach To Querying Complex Graphs
http://www.codeproject.com/Articles/247254/Improving-Entity-Framework-Query-Performance-Using
https://entityframework.codeplex.com/workitem/1386
https://github.com/oinuar/GBQ
You can't do that, but you can make an extensionmethod to get all.
This might be a crazy idea, but you could make a generic extension-method to handle all your types, to make the code more clean.
Example, (one could extend this with factory-pattern if necessary):
public static IQueryable<T> IncludeAll<T>(this IQueryable<T> query)
{
if (typeof(T) == typeof(StructureDivisionEntity))
{
return GetAllWithInclude(query);
}
throw new NotImplementedException("IncludeAll not implemented for type {0}",typeof(T).FullName);
}
private static IQueryable<StructureDivisionEntity> GetAllWithInclude<StructureDivisionEntity> (IQueryable<StructureDivisionEntity> query)
{
return query.Include(e => e.Structure)
.Include(e => e.ParentStructureDivision.Structure)
.Include(e => e.Structure.Site.Client);
}