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

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.

Related

EF Core: many-to-many query

I am trying to return all items in UserDTO from a User given a particular Team. The below code only returns the UserId (since that is what contained in TeamUser). Can this be done in a straightforward way? Or do I have to perform a second lookup on Users once the UserId's are given?
public class User
{
public long UserId { get; set; }
public string? Name { get; set; }
[Required]
[EmailAddress]
public string? Email { get; set; } = default!;
[Required]
public string? CreatedDate { get; set; }
/* relationships */
public virtual ICollection<TeamUser>? Teams { get; set; } //many Teams to many Users
}
public class Team
{
public long TeamId { get; set; }
[Required]
public string? Name { get; set; }
[Required]
public string? CreatedDate { get; set; }
/* relationships to User */
public virtual ICollection<TeamUser>? Users { get; set; } //many Users to many Teams
}
public class UserDTO
{
public long UserId { get; set; }
public string? Name { get; set; }
[Required]
[EmailAddress]
public string? Email { get; set; } = default!;
}
public class TeamUser
{
public long TeamId { get; set; }
public Team? Team { get; set; }
public long UserId { get; set; }
public User? User { get; set; }
}
[HttpGet]
public async Task<ActionResult<IEnumerable<UserDTO>>> GetUsers(long? userId, long? teamId)
{
if(teamId == null)
{
return await _context.Users.Select(x => UserToDTO(x)).ToListAsync();
}
else
{
// get users given teamId
return await _context.TeamUsers
.Include(u => u.User)
.Where(t => t.TeamId == teamId)
.Select(pt => new UserDTO
{
UserId = pt.UserId
})
.ToListAsync();
}
EF Core introduced the new ThenInclude() extension method to load multiple levels of related entities.
As EF Core won't load related properties automatically, so you'll need to explicitly do this, but something like the following should do the trick:
var result = context.Begrip
.Include(x => x.Categories)
.ThenInclude(x => x.category);
Note, intellisense doesn't always work on .ThenInclude at the moment, but the code should still compile even if it gets a red underline.
If you're returning this to the view or an API, you'll likely want to map it to a DTO so you don't have to deal with .Categories[0].category.Name etc.

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

Self Referencing Many-to-Many relations

I have an Ticket entity:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTickets { get; set; }
}
I want to setup many-to-many self-relations in Entity Framework Core, so i made two one-to-many relations:
public class Relation
{
[Required, ForeignKey("TicketFrom")]
public int FromId { get; set; }
[Required, ForeignKey("TicketTo")]
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
I've tried to create the relationship using fluent API:
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.FromId);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
But in result i have an error:
Cannot create a relationship between 'Ticket.RelatedTickets' and
'Relation.TicketTo', because there already is a relationship between
'Ticket.RelatedTickets' and 'Relation.TicketForm'. Navigation
properties can only participate in a single relationship.
The possible solution is to add Parent relation directly to TicketEntity:
public class Ticket
{
public int Id { get; set; }
[Required, ForeignKey("ParentRelation")]
public Nullable<int> ParentRelationId { get; set; }
public virtual Ticket ParentRelation {get;set;}
public virtual ICollection<Ticket> RelatedTickets { get; set; }
...
}
With fluent api like this:
modelBuilder.Entity<Ticket> =>
{
entity
.HasMany(e => e.RelatedTickets)
.WithOne(e => e.ParentRelation)
.HasForeignKey(e => e.ParentRelationId );
});
But it looks 'dirty' to store parent relation like this.
What is the right approach?
It's not possible to have just one collection with relations. You need two - one with relations the ticket equals TicketFrom and second with relations the ticket equals TicketTo.
Something like this:
Model:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTo { get; set; }
public virtual ICollection<Relation> RelatedFrom { get; set; }
}
public class Relation
{
public int FromId { get; set; }
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
Configuration:
modelBuilder.Entity<Relation>()
.HasKey(e => new { e.FromId, e.ToId });
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketFrom)
.WithMany(e => e.RelatedTo)
.HasForeignKey(e => e.FromId);
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketTo)
.WithMany(e => e.RelatedFrom)
.HasForeignKey(e => e.ToId);
Note that a solution using Parent is not equivalent, because it would create one-to-many association, while if I understand correctly you are seeking for many-to-many.
Here is very good explanation how to make many-to-many relationship in EF Core
Many-to-many self referencing relationship
Every collection or reference navigation property can only be a part of a single relationship. While many to many relationship with explicit join entity is implemented with two one to many relationships. The join entity contains two reference navigation properties, but the main entity has only single collection navigation property, which has to be associated with one of them, but not with both.
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany() // <-- one of this must be empty
.HasForeignKey(pc => pc.FromId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
Just make sure that WithMany exactly matches the presence/absence of the corresponding navigation property.
Note that you have to turn the delete cascade off.
#IvanStoev is correct. This is an example of a more general self referencing many to many relationship with many parents and many children.
public class Ticket
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public List<TicketTicket> TicketChildren { get; set; }
public List<TicketTicket> TicketParents { get; set; }
}
public class TicketTicket
{
public int TicketChildId { get; set; }
public Ticket TicketChild { get; set; }
public int TicketParentId { get; set; }
public Ticket TicketParent { get; set; }
}
modelBuilder.Entity<TicketTicket>()
.HasKey(tt => new {tt.TicketChildId, tt.TicketParentId});
modelBuilder.Entity<Ticket>()
.HasMany(t => t.TicketChildren)
.WithOne(tt => tt.ProductParent)
.HasForeignKey(f => tt.ProductParentId);
modelBuilder.Entity<Ticket>()
.HasMany(t => t.TicketParents)
.WithOne(tt => tt.TicketChild)
.HasForeignKey(tt => tt.TicketChildId);

