EF6 Many-To-Many Join-Tables Half or Fully Broken - sql

I'm working on a database exercise project, effectively "porting" a board game to a command line project.
I have several tables that effectively serve to join tables that are related via Many-to-Many relationships. For example, a single boardSpace may exist upon several mapPanels, and a single mapPanel has several dozen boardSpaces. I have a BoardSpace table, a MapPanel table, and a BoardSpaceMapPanel table, the rows of which are only ever built once, and are just pulled for each game, depending on which pieces of the map board are being used.
public class BoardSpaceMapPanel
{
[Key]
public int Id { get; set; }
public BoardSpace BoardSpace { get; set; }
public MapPanel MapPanel { get; set; }
public BoardSpaceMapPanel()
{
}
public BoardSpaceMapPanel(BoardSpace boardSpace, MapPanel mapPanel)
{
BoardSpace = boardSpace;
MapPanel = mapPanel;
}
}
public class BoardSpace
{
[Key]
public int Id { get; set; }
public enum CitySiteType
{
None,
Normal,
Flood
}
public string Name { get; set; }
public CitySiteType CitySite { get; set; }
public bool FloodPlane { get; set; }
public int MaxPop { get; set; }
public Volcano Volcano { get; set; }
public bool Coastal { get; set; }
public bool Ocean { get; set; }
public bool Island { get; set; }
public Civilization.Civs StartingArea { get; set; }
public virtual ICollection<BoardSpaceMapPanel> BoardSpaceMapPanels { get; set; }
public BoardSpace()
{
}
}
public class MapPanel
{
public int Id { get; set; }
public int LeftRightSort { get; set; }
public MapPanel()
{
}
}
public class Board
{
public int Id { get; set; }
public Board()
{
}
public virtual ICollection<BoardMapPanel> BoardMapPanels { get; set; }
public IEnumerable<MapPanel> PanelsInPlay(DatabaseContext context)
{
var allBmps = context.BoardMapPanels.ToList();
var thisBmps = allBmps.Where(it => it.Board == this);
return thisBmps.Select(it => it.MapPanel);
}
public IEnumerable<BoardSpace> BoardSpaces(DatabaseContext context)
{
var panels = BoardMapPanels.Select(it => it.MapPanel).ToList();
var bsmps = context.BoardSpaceMapPanels
.Select(it => new { BoardSpace = it.BoardSpace, MapPanel = it.MapPanel})
.ToList().Where(it => panels.Contains(it.MapPanel));
return bsmps.Select(it => it.BoardSpace).Distinct();
}
}
public class BoardMapPanel
{
[Key]
public int Id { get; set; }
public Board Board { get; set; }
public MapPanel MapPanel { get; set; }
public BoardMapPanel()
{
}
public BoardMapPanel(Board board, MapPanel panel)
{
Board = board;
MapPanel = panel;
}
}
I have known good rows in the database. However, in code, whenever I grab the BoardSpaceMapPanels from the Database and call ToList(), only the MapPanel connection is made in what is produced, where the BoardSpace is null for ALL returned objects. However, I am able to filter these just fine through LINQ .Where calls, so I know that on the Database side, there is a connection between tables. See the method BoardSpaces above, which I had to add the .Select line into the var bsmps to effectively "cast" my DB rows into fully usable BoardSpaceMapPanel objects.
Additionally, I have several other similar many-to-many tables and some return null on rows for BOTH foreign key'd tables and some actually work properly. I have the table BoardMapPanel, which joins the game-session-specific Board with a few MapPanels that are being used. Similarly, I can filter these down by those belonging to a specific board, see PanelsInPlay method in Board class, but on debugging, I am able to see both Board AND MapPanel connections. I'm left thinking that something is fine about how Board and MapPanel classes are built but something wrong with how the BoardSpace class is built, since thats the one that wont bind after calling ToList on the IQueryable.
At work, I use NHibernate because that's what the Database was built on when I got there and following existing conventions is easy.
Any help is greatly appreciated. I could technically just call the .Select like I did to get bsmps in Board.BoardSpaces, but I know this isnt how things should be done and is super frustrating and a verbose refactor.
Thanks in advance

Related

Entity Framework Core - one-to-many but parent also has navigation property to a single child?

I currently have a working one-to-many relationship between the entities 'Conversation' and 'Message', where a conversation can have multiple messages.
This works fine:
public class Conversation
{
public long ID { get; set; }
}
public class Message : IEntity
{
public virtual Conversation Conversation { get; set; }
public long ConversationID { get; set; }
public long ID { get; set; }
}
However, I am trying to add a navigation property to the 'Conversation' class called 'LastMessage' which will keep track of the last message record that was created:
public class Conversation
{
public long ID { get; set; }
public virtual Message LastMessage { get; set; }
public long LastMessageID { get; set; }
}
When I try to apply the above, I get the error
System.InvalidOperationException: The child/dependent side could not
be determined for the one-to-one relationship between
'Conversation.LastMessage' and 'Message.Conversation'.
How do I maintain a one-to-many relationship between 'Conversation' and 'Message', but ALSO add a navigation property in the 'Conversation' class that navigates to a single 'Message' record?
If conversation can have several messages it is called one-to-many relations.
You have to fix the tables:
public class Conversation
{
[Key]
public long ID { get; set; }
[InverseProperty(nameof(Message.Conversation))]
public virtual ICollection<Message> Messages { get; set; }
}
public class Message
{
[Key]
public long ID { get; set; }
public long ConversationID { get; set; }
[ForeignKey(nameof(ConversionId))]
[InverseProperty("Messages")]
public virtual Conversation Conversation { get; set; }
}
After trying all sorts of Data Annotations and Fluent API nonsense, the cleanest solution I could come up with turned out to be very simple which requires neither. It only requires adding a 'private' constructor to the Conversation class (or a 'protected' one if you're using Lazy Loading) into which your 'DbContext' object is injected. Just set up your 'Conversation' and 'Message' classes as a normal one-to-many relationship, and with your database context now available from within the 'Conversation' entity, you can make 'LastMessage' simply return a query from the database using the Find() method. The Find() method also makes use of caching, so if you call the getter more than once, it will only make one trip to the database.
Here is the documentation on this ability: https://learn.microsoft.com/en-us/ef/core/modeling/constructors#injecting-services
Note: the 'LastMessage' property is read-only. To modify it, set the 'LastMessageID' property.
class Conversation
{
public Conversation() { }
private MyDbContext Context { get; set; }
// make the following constructor 'protected' if you're using Lazy Loading
// if not, make it 'private'
protected Conversation(MyDbContext Context) { this.Context = Context; }
public int ID { get; set; }
public int LastMessageID { get; set; }
public Message LastMessage { get { return Context.Messages.Find(LastMessageID); } }
}
class Message
{
public int ID { get; set; }
public int ConversationID { get; set; }
public virtual Conversation Conversation { get; set; }
}

Model Binding in Web API for .NET Core Type Mismatch

I have the following controller which is supposed to create a new object in the database:
[HttpPost]
public ActionResult<Panels> CreateNewPanel(Panels panel)
{
_context.Panels.Add(panel);
_context.SaveChanges();
return CreatedAtAction(nameof(GetPanelById), new { id = panel.ID }, panel);
}
It is receiving some JSON data, example:
{
"desc": "test5",
"frame": 2,
"aC240v": false
}
Which maps to the following model:
public class Panels
{
public int ID { get; set; }
public string Desc { get; set; }
public PanelFrames Frame { get; set; }
public bool AC240v { get; set; }
}
It works for the most part if "frame" isn't set, but if it is set to an integer like the code above it fails because it is type PanelFrames not an integer.
PanelFrames is another model that has a one to many relationship with Panels, each Panel can have only one PanelFrame so in the database this is recorded as simply an integer, the PanelFrames ID.
How do I reconcile this so that the integer (which is the PanelFrame ID) get's passed through the API and recorded in the database. The MS documentation doesn't seem to cover this, though it seems like it would be a pretty common occurrence, so I must not be understanding something, or doing something very wrong.
If you use EF Core one-to-many relationships and save the principle entity(PanelFrames) id,you just need to add a foreign key for your navigation property in your Panel model.Refer to my below demo:
1.Models
public class Panels
{
[Key]
public int ID { get; set; }
public string Desc { get; set; }
public int FrameID { get; set; }
[ForeignKey("FrameID")]
public PanelFrames Frame { get; set; }
public bool AC240v { get; set; }
}
public class PanelFrames
{
[Key]
public int PanelFramesID { get; set; }
public string Name { get; set; }
public List<Panels> Panels { get; set; }
}
2.In my case, I pass json data using postman, so I need to use [FromBody] on action parameters.
json:
{
"desc": "test5",
"frameid": 2,
"aC240v": false
}
Action:
[HttpPost]
public ActionResult<Panels> CreateNewPanel([FromBody]Panels panel)
Then a new Panel with FrameId would be added into database.
3.If you need to get panels with their Frame, just use Include method in action like
using Microsoft.EntityFrameworkCore;//Add necessary namespaces before
//...
var panels= _context.Panels
.Include(p => p.Frame)
.ToList();

.net Core Many to Many relationship

I am trying to determine what would be the smartest way to accomplish this. I may be way way overthinking what I am trying to do, but here goes.
I have the following entities, simplified
public class Meet
{
public int Id { get; set; }
//various properties
public List<MeetComp> Competitors { get; set; }
}
public class Competitor
{
public int Id { get; set; }
// various properties
public List<MeetComp> Meets { get; set; }
[ForeignKey("GymManager")]
public int GymManagerId { get; set; }
public GymManager GymManager { get; set; }
}
public class GymManager
{
public int Id { get; set; }
//various properties
public List<Competitor> Competitors { get; set; }
}
public class MeetComp
{
public int Id { get; set; }
[ForeignKey("Competitor")]
public int CompetitorId { get; set; }
public Competitor Competitor { get; set; }
[ForeignKey("Meet")]
public int MeetId { get; set; }
public Meet Meet { get; set; }
}
So I am creating a razor page where I get a specific Gymmanager and load all the related competitors to display in a list, which I have working just fine.
However I need another list (on the same page) of the related competitors of the Gymmanager but also who have an entry in the "MeetComp" table by a specific meetid. So List #1 is all of my Competitors and List #2 is all of my Comptetitors that are registered for that Meet.
Would it be smarter to have EF pull the data I get the data the first time with a ThenInclude()? Then I write some logic to determine if they get added to list #2? or should I make another trip to the Database? Then if I do make another trip to the database is there an easy to way to query for the List of CompId's I already have?
So here's what I ended up doing is making another trip to the DB.
public async Task<IActionResult> GetRegisteredComps(List<int> Comps, int meetid)
{
if(Comps.Count == 0)
{
return Ok();
}
if(meetid == 0)
{
return BadRequest();
}
var query = _context.MeetsComps.Include(c => c.Competitor)
.AsQueryable();
query = query.Where(c => c.MeetId == meetid);
query = query.Where(c => Comps.Contains(c.CompetitorId));
var results = await query.ToListAsync();
return Ok(results);
}

How do I Get Asp.net web api to join 2 tables (1 to many relation)

I am new to Web Api and just trying to learn by playing with different examples. I am completely stuck on trying to write a Get request to return a complex type. I have 3 entities, 1 of the entities has a list of another entity, So I am trying to figure out how to return the data from within both.
I looked at some examples on stack overflow, that showed to use the .Include linq statement, but when I try that, I am getting compiler errors (type argument cannot be inferred.
Basically, I have a class of Players, Teams and Specialties. Once I get this working, I am planning on writing an angular project. The specialties table is a multiselect for a given player.
Here is what I have written so far
public class Player
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int JerseyNo { get; set; }
public DateTime DateAquired { get; set; }
public string Bio { get; set; }
[ForeignKey("TeamID")]
public virtual Team Team { get; set; }
public virtual IEnumerable<Specialty> Specialites { get; set; }
}
public class Specialty
{
public int Id { get; set; }
public string Speciality { get; set; }
public virtual Player Player { get; set; }
}
public class Team
{
public int Id { get; set; }
public string TeamName { get; set; }
public virtual Player Player { get; set; }
}
public class dbContext :DbContext
{
public DbSet<Player> Players { get; set; }
public DbSet<Team> Teams { get; set; }
public DbSet<Specialty> Specialties { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder builder)
{
builder.UseSqlServer(#"Server=(localdb)\mssqllocaldb;Database=Test;Trusted_Connection=True;");
}
}
When I created the database using migrations, it looks how I want it to, but cannot figure out Web Api's joins to get the data from my specialties table. The .Include cannot recognize any value I enter as parameters
private dbContext db = new dbContext();
// GET: api/values
[HttpGet]
public IEnumerable<Player> Get()
{
var teams = db.
Players
.Include("Specialties")
.Select(p=> new Player
Looks like this an Entity Framework question.
Try if you can get this to work, for debugging purpose:
var teams = db.Players.ToList();
foreach (var player in teams)
{
// Force lazy loading of Specialities property
player.Specialities.ToList();
}
If this doesn't work, it looks like EF cannot figure out the mapping to the database.

How to load master-details data(more than 2 hierarchy) using WCF RIA Services

I'm trying to get my head around treeviews and am able to do two levels, but I'm stumped with adding the 3rd level. I assume you make a 3rd Hierarchical Template - but I'm not sure how this is loaded. I'm using the AdventureWorks database as sample data.
I'm using the Product, ProductCategory, and ProductSubCategory tables.
My Metadata looks like. (just the interesting bits)
public partial class Product
{
public string Name { get; set; }
public int ProductID { get; set; }
public string ProductNumber { get;set; }
public ProductSubcategory ProductSubcategory { get; set; }
public Nullable<int> ProductSubcategoryID { get; set; }
}
public partial class ProductCategory
{
public string Name { get; set; }
public int ProductCategoryID { get; set; }
[Include]
[Composition]
public EntityCollection<ProductSubcategory> ProductSubcategory { get; set; }
}
public partial class ProductSubcategory
{
public string Name { get; set; }
[Include]
[Composition]
public EntityCollection<Product> Product { get; set; }
public ProductCategory ProductCategory { get; set; }
public int ProductCategoryID { get; set; }
public int ProductSubcategoryID { get; set; }
My Queries look like :
public IQueryable<Product> GetProduct()
{
return this.ObjectContext.Product;
}
public IQueryable<ProductCategory> GetProductCategory()
{
return this.ObjectContext.ProductCategory.Include("ProductSubcategory");
}
public IQueryable<ProductSubcategory> GetProductSubcategory()
{
return this.ObjectContext.ProductSubcategory.Include("Product");
}
My Code behind (which is where I'm having the problem understanding how to load two queries). I want to return Products under ProductSubCategory under ProductCategory.
public partial class Tree : Page
{
public Tree()
{
InitializeComponent();
AdventureWorksContext ads = new AdventureWorksContext();
trvTree.ItemsSource = ads.ProductCategories;
ads.Load(ads.GetProductCategoryQuery());
}
}
Try modifying your GetProductCategory query as such:
{
return this.ObjectContext.ProductCategory.Include("ProductSubcategory").Include("ProductSubcategory.Product");
}
I don't know if it'll work in your case, where you want to build a tree, but it should include the data as needed.