.net Core Many to Many relationship - optimization

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);
}

Related

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

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

Can not get list in a model which I get it AsQuerable in ef?

public class Product
{
public int Id{get;set;}
public int UserId{get;set;}
public Users User{get;set;}
}
I have set the Users to Product's relative:
b.HasOne("User").WithMany().HasForeignKey("UserID");
When I use entityframework to get the list of products.
The User is returned null, why?
There is a value in User table and the UserId is right in Product Table.
var list = _context.Products.AsQueryable();
the items in list has the User=null.
You need to Include the entity you're looking for. For example, let's suppose I have the following context.
AppDbContext.cs
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Notification> Notifications { get; set; }
public DbSet<Offer> Offers { get; set; }
}
Notification.cs
public class Notification
{
public int Id { get; set; }
public string Title { get; set; }
public int? OfferId { get; set; }
public virtual Offer Offer { get; set; }
}
If you want to use the Offer entity from Notification, you need to use the following statement:
context.Notifications.Include(n=> n.Offers).ToList();
// Your code goes here
In your situation:
var list = _context.Products.Include(p=> p.User).AsQueryable();
You have to explicitly ask to include the users in the returned list.
_context.Products.Include(p => p.Users).AsQueryable();

Modeling a 1:1 optional-on-both-sides relationship

I have three entities:
User - can have many Reviews and can have many Transactions
Transaction - must have a FromUser and ToUser, can have FromUserReview or ToUserReview
Review - Can have Transaction, must have FromUser and ToUser
The idea is that users may write reviews on one another, may send payments to each other. A user can only write one non-transactional review for another user - otherwise, reviews must be attached to transactions.
Essentially, this becomes a 1:1 optional-on-both-sides relationship between Transactions and Reviews. I was thinking about modeling this with a join table that contains:
ReviewId
TransactionId
And calling it TransactionReview. This seems to eliminate model/code duplication, but complicates my business logic.
Another alternative I see is creating two entities: UserReview and TransactionReview - which will simplify logic but will force me into code repetition and having two tables for what should be a single entity.
What is the right way to go about this? I am using Entity Framework code-first, in case it matters.
I have prepare some code, please check and try.
public class User
{
// properties
public int Id { get; set; }
public string Name { get; set; }
public virtual Address Address { get; set; }
public virtual ICollection<UserReview> UserReviewsFromMe { get; set; }
public virtual ICollection<UserReview> UserReviewsToUsers { get; set; }
public virtual ICollection<TransactionReview> TransactionReviews { get; set; }
}
public class Review
{
public int Id { get; set; }
public string Content { get; set; }
public string EntityName { get; set; }
public int EntityId { get; set; }
public virtual TransactionReview TransactionReview { get; set; }
public virtual UserReview UserReview { get; set; }
}
public class Transaction
{
public int Id { get; set; }
public string Note { get; set; }
public DateTime CreatedOnUtc { get; set; }
public virtual ICollection<TransactionReview> TransactionReviews { get; set; }
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
ToTable("User");
HasKey(p => p.Id);
}
}
public class ReviewConfiguration : EntityTypeConfiguration<Review>
{
public ReviewConfiguration()
{
ToTable("Review");
HasKey(x => new { x.Id });
}
}
public class TransactionConfiguration : EntityTypeConfiguration<Transaction>
{
public TransactionConfiguration()
{
ToTable("Transaction");
HasKey(x => new { x.Id });
}
}
public class UserReview
{
public int Id { get; set; }
public int FromUserId { get; set; }
public int ToUserId { get; set; }
public virtual User FromUser { get; set; }
public virtual Review Review { get; set; }
public virtual User ToUser { get; set; }
}
public class TransactionReview
{
public int Id { get; set; }
public int TransactionId { get; set; }
public int UserId { get; set; }
public virtual Transaction Transaction { get; set; }
public virtual Review Review { get; set; }
public virtual User User { get; set; }
}
public class UserReviewConfiguration : EntityTypeConfiguration<UserReview>
{
public UserReviewConfiguration()
{
ToTable("UserReview");
HasKey(x => new { x.Id });
Property(a => a.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
this.HasRequired(ur => ur.FromUser)
.WithMany(u => u.UserReviewsFromMe)
.HasForeignKey(ur => ur.FromUserId)
.WillCascadeOnDelete(false);
this.HasRequired(ur => ur.Review)
.WithOptional(r => r.UserReview);
this.HasRequired(ur => ur.ToUser)
.WithMany(u => u.UserReviewsToUsers)
.HasForeignKey(ur => ur.ToUserId)
.WillCascadeOnDelete(false);
}
}
In the above UserReviewConfiguration class, I mapped like this: A user can have zero or more UserReview's posted, a UserReview is posted by one user only and can be for one user only, and is mapped with one review only, making the Review and User entities independent as well if someone needs.
public class TransactionReviewConfiguration : EntityTypeConfiguration<TransactionReview>
{
public TransactionReviewConfiguration()
{
ToTable("TransactionReview");
HasKey(x => new { x.Id });
Property(a => a.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.None);
this.HasRequired(tr => tr.Transaction)
.WithMany(t => t.TransactionReviews)
.HasForeignKey(tr => tr.TransactionId);
this.HasRequired(tr => tr.Review)
.WithOptional(r => r.TransactionReview);
this.HasRequired(tr => tr.User)
.WithMany(u => u.TransactionReviews)
.HasForeignKey(tr => tr.UserId);
}
}
In the above TransactionReviewConfiguration class, I mapped like this: A user can have zero or more TransactionReview's posted, a TransactionReview is posted by one user only and can be for one Transaction only, and is mapped with one review only, making the User, Review and Transaction entities independent as well if someone needs.
Hope this helps...
I'd probably go with a simple data model:
User
Transaction (without storing the information about reviews here)
Review (a review must either be for a particular user or a transaction)
You could differentiate a review by it's type (a dictionary table) to know which review is standalone and which comes with a transaction.
You could go two ways about it:
have two columns for storing ids of Transaction and User entity and keeping nulls depending on the type
or having one column that would identify the id of an entity thanks to the type of a review
I don't see a need for TransactionReview entity, since one review can only be attached to 0..1 transaction. Since transactions can have 0..2 reviews this becomes a one-to-many relationship with optional zero elements.
I agree that it might complicate the logic of retrieval (having to remember that) but I find it very handy when dealing with data modelled like that.

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.