Entity Framework Code First Many-to-Many relationship and inheritance

Forgive me if this question has been answered somewhere, I have been having a hard time finding a solution for this problem.
I am trying to set up EF Code First on an MVC4 Project. I have a User and Customer that both inherit from Person. I then have a Template object that has a Many-to-Many relationship with Customer and a One-to-Many relationship with User. Here is how I have it set up:
MODELS
public class Person
{
[Key]
public int PersonID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string FullName
{
get
{
return String.Format("{0} {1}", FirstName, LastName);
}
}
public string Email { get; set; }
public virtual List<Template> Templates { get; set; }
}
public class User : Person
{
....
}
public class Customer : Person
{
....
}
public class Template
{
public int TemplateId { get; set; }
public string TemplateName { get; set; }
public virtual List<Customer> Customers { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public virtual User User { get; set; }
}
CONTEXT
public class ProjectContext : DbContext
{
public ProjectContext()
: base("name=ProjectDB")
{
}
public DbSet<Template> Templates { get; set; }
public DbSet<User> Users { get; set; }
public DbSet<Customer> Customers { get; set; }
public DbSet<Person> People { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions
.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Template>()
.HasMany(x => x.Customers)
.WithMany(x => x.Templates)
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer")
);
}
}
If I remove the Person DBSet out of the context this works fine but sets up TPT inheritance. I would like to use TPH inheritance, but when I enable migrations with the Person DBSet in the context it chokes:
NavigationProperty 'Templates' is not valid. Type 'MvcProject.Models.Customer' of FromRole 'Template_Customers_Target' in AssociationType 'MvcProject.Models.Template_Customers' must exactly match with the type 'MvcProject.Models.Person' on which this NavigationProperty is declared on.
Where am I going wrong here?
You cannot inherit navigation properties from a base entity. They always must be declared in the class the other end of the relationship is refering to.
Template.Customers is refering to Customer (not to Person), hence the inverse navigation property Templates must be declared in Customer (not in Person)
Template.User is refering to User (not to Person), hence the inverse navigation property Templates must be declared in User (not in Person)
So, basically you must move the Templates collection from Person into both derived classes:
public class Person
{
// no Templates collection here
}
public class User : Person
{
//...
public virtual List<Template> Templates { get; set; }
}
public class Customer : Person
{
//...
public virtual List<Template> Templates { get; set; }
}
Then you can define the two relationships with Fluent API like so:
modelBuilder.Entity<Template>()
.HasMany(t => t.Customers)
.WithMany(c => c.Templates) // = Customer.Templates
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer"));
modelBuilder.Entity<Template>()
.HasRequired(t => t.User)
.WithMany(u => u.Templates) // = User.Templates
.HasForeignKey(t => t.UserId);
Change your HasMany selector to People:
modelBuilder.Entity<Template>()
.HasMany(x => x.People) // here
.WithMany(x => x.Templates)
.Map(x => x.MapLeftKey("TemplateId")
.MapRightKey("PersonId")
.ToTable("TemplateCustomer")
);

How to use composite Ids in one-to-many mappings in fluent nhibernate?

I got a scenario where a composite Id uniquely identifies an entity. I defined the MSSQL to have a multiple primary key on those fields. In addition I would like an auto-incremented id to be used for referencing a one-to-many relationship. Here's the schema:
public class Character
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Region Region { get; set; }
public virtual string Realm { get; set; }
public virtual IList<CharProgression> Progression { get; set; }
}
public class CharProgression
{
public virtual int Id { get; set; }
public virtual Character Character { get; set; }
public virtual Stage Stage { get; set; }
public virtual int ProgressionPoints { get; set; }
public virtual int NumOfSaves { get; set; }
}
public class Stage
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Description { get; set; }
}
The mappings look like this:
class CharacterMap : ClassMap<Character>
{
public CharacterMap()
{
Table("characters");
Id(ch => ch.Id, "id").GeneratedBy.Identity().Not.Nullable();
CompositeId().KeyProperty(ch => ch.Region, "region")
.KeyProperty(ch => ch.Realm, "realm")
.KeyProperty(ch => ch.Name, "name");
HasMany<CharProgression>(ch => ch.Progression).Inverse().Cascade.All();
}
}
class CharProgressionMap : ClassMap<CharProgression>
{
public CharProgressionMap()
{
Table("char_progression");
CompositeId().KeyReference(cprog => cprog.Character, "char_id",
.KeyReference(cprog => cprog.Stage, "stage_id");
Id(cprog => cprog.Id, "id").GeneratedBy.Identity().Not.Nullable();
Map(cprog => cprog.ProgressionPoints, "progression_points");
Map(cprog => cprog.NumOfSaves, "num_of_saves");
}
}
public class StageMap : ClassMap<Stage>
{
public StageMap()
{
Table("stages");
Id(st => st.Id, "id").GeneratedBy.Identity().Not.Nullable();
Map(st => st.Name, "name");
Map(st => st.Description, "description");
}
}
Now, the thing is that I would like to use SaveOrUpdate() on a character and use the composite id for the update, since the character uniqueness is defined by those 3 fields - region, realm, name.
However, when I am referencing the Character from CharProgression, I don't want to use the composite Id as I don't want the char_progression table to hold 3 fields for identifying a character, a simple Id is enough... which is why I also defined an IDENTITY id on the Character entity.
Is what i'm trying possible? or is there another way to achieve this?
Thanks :)