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

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)?

Related

Define circular relationship for ICollection of bookings in 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);

Invalid column name when using Entity Framework Core Table Per Hierarchy Inheritance

I am new to EF Core and am trying to use TPH Inheritance with Entity Framework Core
I have the following classes defined
public class WorkItem {
public Guid Id { get; set; }
public string WorkItemType { get; set; }
public string Description { get; set; }
}
public class Job : WorkItem {
public string BillingNotes { get; set; }
}
In my context, I have
public class JobContextNew : DbContext {
public virtual DbSet<WorkItem> WorkItem { get; set; }
public virtual DbSet<Job> Job { get; set; }
public JobContextNew(DbContextOptions<JobContextNew> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<WorkItem>(entity => entity.Property(e => e.Id).ValueGeneratedNever());
modelBuilder.Entity<WorkItem>()
.HasDiscriminator(workitem => workitem.WorkItemType)
.HasValue<Job>(nameof(Job));
}
}
If I omit the field in Job, it will pull the data just fine but when I add the BillngNotes back in I get the following error: Invalid column name 'BillingNotes
Can anyone tell me what I might be doing wrong?

How can I name a navigation property a different name from it's entity name in my EF POCO?

I have a POCO Entity named Employee.
And then I have a second POCO Entity named Case.
I want a navigation property that looks like instead this:
public class Case : BaseEntity
{
public long EmployeeId { get; set; }
public virtual Employee Employee{ get; set; }
like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
public virtual Employee InitialContact { get; set; }
I want to name my property InitialContact. Not Employee.
But I get this error when EF tries to create the Database:
Unable to determine the relationship represented by navigation property 'Case.InitialContact' of type 'Employee'. Either manually configure the relationship, or ignore this property from the model.
Update 1:
I got it to work like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
}
The primary key is ID in my BaseEntity. Not EmployeeId.
But I have second part to my question.
Here is my Complete Employee POCO:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using Hrsa.Core.Generic.Model.Framework.Concrete;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace Hrsa.Core.Generic.Model.Lerd
{
public class Employee : BaseEntity
{
[BindNever]
public string Email { get; set; }
[BindNever]
public long OrganizationId { get; set; }
[BindNever]
public string Supervisor { get; set; }
[BindNever]
public string SupervisorEmail { get; set; }
[BindNever]
public string FirstName { get; set; }
[BindNever]
public string LastName { get; set; }
public string Notes { get; set; }
[BindNever]
public long BargainingUnitId { get; set; }
[BindNever]
public long PayPlanId { get; set; }
[BindNever]
public long GradeRankId { get; set; }
[BindNever]
public long PositionTitleId { get; set; }
[BindNever]
public long SeriesId { get; set; }
public bool IsUnionEmployee { get; set; }
public virtual Organization Organization { get; set; }
public virtual BargainingUnit BargainingUnit { get; set; }
public virtual PayPlan PayPlan { get; set; }
public virtual GradeRank GradeRank { get; set; }
public virtual PositionTitle PositionTitle { get; set; }
public virtual Series Series { get; set; }
public virtual ICollection<UnionHours> UnionHours { get; set; }
public virtual ICollection<Case> Cases { get; set; }
[NotMapped]
public string UnionEmployeeYesNo => (IsUnionEmployee) ? "Yes" : "No";
}
}
I want my Employee to have many Cases:
public virtual ICollection<Case> Cases { get; set; }
Here is my complete Cases POCO:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
}
So now my DB looks like this:
So I have my InitialContactId in Cases ok.
But now I need my Case to have many Employees.
So I add this in to my Case POCO:
public virtual ICollection<Employee> Employees { get; set; }
Now it looks like this:
public class Case : BaseEntity
{
public long InitialContactId { get; set; }
[ForeignKey("Id")]
public virtual Employee InitialContact { get; set; }
public DateTime InitalConsultDate { get; set; }
public Guid AppUserId { get; set; }
public virtual AppUser LerSpecialist { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
Now when I run it, I get this error again:
Unable to determine the relationship represented by navigation property 'Case.InitialContact' of type 'Employee'. Either manually configure the relationship, or ignore this property from the model.
Update 2:
I found this article for a Many-Many relationship in .Net Core 1:
http://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
So now I have a bridge lookup entity:
public class EmployeeCase
{
[ForeignKey("Id")]
public long EmployeeId { get; set; }
public Employee Employee { get; set; }
[ForeignKey("Id")]
public long CaseId { get; set; }
public Case Case { get; set; }
}
Employee POCO:
Changed:
public virtual ICollection<Case> Cases { get; set; }
to:
// Mapping - Collection of Cases
public virtual ICollection<EmployeeCase> EmployeeCases { get; set; }
Case POCO:
Changed:
public virtual ICollection<Employee> Employees { get; set; }
to:
// Mapping - Collection of Employees
public virtual ICollection<EmployeeCase> EmployeeCases { get; set; }
In my AppDbContext
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
#region Many-to-Many Employees Cases
modelBuilder.Entity<EmployeeCase>()
.HasKey(ec => new { ec.EmployeeId, ec.CaseId });
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Employee)
.WithMany(e => e.EmployeeCases)
.HasForeignKey(ec => ec.EmployeeId);
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Case)
.WithMany(c => c.EmployeeCases)
.HasForeignKey(ec => ec.CaseId);
#endregion
}
Now when I run I get this error:
An exception of type 'System.Data.SqlClient.SqlException' occurred in Microsoft.EntityFrameworkCore.Relational.dll but was not handled in user code
Additional information: Introducing FOREIGN KEY constraint 'FK_EmployeeCase_Employees_EmployeeId' on table 'EmployeeCase' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint or index. See previous errors.
Update 3:
Finally got my tables the way I want with this piece of code from:
Introducing FOREIGN KEY constraint may cause cycles or multiple cascade paths - why?
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Get rid of Cascading Circular error on ModelBuilding
foreach (var relationShip in modelBuilder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationShip.DeleteBehavior = DeleteBehavior.Restrict;
}
#region Many-to-Many Employees Cases
modelBuilder.Entity<EmployeeCase>()
.HasKey(ec => new { ec.EmployeeId, ec.CaseId });
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Employee)
.WithMany(e => e.EmployeeCases)
.HasForeignKey(ec => ec.EmployeeId);
modelBuilder.Entity<EmployeeCase>()
.HasOne(ec => ec.Case)
.WithMany(c => c.EmployeeCases)
.HasForeignKey(ec => ec.CaseId);
#endregion
base.OnModelCreating(modelBuilder);
}
Update 4:
This did not work after all.
Remvoving the delete behavior for everything messes up my other relationships and I get errors.
How can I fix this?
This is disgusting.
So wishing I did not go Core.
Entity Framework uses conventions to guess how to map your C# model to database objects.
In your case you violate convention by custom name, so you should explain Entity Framework how to map this stuff.
There are two possible ways: attributes and fluent API. I'd suggest to use the latter one.
See section "Configuring a Foreign Key Name That Does Not Follow the Code First Convention" here: Entity Framework Fluent API - Relationships
I have made it a habit of explicitly defining my relationships as EF does not always get them the way I want. I like to create a Mapping folder that contains my entity maps. The fluent api works great for this and inherits from EntityTypeConfiguration.
Try this.
public class CaseMap : EntityTypeConfiguration<Case>
{
public CaseMap()
{
HasKey(m => m.Id)
HasRequired(m => m.InitialContact)
.WithMany(e => e.Cases)
.HasForeignKey(m => m.InitialContactId);
}
}
Almost forgot. You need to tell your DbContext where to find these mappings. Add this to your DbContexts OnModelCreating method.
modelBuilder.Configurations.AddFromAssembly(typeof(MyContext).Assembly);
This is what worked finally for the Cascading Delete circular references on the many-to-many in EF Core:
// Get rid of Cascading Delete Circular references error.
var type = modelBuilder.Model.GetEntityTypes().Single(t => t.Name == "Hrsa.Core.Generic.Model.Lerd.EmployeeCase");
foreach (var relationship in type.GetForeignKeys())
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
You have to get the Entity representing the many to many lookup only.
And from there restrict the DeleteBehavior.

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.

