Define circular relationship for ICollection of bookings in entity framework - entity-framework

I am trying to add a circular relationship in my project. I have the following problem:
My database consists of a table with bookings (on a specific machine). Since the machines can handle multiple bookings at once, I have another table that stores all the (overlapping) parallel bookings. How can I now attach the overlapping bookings to the original booking element? I would like to access the overlaps like this:
var bookings = dbContext.Booking.Include(x => x.OverlapBookings).ToList();
foreach (var booking in bookings)
{
var overlaps = booking.OverlapBookings;
...
However, when trying to add the migration, I am running into the following error:
Unable to determine the relationship represented by navigation 'BookingDbModel.OverlapBookings' of type 'ICollection'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
How can I now define this circular relationship?
Here are the classes:
public class BookingDbModel
{
public int id { get; set; }
public string Name { get; set; }
public string Client { get; set; }
public string Machine { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public ICollection<OverlapBookingDbModel> OverlapBookings { get; set; }
}
and
public class OverlapBookingDbModel
{
public int OriginalBookingId { get; set; }
public BookingDbModel OriginalBooking { get; set; }
public int TargetBookingId { get; set; }
public BookingDbModel TargetBooking { get; set; }
}

With the following manual relationship definition, the entity updated successfully and all the models are now accessible with only one dbContext call:
DbContext.cs
public class ApplicationDbContext : DbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<BookingDbModel> Booking { get; set; }
public DbSet<OverlapBookingDbModel> OverlapBooking { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<OverlapBookingDbModel>()
.HasOne(p => p.OriginalBooking)
.WithMany(b => b.OverlapBookings)
.HasForeignKey(k => k.OriginalBookingId);
base.OnModelCreating(modelBuilder);
}
}
I can now access all related Overlapbookings like this:
var testbookings = dbContext.Booking.Include(x => x.OverlapBookings).ThenInclude(y => y.TargetBooking).FirstOrDefault(x => x.Id == 12);

Related

Mapping POCO class which has a (one-to-one) reference to another POCO class with AutoMapper EF Core

My apologies for (perhaps) not using the right terms in the title and this post.
The problem is as follows:
I have a POCO class which has a reference to another table (which is read only). This table has a one-to-one relationship with the other table.
I have set this upo as follow:
public class Commodity
{
public Commodity()
{
}
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
}
The OmsCommodityMaterial property is the referenced table. This referenced table is also a POCO class which has some other fields, and a porperty back to my own (Commodity) table so I can make a one-to-one relationship with Fluent:
public class OmsCommodityMaterial : OmsBaseClass
{
public OmsCommodityMaterial()
{
}
public long? CommodityMaterialID { get; set; }
public long? CommodityID { get; set; }
public string Name { get; set; }
public long? SortOrder { get; set; }
public Commodity Commodity { get; set; }
}
Fluent (for the one-to-one relation) is set up as follows:
public class MyContext : IdentityDbContext<ApplicationUser>
{
public virtual DbSet<Commodity> Commodity { get; set; }
// Oms classes:
public virtual DbSet<OmsCommodityMaterial> OmsCommodityMaterial { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.CommodityMaterialID)
.HasColumnName("CommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.OmsCommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.CommodityMaterialID);
});
}
}
In my endpoint I want to do a GET of all values which return the specific fields of my own table (Commodity) and all the fields of the referenced table (OmsCommodityMaterial).
For this purpose I created a ViewModel (also because else I get a circular reference as I found out in this post: ERR_CONNECTION_RESET returning Async including object child collections) which looks as follow:
public class CommodityViewModel
{
public long CommodityID { get; set; }
public long CommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public OmsCommodityMaterial OmsCommodityMaterial { get; set; }
}
For the ViewModels I am using AutoMapper, but I actually have no clue how I can map / return the list of the above ViewModel.
UPDATE
I ended up eliminating the Circular reference error by adding the [JsonIgnore] attribute to the public virtual Commodity Commodity { get; set; } property in the OmsCommodityMaterial POCO class. Now I can get all the needed column values:
return await this.Context.Commodity
.Include(i => i.OmsCommodityMaterial)
.ToListAsync();
Though, I suppose this is not the way to go. There should be a better solution for this by creating a ViewModel that retrieves the Commodity columns and (some) of the referenced OmsCommodityMaterial columns without falling in the Circular Reference error, but how (using AutoMapper)?

EF one-to-one relation not creates

I'll try create one-to-one relation using EF and Fluent API.
First class:
public class Game
{
public int Id { get; set; }
public Guid Token { get; set; }
public string Player { get; set; }
public virtual Field Field { get; set; }
public virtual ICollection<Move> Moves { get; set; }
public GameStatus Status { get; set; }
public DateTime StartTime { get; set; }
public DateTime? EndTime { get; set; }
public PlayerCode Winner { get; set; }
public Game()
{
Status = GameStatus.NoteDone;
StartTime = DateTime.UtcNow;
Winner = PlayerCode.None;
Field = new Field {Game = this};
Token = Guid.NewGuid();
}
}
Secong class:
public class Field : IEntity
{
public int Id { get; set; }
public virtual Game Game { get; set; }
public string CellsString { get; set; }
}
And configure relations in context
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Game>()
.HasRequired<Field>(g => g.Field)
.WithRequiredDependent(f => f.Game);
base.OnModelCreating(modelBuilder);
}
But after this relation in DB is not created. Tables look like this
I try many variations of Fluent configuration, but no one works for me. Where i do mistake?
You can specify a mapping for foreign key if you don't wish to add it as a property to your entity class.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Game>()
.HasRequired(g => g.Field)
.WithRequiredPrincipal(f => f.Game)
.Map(m => m.MapKey("GameId"));
}
You probably meant WithRequiredPrincipal, not WithRequiredDependent since you probably want that foreign key to be in the Field table.

Entity Framework Navigation Properties Are Null

I am having a problem where my Entity Framework navigation properties are null. My two models are Order and OrderLine:
class Order
{
public string CustomerId { get; set; }
public string OrderNumber { get; set; }
public ICollection<OrderLine> Lines { get; set; }
}
class OrderLine
{
public int LineNumber { get; set; }
public string OrderNumber { get; set; }
public string ProductId { get; set; }
public int Quantity { get; set; }
public Order Order { get; set; }
}
My Context class looks like this
class MyContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public DbSet<OrderLine> OrderLines { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasKey(p => p.OrderNumber);
modelBuilder.Entity<OrderLine>()
.HasKey(p => new { p.OrderNumber, p.LineNumber });
modelBuilder.Entity<OrderLine>()
.HasRequired(p => p.Order)
.WithMany(p => p.Lines)
.HasForeignKey(p => p.OrderNumber);
}
}
When I run the following code, my orders load (the message box shows the correct count), but the Order.Lines collection is null.
List<Order> orders = (from o in context.Orders select o).ToList();
// This message box shows the correct number of orders
MessageBox.Show(orders.Count.ToString());
// This line crashes because orders[0].Lines is null. There are lines in the database that should be joining to orders[0]
MessageBox.Show(orders[0].Lines.Count.ToString());
I've looked at a lot of examples, and I can't figure out what I'm doing incorrectly.
You need to declare the navigation properties as virtual in order to be lazy loaded:
public class Order
{
//...
public virtual ICollection<OrderLine> Lines { get; set; }
}
public class OrderLine
{
//...
public virtual Order Order { get; set; }
}
For more info check this link to see all the requirements you need to follow.

M:M Mapping - EF 4.3 CodeFirst (Existing Database)

I have two tables (Table A, Table B) joined with a join table (TableAB) with 3 payload columns. By Payload I mean columns apart from Id, TableAId, and TableBId.
I can insert into all tables successfully, but I need to insert data into one of the payload columns on Insert. I'm using EF 4.3, Fluent API. Can anyone help? Thanks in advance.
public class Organisation : EntityBase<int>, IAggregateRoot
{
public string Name { get; set; }
public string Url { get; set; }
public int CountryId { get; set; }
public int? OwnershipTypeId { get; set; }
public int OrganisationStatusId { get; set; }
public virtual ICollection<Feature> Features { get; set; }
public virtual ICollection<OrganisationType> OrganisationTypes { get; set; }
public virtual ICollection<PricePlan> PricePlans { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User: EntityBase<Guid>, IAggregateRoot
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string JobTitle { get; set; }
public int? PhoneCallingCodeId { get; set; }
public int? PhoneAreaCode{ get; set; }
public string PhoneLocal { get; set; }
public int? MobileCallingCodeId { get; set; }
public int? MobileAreaCode { get; set; }
public string MobileLocal { get; set; }
public virtual ICollection<Organisation.Organisation> Organisations { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public int OrganisationRoleId {get; set;}//Foreign Key - have tried leaving it out, tried it as public virtual Organisation Organisation {get;set;
public bool IsApproved { get; set; }
}
public class SDContext : DbContext
{
public ObjectContext Core
{
get
{
return (this as IObjectContextAdapter).ObjectContext;
}
}
public IDbSet<User> User { get; set; }
public IDbSet<Organisation> Organisation { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Organisation>().HasMany(u => u.Users).WithMany(o => o.Organisations).Map(m =>
{
m.MapLeftKey("OrganisationId");
m.MapRightKey("UserId");
m.ToTable("OrganisationUser");
});
//I have tried specifically defining the foreign key in fluent, but I really need to understand how I can add the payload properties once I access and edit them.
Your mapping is not correct for your purpose. If you want to treat OrganisationUser as an intermediate entity between Organisation and User you must create relationships between Organisation and OrganisationUser and between User and OrganisationUser, not directly between Organisation and User.
Because of the intermediate entity which contains its own scalar properties you cannot create a many-to-many mapping. EF does not support many-to-many relationships with "payload". You need two one-to-many relationships:
public class Organisation : EntityBase<int>, IAggregateRoot
{
// ...
// this replaces the Users collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class User : EntityBase<Guid>, IAggregateRoot
{
// ...
// this replaces the Organisations collection
public virtual ICollection<OrganisationUser> OrganisationUsers { get; set; }
}
public class OrganisationUser : EntityBase<int>, IAggregateRoot
{
public int OrganisationId { get; set; }
public Organisation Organisation { get; set; }
public Guid UserId { get; set; }
public User User { get; set; }
// ... "payload" properties ...
}
In Fluent API you must replace the many-to-many mapping by the following:
modelBuilder.Entity<Organisation>()
.HasMany(o => o.OrganisationUsers)
.WithRequired(ou => ou.Organisation)
.HasForeignKey(ou => ou.OrganisationId);
modelBuilder.Entity<User>()
.HasMany(u => u.OrganisationUsers)
.WithRequired(ou => ou.User)
.HasForeignKey(ou => ou.UserId);
Your derived DbContext may also contain a separate set for the OrganisationUser entity:
public IDbSet<OrganisationUser> OrganisationUsers { get; set; }
It's obvious now how you write something into the intermediate table:
var newOrganisationUser = new OrganisastionUser
{
OrganisationId = 5,
UserId = 8,
SomePayLoadProperty = someValue,
// ...
};
context.OrganisastionUsers.Add(newOrganisastionUser);
context.SaveChanges();
If you want to make sure that each pair of OrganisationId and UserId can only exist once in the link table, it would be better to make a composite primary key of those two columns to ensure uniqueness in the database instead of using a separate Id. In Fluent API it would be:
modelBuilder.Entity<OrganisationUser>()
.HasKey(ou => new { ou.OrganisationId, ou.UserId });
More details about such a type of model and how to work with it is here:
Create code first, many to many, with additional fields in association table

EF CF: complex types for legacy database

I successfully mapped my complex type like this:
modelBuilder
.ComplexType<Name>()
.Property(name => name.First)
.HasColumnName("firstNameColumn");
modelBuilder
.ComplexType<Name>()
.Property(name => name.Last)
.HasColumnName("lastNameColumn");
So far so good. But notice that we do not specify any entity type. What if we want to map the same complext type also for a table with columns "firstN" and "lastN"? I tried EntityTypeConfiguration<> but you are not allowed to specify complex types there. Finally it looks like that complexTypes are defined globally.
You can also customize the complex type columns names at the entity level, like the following:
public class User
{
public int UserId { get; set; }
public Name NameInfo { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public Name NameInfo { get; set; }
}
[ComplexType]
public class Name
{
public string First { get; set; }
public string Last { get; set; }
}
public class Context : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Customer> Customers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.ComplexType<Name>()
.Property(name => name.First)
.HasColumnName("firstNameColumn");
modelBuilder.ComplexType<Name>()
.Property(name => name.Last)
.HasColumnName("lastNameColumn");
// Here is how can customize the column names at the entity level:
modelBuilder.Entity<Customer>().Property(u => u.NameInfo.First)
.HasColumnName("firstN");
modelBuilder.Entity<Customer>().Property(u => u.NameInfo.Last)
.HasColumnName("lastN");
}
}
And the resultant schema will be:
Here you can find another example.