Entity Framework - customize navigation properties to abstract joins - c#

I have looked through similar questions here, but not seen this specific scenario.
Using EF 6 Code First, I have three tables, A, B and C. The relationship is A => B = 1:M, and B=>C = 1:1
The end result in this schema is that there is an implicit 1:M between A and C.
I do NOT want the consumer of the Entity Framework model to know about B. Ideally they would have a 1:M Navigation property from A to C (and I'd like to be able to surface this Entity Model through Web API and OData as IQueryable)
How could I do this?
If I add a custom [NotMapped] property to A which a collection of C, I have no way of populating C within the getter of that property because the entity doesn't know about its context.
Anyone have any ideas as to how to implement an IQueryable where A has a navigation property to C and B is 'abstracted' out of existence?
EDIT
Attempted to put the following into the code first entity A:
[NotMapped]
public ICollection<C> Cs
{
get { return this.Bs.Select(b => b.C) as ICollection<C>; }
}
But got this error:
The navigation property 'C' is not a declared property on type 'A'. Verify that it has not been explicitly excluded from the model and that it is a valid navigation property.
Thanks.

Here is an example.
public static class OrderDAL
{
public static Order Get(int key)
{
using (var context = new AppContext())
{
var order = context.Orders.Include(a => a.OrderDetails.Select(b => b.Information)).FirstOrDefault(a => a.Id == key);
// Fills C.
order.OrderDetailAdditionalInformation = order.OrderDetails.Select(b => b.Information).ToArray();
// Hides information about B.
foreach (var information in order.OrderDetailAdditionalInformation)
{ information.OrderDetail = null; }
order.OrderDetails = null;
return order;
}
}
}
public class AppContext : DbContext
{
public DbSet<Order> Orders { get; set; }
}
// A
public class Order
{
public int Id { get; set; }
public DateTime Date { get; set; }
public ICollection<OrderDetail> OrderDetails { get; set; }
[NotMapped]
public ICollection<OrderDetailAdditionalInformation> OrderDetailAdditionalInformation { get; set; }
}
// B, one A many B
public class OrderDetail
{
public int Id { get; set; }
public int Qty { get; set; }
public string Item { get; set; }
public int OrderId { get; set; }
public Order Order { get; set; }
public OrderDetailAdditionalInformation Information { get; set; }
}
// C, one B one C
public class OrderDetailAdditionalInformation
{
[ForeignKey("OrderDetail")]
public int Id { get; set; }
public int Width { get; set; }
public int Height { get; set; }
public int Long { get; set; }
public OrderDetail OrderDetail { get; set; }
}

Related

C# Web Api rest linq query return too much data

Good day,
I run into the problem then returning DTO object.
I have these classes
public class ProductBase
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public IEnumerable<ProductVariant> Variants { get; set; }
public int BaseImageId { get; set; } = 0;
}
public class ProductVariant
{
public int Id { get; set; }
public int Quantity { get; set; }
public int ProductBaseId { get; set; }
public ProductBase productBase { get; set; }
public int ProductSizeId { get; set; }
public ProductSize ProductSize { get; set; }
public int ProductColorId { get; set; }
public ProductColor ProductColor { get; set; }
public IEnumerable<ImageVariant> imageVariants { get; set; }
}
public class ProductColor
{
public int Id { get; set; }
public string Color { get; set; }
public IEnumerable<ProductVariant> productVariant { get; set; }
}
public class ProductSize
{
public int Id { get; set; }
public string Size { get; set; }
public IEnumerable<ProductVariant> productVariant { get; set; }
}
In productBaseRepository I have this call
public async Task<IEnumerable<Models.ProductBase>> GetAllWithVariantsAsync()
{
var result = await _dataContext.ProductBases
.Include(pb => pb.Variants)
.ThenInclude(v => v.ProductSize)
.Include(pb => pb.Variants)
.ThenInclude(v => v.ProductColor)
.ToListAsync();
return result;
}
I have created DTO convertion function
public static IEnumerable<ProductBaseDTO> ConvertToDto(this IEnumerable<ProductBase> productBases)
{
var returnProductBaseDto = (from product in productBases
select new ProductBaseDTO
{
Id = product.Id,
Name = product.Name,
Variants = product.Variants.ToList(),
Description = product.Description,
BaseImageId = product.BaseImageId,
}).ToList();
return returnProductBaseDto;
}
But then I call this function from swagger
[HttpGet]
public async Task<ActionResult<List<ProductBaseDTO>>> GetAllProductsWithVariants()
{
var baseProductDomain = await _productBaseRepository.GetAllWithVariantsAsync();
var baseProduct = baseProductDomain.ConvertToDto();
return Ok(baseProduct);
}
I get that
System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32
If I remove variants from call it works, so I need to some how remove Unecessry values from Variants
The problem happens because your ProductVariant references ProductSize and ProductColor and ProductSize and ProductColor reference a list of ProductVariant objects. And ProductVariant has a reference to its BaseProduct. This creates a cycle, two objects referencing each other.
In order to solve this issue, remove productVariant List from ProductSize and ProductColor or remove the ProductSize and ProductColor references from ProductVariant. Also remove the productBase reference from ProductVariant.
Use the IDs to find object references if required instead of having circular object references.
See prevent property from being serialized in web API on how to prevent properties from being serialized without removing the property declaration from the class.
This is because Variants has a reference to its parents type,
You could simply set this to null, e.g.
foreach(var x in baseProduct.SelectMany(c => c.Variants) { x.ProductBase = null }
We ussually have different viewmodels to stop these cycles, e.g:
public class Order {
public List<OrderLine> OrderLines {get;set}
}
public class OrderLine {
public Order Order {get;set}
}
// Gets mapped to the following viewmodels:
public class OrderViewModel {
public List<OrderOrderLineViewModel > OrderLines {get;set}
}
public class OrderOrderLineViewModel {
public Order Order => null; // Stop object cycling
}
Note that the exception message should tell you where the issue is for example $.Variants.productBase.variants.productBase.variants or something similar.

c# asp.net make a get request on relational table with entity framework database

I am trying to get data from a table with a get request with a controller. When I make the request with a normal table (TestTable) it is ok, but if I make the request with a relational table I get the fail message:
"The 'ObjectContent`1' type failed to serialize the response body for
content type 'application/xml; charset=utf-8'."
My controller (Mdata):
namespace ScThAsp.Controllers
{
public class MDataController : ApiController
{
public IEnumerable<Måledata> Get()
{
using (var e = new SCTHDBEntities())
{
return e.Måledata.ToList();
}
}
public TestTable Get(int id)
{
using (SCTHDBEntities entities = new SCTHDBEntities())
{
return entities.TestTable.FirstOrDefault(e => e.Id == 1);
}
}
}
}
My Table for måledata is:
public partial class Måledata
{
public int MDid { get; set; }
public Nullable<int> BBid { get; set; }
public Nullable<decimal> Måling1 { get; set; }
public Nullable<decimal> Måling2 { get; set; }
public Nullable<decimal> Måling3 { get; set; }
public Nullable<decimal> Måling4 { get; set; }
public Nullable<System.DateTime> RegTid { get; set; }
public virtual BuildBoard BuildBoard { get; set; }
}
My database looks like:
Database
See link..
I think I mayby should make a inner join with the other table connected to Måledata table - I am not sure how to do that in a EF environment.
I have really tried a lot now - hope for an answer. Thanks
Your class Måledata contains more data that you presented (it is marked as partial) and probably contains stuff related to EF. This magic stuff is not serializable. To avoid problem rewrite results to a plain object with properties you need. This object must be serializable (if contains plain properties and classes it will).
Building upon Piotr Stapp's answer you need to create a DTO (Data Transfer Object) for your Måledata which contains properties as your model, Måledata other than the EF properties. Use some sort of Mapper, maybe AutoMapper to map the required properties in your final response.
public class MaledataDTO
{
public int MDid { get; set; }
public int? BBid { get; set; }
public decimal? Måling1 { get; set; }
public decimal? Måling2 { get; set; }
public decimal? Måling3 { get; set; }
public decimal? Måling4 { get; set; }
public DateTime? RegTid { get; set; }
//... other properties
}
public IEnumerable<MaledataDTO> Get()
{
using (var e = new SCTHDBEntities())
{
var result = e.Måledata.ToList();
return Mapper.Map<List<MaledataDTO>>(result);
}
}
I found 2 solutions.
1) Solution was with Automapper (thanks Abdul). Installing automapper and a Using Automapper. Added a class called MåledataDTO : ` public class MåledataDTO
{
public int MDid { get; set; }
public int? BBid { get; set; }
public decimal? Måling1 { get; set; }
public decimal? Måling2 { get; set; }
public decimal? Måling3 { get; set; }
public decimal? Måling4 { get; set; }
public DateTime? RegTid { get; set; }
//... other properties
}
`
In my controller I used the following code
public IEnumerable<MåledataDTO> Get()
{
using (var e = new SCTHDBEntities())
{
Mapper.Initialize(config =>
{
config.CreateMap<Måledata, MåledataDTO>();
});
var result = e.Måledata.ToList();
return Mapper.Map<List<MåledataDTO>>(result);
2: In the second solution: In the picture you see the relations bewtween the tables - made in VS - but that creates a problem in the tables Get SET classes. The relation creates a Virtual object in the class - like mentioned before
public virtual BuildBoard BuildBoard { get; set; }
If you delete the relations and make the public partial class Måledata like
in this video
https://www.youtube.com/watch?v=4Ir4EIqxYXQ
the controller should then have one of two solutions:
using (SCTHDBEntities e = new SCTHDBEntities()) {
//this works
//var knud = new List<Måledata>();
//knud = (e.BuildBoard.Join(e.Måledata, c => c.BBid, o => o.BBid,
// (c, o) => o)).ToList();
//return knud;
//this works too
return (from p in e.BuildBoard
where p.BBid == 1
from r in e.Måledata
where r.BBid == p.BBid
select p).ToList();
That was that
Gorm

Entity Framework query with simple join

I am using Entity Framework 6 in a project and am having trouble creating a query.
Say my classes are defined like:
public class MyContext : DbContext
{
public MyContext(string connectionString) : base(connectionString)
{
}
public DbSet<EntityXXX> XXXSet { get; set; }
public DbSet<EntityYYY> YYYSet { get; set; }
}
public class EntityXXX
{
public string XXXName { get; set; }
public int id { get; set; }
public int YYYid { get; set; }
}
public class EntityYYY
{
public string YYYName { get; set; }
public int id { get; set; }
}
The YYYid property of EntityXXX is the 'id' of the EntityYYY instance that it relates to.
I want to be able to fill a Grid with rows where the first Column is XXXName and the second column is YYYName (from its related EntityYYY), but I can't see how to do this?
I'm sure it's really simple, but I'm new to EF.
You need to put a virtual navigation property on your EntityXXX
public virtual EntityYYY YYY { get; set; }
Then you can do a projection:
db.XXXSet
.Select(x => new { x.XXXName, YYYName = x.YYY.YYYName })
.ToList();
Which will get you the list you need.

Multiple .Include in Repository

At first, here what I have:
4 Classes have some relations between them as:
Project has many Bridges.
Bridge has many Foundations.
Foundation has many PierCaps and many PierColumns.
Project.cs:
public class Project
{
public Project()
{
Bridges = new HashSet<Bridge>();
StartDate = DateTime.Now;
EndDate = DateTime.Now;
}
// Fields
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
// Calculated Fields
public int BridgeCount => Bridges.Count;
public int PierFoundationCount = 0;
public int PierCapCount = 0;
public int PierColumnCount = 0;
// Relation To Children
public virtual ICollection<Bridge> Bridges { get; private set; }
}
Bridge.cs:
public class Bridge : BasicBaseEntityWithName
{
public Bridge()
{
PierFoundations = new HashSet<PierFoundation>();
}
// Fields
public int ProjectID { set; get; }
public Project Project { set; get; }
// Calculated Fields
public int PierFoundationCount => PierFoundations.Count;
public int PierCapCount => PierFoundations.Count;
public int PierColumnCount => PierFoundations.Count;
// Relation To Children
public ICollection<PierFoundation> PierFoundations { get; private set; }
}
PierFoundation.cs:
public class PierFoundation : BasicBaseEntityWithName
{
public PierFoundation()
{
PierCaps = new HashSet<PierCap>();
PierColumns = new HashSet<PierColumn>();
}
// Fields
public float Length { set; get; }
// Calculated Fields
public int PierColumnCount => PierColumns.Count;
public int PierCapCount => PierCaps.Count;
public float FoundationQty => (Length * Width * Height);
// Relations To Parents
public int BridgeID { set; get; }
public Bridge Bridge { set; get; }
// Relations To Children
public ICollection<PierCap> PierCaps { get; set; }
public ICollection<PierColumn> PierColumns { get; set; }
}
PierColumn.cs:
public class PierColumn : BaseEntityNoName
{
// Fields
public float Diameter { set; get; }
// Relations To Parents
public int PierFoundationID { set; get; }
public PierFoundation PierFoundation { set; get; }
}
PierCap.cs:
public class PierCap : BaseEntityNoName
{
// Fields
public float CapWidth { set; get; }
// Relations To Parents
public int PierFoundationID { set; get; }
public PierFoundation PierFoundation { set; get; }
}
BaseEntityNoName.cs:
public class BaseEntityNoName
{
public BaseEntityNoName()
{
AddedBy = Core.General.Global.CurrentUseId;
AddedDate = DateTime.Now;
}
public int Id { get; set; }
public DateTime AddedDate { get; set; }
public int AddedBy { get; set; }
// Becuase they will not appear at new, they will be nullable
public DateTime? ModifiedDate { get; set; }
public int? ModifiedBy { get; set; }
}
BasicBaseEntityWithName.cs:
public class BasicBaseEntityWithName : BaseEntityNoName
{
public string Name { get; set; }
}
Adding to that, I have this method in my Repository:
public IEnumerable<TEntity> Include(params Expression<Func<TEntity, object>>[] includeExpressions)
{
DbSet<TEntity> dbSet = Context.Set<TEntity>();
IEnumerable<TEntity> query = null;
foreach (var includeExpression in includeExpressions)
{
query = dbSet.Include(includeExpression);
}
return query ?? dbSet;
}
My questions are:
Q1. When I use Include the related entities (PierFoundation, PierCap, PierColumn), I don't get them all!. So, when I type:
PierFoundations = CurrentUnitOfWork.PierFoundationRepository.Include(x => x.PierColumns, x => x.PierCaps);
I received just PierFoundation and PierCap, but not PierColumn
What is the wrong here?. I want to retrieve all data in the all three of them.
Q2. In the Project class, I have four calculate fields:
BridgeCount which is solved by Bridges.Count
PierFoundationCount
PierCapCount
PierColumnCount
The last three, I don't know how to get them, because they don't have direct relation with Project
BTW: I am using EF Core 2
The two issues are unrelated, so answering only the main (as indicated by the post title) Q1:
When I use Include the related entities (PierFoundation, PierCap, PierColumn), I don't get them all!.
It's caused by the following line in your repository method implementation:
query = dbSet.Include(includeExpression);
As with many LINQ method, it's important to use the Include method return value when chaining with other calls, and as you can see, this code stores only the last Include call, hence the behavior you are observing.
It can be fixed by changing the implementation like this:
var query = Context.Set<TEntity>().AsQueryable();
foreach (var includeExpression in includeExpressions)
query = query.Include(includeExpression); // Note the usage of query variable
return query;
or simply
return includeExpresions.Aggregate(Context.Set<T>().AsQueryable(), (q, e) => q.Include(e));
But note that EF Core uses different approach for including nested data levels (ThenInclude) which cannot be expressed with Expression<Func<T, object>>, so you might consider exposing IQueryable<TEntity> returning method from your repository and use the EF Core provided Include / ThenInclude extension methods.
What about Q2, consider posting it in a separate SO question (post).

EF Core returns only first record of a list unless FK entities are reset

I am facing the same issue as described in this question. Problem: my method GetAllConferences() returns correctly all the conferences from the DB, but when I return the result to the View from the controller return Ok(tripListVm) inly the first collection item is returned to the client. On the otehr side, by setting to null all the FK references (as pointed out in the SO question above) I can return correctly all the entities to the client, however this does not seem to me the proper way of proceeding.
EDIT: the solution was much simpler than I though. In the code below (I leave it in its original form for others to see it) I was not mapping the FK entities inside the ViewModel to Dto objects, but returning the model entity itself. That was the reason why I needed to null those inner references to make it work. By returning all Dtos objects, it works properly.
I have three entities involved with 1-many relationships:
public class Conference
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Venue> Venues { get; set; }
public int? LocationId { get; set; }
public Location Location { get; set; }
}
public class Venue
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public int? ConferenceId { get; set; }
public Trip Conference { get; set; }
public int? LocationId { get; set; }
public City City { get; set; }
}
public class City
{
public int Id { get; set; }
[Required]
[MaxLength(50)]
public string Name { get; set; }
public ICollection<Conference> Conferences { get; set; }
public ICollection<Venue> Venues { get; set; }
}
In the repository, I have a method that returns the conferences and the related entities (City and Venues):
public IEnumerable<Conference> GetAllConferences()
{
return _context.Conferences
.Include(t => t.Venues)
.Include(t => t.City)
.ToList();
}
In the controller I need to use the following code to return all the results:
var conferences = _repository.GetAllConferences();
if (conferences.Any())
{
var conferenceListVm = trips.ToConferenceVmList();
//Without setting the FK references to null, I can return only the first result of the collection
foreach (var vm in conferenceListVm)
{
foreach (var pm in vm.PoinOfInterests)
{
pm.Trip = null;
}
vm.Location.Conferences = null;
vm.Location.Venues = null;
}
return Ok(conferenceListVm);
}
public static ConferenceViewModel ToConferenceVm(this Conference conference)
{
var confVm = new ConferenceViewModel();
confVm.Name = conference.Name;
confVm.City = conference.City;
confVm.Venues = conference.Venues;
return tripVm;
}
public static IEnumerable<ConferenceViewModel> ToConferenceVmList(this IEnumerable<Conference> conferences)
{
return conferences.Select(c => c.ToConferenceVm()).ToList();
}

Categories