Relationship Mapping in EF4 code-only CTP (when using inheritance?)

I'm producing a simple composite patterned entity model using EF4 w/ the code-first CTP feature:
public abstract partial class CacheEntity
{
[Key]public string Hash { get; set; }
public string Creator { get; set; }
public int EntityType { get; set; }
public string Name { get; set; }
public string Predecessor { get; set; }
public DateTime DateTimeCreated { get; set; }
public virtual ICollection<CacheReference> References { get; set; }
}
public partial class CacheBlob : CacheEntity
{
public byte[] Content { get; set; }
}
public partial class CacheCollection : CacheEntity
{
public virtual ICollection<CacheEntity> Children { get; set; }
}
public class CacheReference
{
public string Hash { get; set; }
[Key]public string Reference { get; set; }
public virtual CacheEntity Entity { get; set; }
}
public class CacheEntities : DbContext
{
public DbSet<CacheEntity> Entities { get; set; }
public DbSet<CacheReference> References { get; set; }
}
Before I split out the primitive/collection derived classes it all worked nicely, but now I get this:
Unable to determine the principal end of the 'Cache.DataAccess.CacheEntity_References'
relationship. Multiple added entities may have the same primary key.
I figured that it may have been getting confused, so I thought I'd spell it out explicitly using the fluent interface, rather than the DataAnnotation attributes. Here's what I think defines the relationship properly:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CacheEntity>().HasKey(ce => ce.Hash);
modelBuilder.Entity<CacheEntity>().HasOptional(ce => ce.References).WithMany();
modelBuilder.Entity<CacheReference>().HasKey(ce => ce.Reference);
modelBuilder.Entity<CacheReference>().HasRequired(cr => cr.Entity).WithOptional();
}
But I must be wrong, because now I get this:
Entities in 'CacheEntities.CacheReferenceSet' participate in the
'CacheReference_Entity' relationship. 0 related 'Entity' were found. 1 'Entity' is expected.
Various other ways of using the fluent API yield different errors, but nothing succeeds, so I am beginning to wonder whether these need to be done differently when I am using inheritance.
Any clues, links, ideas, guidance would be very welcome.
using the MapHierarchy works for me:
protected override void OnModelCreating(ModelBuilder builder){
builder.Entity<CacheBlob>().HasKey(b=> b.Hash).MapHierarchy();
}
As an example.
Further reference : http://blogs.msdn.com/b/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx