OData EF Core multiple self referencing table - sql

First up, this was working in EF6 however I can't seem to get it working in OData EF Core. In EF6 I was using VisualStudio's .edmx designer
I have a single Table called FacilityStructure which represents a hierarchy of
Facility
Building
Floor
Zone
Site
ie, a Facility can have 0 or more Buildings, a Building can have 0 or more Floors etc ..
The SQL Table is basically
Id PK INT NOT NULL
StructureType INT NOT NULL (0 = Facility, 1 = Building etc)
ParentId INT NULL
Name NVARCHAR(255) NOT NULL
The table is self referencing through ParentId and works fine - I also have a SQL View which basically self joins for each StructureType to give the Parent, Grand Parent, Great Grand Parent etc such that a bottom level Site record through this view shows its Zone, Floor, Building and Facility resulting in the following POCO
public partial class FacilityStructure
{
public int Id { get; set; }
public int StructureType { get; set; }
public Nullable<int> ParentId { get; set; }
public string Name { get; set; }
public Nullable<int> FSFacilityId { get; set; }
public Nullable<int> FSBuildingId { get; set; }
public Nullable<int> FSFloorId { get; set; }
public Nullable<int> FSZoneId { get; set; }
public Nullable<int> FSSiteId { get; set; }
[ForeignKey(nameof(FSFacilityId))]
public virtual FacilityStructure FSFacility { get; set; }
[ForeignKey(nameof(FSBuildingId))]
public virtual FacilityStructure FSBuilding { get; set; }
[ForeignKey(nameof(FSFloorId))]
public virtual FacilityStructure FSFloor { get; set; }
[ForeignKey(nameof(FSZoneId))]
public virtual FacilityStructure FSZone { get; set; }
[ForeignKey(nameof(FSSiteId))]
public virtual FacilityStructure FSSite { get; set; }
}
I have attributed the Navigation properties (FSFacility, FSBuilding etc) with their ForeignKey columns.
Since I am using OData EF Core and it has its own ODataModelBuilder which simply
builder.EntityType<FacilityStructure>().HasKey(e => e.Id);
and the ModelBuilder in OnModelCreating of the DbContext does similar with mapping to the correct View
builder.Entity<FacilityStructure>(entity =>
{
entity.HasKey(e => e.Id);
entity.ToTable("FacilityStructure", "odata");
});
but when I query the OData controller which simply
[EnableQuery]
public IQueryable<FacilityStructure> GetFacilityStructures()
{
return db.FacilityStructures;
}
I get the following runtime error ..
The query specified in the URI is not valid.
Unable to determine the relationship represented by navigation
property 'FacilityStructure.FSFacility' of type 'FacilityStructure'.
Either manually configure the relationship, or ignore this property
using the '[NotMapped]' attribute or by using
'EntityTypeBuilder.Ignore' in 'OnModelCreating'."
So its complaining about not being able to work out the relationship for FSFacility and I should manually configure it, which I think I am doing via the [ForeignKey()] attribute. The weird thing is if I comment out 3 of the relationships (FSFloor, FSZone, FSSite) then it works, I get data and I can $expand on FSFacility,FSBuilding which work. As soon as I add a 3rd relationship then the error returns.
This seems like a bug to me in EF Core since I am explicitly defining the 0:1 relationship and it works if 2 are defined but not for 3 or more.
I realise now that I should have perhaps split the single self referencing table into tables for each StructureType but this decision was actually made years ago and I am now porting the .NET Framework monolithic Webapp to .NET Core and Microservice architecture but require the monolithic Webapp to continue to work
Any help much appreciated

Related

Can't load One To Many navigation property in EF Core

My question may be a littler more specific and I'm just learning EF Core. I have two classes. tblBuilding and tblBuildingHour. Simple classes. Don't mind the naming convention of the tables. It's a legacy database. It works fine if I remove the navigation properties. What am I missing? Is there something I need to do for the Castle proxy configuration? Is it the composite keys? Is it the lazy loading of the navigation property? I'm stumped.
public class tblBuilding {
public int ID {get;set;}
public string Name {get;set;}
public virtual ICollection<tblBuildingHour> BuildingHours {get;set;}
}
public class tblBuildingHour {
public int BuildingID {get;set;}
public DateTime BuildingHourDate { get;set; }
public DateTime StartTime {get;set;}
public DateTime EndTime {get;set;
public virtual Building Building {get;set;}
}
There's lazy loading of the entities in my db context.
services.AddDbContext<EMSDataContext>(options => options.UseLazyLoadingProxies()
.UseSqlServer(Configuration.GetSection(EmsDevDb).Value)
.UseLoggerFactory(_loggerFactory));
In the dbContext I added the one to many relationship.
modelBuilder.Entity<tblBuilding>()
.HasMany(b => b.BuildingHours)
.WithOne(r => r.Building)
.HasForeignKey(r => r.BuildingID);
The only odd thing in this tblBuildingHour table is that it has a composite keys so I don't know if that's what's messing it up.
modelBuilder.Entity<tblBuildingHour>()
.HasKey(c => new { c.BuildingID, c.BuildingHourDate });
I wondered if the lazyloading was affecting it so I tried this to no avail.
https://www.learnentityframeworkcore.com/lazy-loading
EF Core creates the relationships provided you create your entity classes correctly. After looking closer at my classes, I removed the fluent api relationship, changed my entity classes to have List instead of ICollection properties, removed .Include from my repo queries, and it worked like a charm. Eager loading wasn't the solution. Surprisingly enough, it wasn't even the composite keys. EF Core managed to create the relationships correctly after I removed mine.
Addendum: to be clear, removing .Include is not part of the fix, that was only for lazy loading.
REMOVED
modelBuilder.Entity<tblBuilding>()
.HasMany(b => b.BuildingHours)
.WithOne(r => r.Building)
.HasForeignKey(r => r.BuildingID);
UPDATED
[Table("tblBuilding")]
public class EMSBuilding
{
public int ID { get; set; }
public string Name{ get; set; }
public virtual List<EMSBuildingHour> Hours { get; set; }
}
[Table("tblBuildingHours")]
public class EMSBuildingHour
{
[Column("BuildingHoursDate")]
public DateTime BuildingHourDate { get; set; }
public DateTime OpenTime { get; set; }
public DateTime CloseTime { get; set; }
public int BuildingID { get; set; }
public virtual EMSBuilding Building { get; set; }
}
modelBuilder.Entity<EMSBuildingHour>()
.HasKey(c => new { c.BuildingID, c.BuildingHourDate });

using EF Core IdentityContext and DbContext both for order management

I am working on creating an ecommerce website on ASP MVC Core 2. I inherited my user from IdentityUser and inherited context from IdentityDbContext for working with user data and inherited a different context from DbContext for working with products and orders etc.
Now, I want to link an order or shopping cart to a particular user and can not wrap my head around how to refer to the user in order table as they are in different contexts. I am also using the default guid created by EF as primary key in both the tables.
Should I ditch DbContext and use IdentityDbContext only? Does doing this causes problems with async methods in identity and other usual non async methods.
Here are some code snippets from my classes
using Microsoft.AspNetCore.Identity;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace shophe1.Models
{
public enum Gender { Male, Female, Other}
public class User : IdentityUser
{
public string FullName { get; set; }
public Gender Gender { get; set; }
public string ReferralID { get; set; }
public DateTime RegistrationDateTime { get; set; }
public string ActivationDateTime { get; set; }
public string UserType { get; set; }
public Wallet Wallet {get;set;}
public virtual ICollection<UserBankDetail> BankDetails { get; set; }
public virtual ICollection<UserAddress> AddressDetails { get; set; }
//public ShoppingCart Cart { get; set; }
//public ICollection<Order> Orders { get; set; }
}
}
Order class
using System.Collections.Generic;
namespace shophe1.Models
{
public class Order
{
public string OrderId { get; set; }
public ICollection<product> CartProducts { get; set; }
//public User User { get; set; }
public decimal OrderTotal { get; set; }
public decimal ShippingCharges { get; set; }
}
}
The issue is if I add user in order model and a collection of orders in user class, both contexts get mixed up and when migrating, all user and product related models get clubbed in same migration whether I use DbContext or IdentityContext in migrations --context option. This is because both user and orders are now interrelated.
Please advice.
Inherit your context from IdentityDbContext. You should have one context per database, ideally - especially if you want to relate the entities to each other.
If you actually want separate databases, such that the Identity tables reside in one, while your application tables reside in another, then you won't be able to directly relate the entities with each other. However, you can still create a pseudo-foreign key, where you simply store the id of a particular user in a column on one of your entities. You'd then merely need to issue a separate query on the other context with this id to fetch the user manually. For example:
var order = await appContext.Orders.FindAsync(orderId);
var user = await identityContext.Users.FindAsync(order.UserId);

How to cast Entity framework Objects in Web API?

I'm using entity framework and I figured out that it ain't able to serialize the output of
EDM Objects. For now I'm using Northwind Products-table. SO thereforth I'm forced to cast the Object to another and are using the .Cast but it doesn't work.
The only solution I have is to property by property do it manually in my code, but I'm thinking - there must be a better way!
For god's sake - it is 2013! And this Entity seems like a good idea in the beginning but it has so many gotchas and constraints that it actually hinders more than it helps, but anyway the EDMX diagrams are nice!
Anybody who has a better solution to casting the objects?
POCO
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
//public Nullable<int> SupplierID { get; set; }
//public Nullable<int> CategoryID { get; set; }
public string QuantityPerUnit { get; set; }
public Nullable<decimal> UnitPrice { get; set; }
public Nullable<short> UnitsInStock { get; set; }
public Nullable<short> UnitsOnOrder { get; set; }
public Nullable<short> ReorderLevel { get; set; }
//public bool Discontinued { get; set; }
public Category Category { get; set; }
//public ICollection<Order_Detail> Order_Details { get; set; }
//public Supplier Supplier { get; set; }
}
View Model
public class ProductsViewModel
{
public List<POCO.Product> Products { get; set; }
public ProductsViewModel()
{
using (NorthwindEntities dNorthwindEntities = new NorthwindEntities())
{
this.Products = dNorthwindEntities.Products.Cast<POCO.Product>().ToList();
Web api controller:
public class ProductsController : ApiController
{
public List<Product> GetAllProducts()
{
var viewmodel = new ProductsViewModel();
return viewmodel.Products;
}
1.
You can use frameworks like AutoMapper to handle Entities to ViewModel / DTO mapping automatically.
2.
Using Entities in the View (even in their POCO form) is not recommended for couple of reasons:
Security: Sending entities back to the client/view may expose more data than you intended.
Serialization: Since your entities usually contain reference to another entities and those entities may contain a reference back to the (parent) entity, you have to configure your serializer to handle this situation otherwise you'll get Circular Dependency Exception.
Incompatibility: The structure of your entity may not be compatible with what your view/client needs to render itself. Sometimes your view just needs a simple string while the entity holds this data in a much complex way hence the view needs to 'extract' it and you end up with a view full of unnecessary entity-drill-down code.

ASP.NET MVC 4 database scaffolding self referential model

I have a constructed a model using code first in C#. The model literally represents a container element for a website building application, in other words, the model defines a Div tag or some such HTML element. Like a Div tag which can contain multiple child elements, I have tried to represent this in my model, but the scaffolding to the DB, does not give me what I'd expect.
I should get a new many to many joins table, but instead I only get a single column in the DB which expects a single int data type.
Here is the model:
public class ElementContainer
{
public int ElementContainerID { get; set; }
public int PageId { get; set; }
public int? ParentElementContainerID { get; set; }
public string ElementContainerName { get; set; }
public ElementType ElementType { get; set; }
public string ElementClass { get; set; }
public PageAsset PageAsset { get; set; } // content of container
public List<ElementContainer> NestedContainers { get; set; }
}
The last line is the self-referential attribute which just appears as a column called ElementContainer_ElementContainerID
Thanks in advance!
I agree with Bahman, DB first is easier.
While I haven't tried to do what you are trying, your code looks like a self-Join that would do exactly what you describe.
This is a One-to-Many relationship. EF Navigation will pull a List of all nested children containers.
If you want to create a many-to-many relationship with EF Code-First, you should create another Entity
public class ContainerChildren
{
public int ElementContainerID { get; set; }
public List<ElementContainer> NestedContainers { get; set; }
}
this reference should help you to get the exact idea http://blogs.msdn.com/b/wriju/archive/2011/05/14/code-first-ef-4-1-building-many-to-many-relationship.aspx

MVC4 / EF5 / Auto Increment

I'm not sure how to exactly word my question which is probably why I cannot find an example of this anywhere. I'm playing around with MVC4 & EF5 (Web API too) but I'm not sure how to proceed with the Model as I've never really had to do much with them before. I'm doing something around the Periodic Tablet of Elements and I want to make it so that I have a list built for an element with it's electron configuration. However, I'd like to have it just auto number based on the input order. How can I tell EF to auto-increment a field? Basically like a primary key field without that limitation behind it. Here's what I have so far - I'm just not sure how to proceed:
public class Elements
{
public int ElementID { get; set; }
public string Name { get; set; }
public int AtomicNumber { get; set; }
public string Symbol { get; set; }
public virtual Categories Category { get; set; }
public virtual States State { get; set; }
public virtual Occurences Occurence { get; set; }
public virtual ICollection<Configurations> Configuration { get; set; }
}
public class Categories
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
public class States
{
public int StateID { get; set; }
public string StateName { get; set; }
}
public class Occurences
{
public int OccurenceID { get; set; }
public string OccurenceName { get; set; }
}
public class Configurations
{
public int ConfigurationID { get; set; }
public int Order { get; set; }
public int Value { get; set; }
}
Looking above what I'd like is for anytime a value is added to Configurations.Order the value starts at 1 and increases with each new 'row' but only for that specific ElementID.
Does that make sense? I was looking at using Data Annotations but I couldn't find anything that matched other than a Key Field but that'd make each Order a unique number - which I don't want. I feel like I'm not expressing this correctly because of all the stuff I've been looking at to figure it out, so here's a picture! yay!
This very well could be something that is better off from a programmatic standpoint. Even though this data changes once in a blue moon, I wanted to try and do it through EF if possible just so I know how.
Thanks a ton in advance. Also, if you see any other glaring errors, by all means let me know :) I rarely get to work with this side of web dev so I'm sure there's ways to do things better.
How can I tell EF to auto-increment a field?
You can't. Not even for a simple auto-incrementing primary key. Let alone for a field that should increment in relation to other values.
The HasDatabaseGeneratedOption mapping method is not a way to tell EF how to generate key values. It tells EF if and how the database generates values for properties, so EF knows how to respond to that.
So you either have to generate the order numbers in code, or let the database do it (by a trigger, or by mapping CUD actions on Configurations to stored procedures) and tell EF that the database computes the values by HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed) in the configuration of the Order property.