Entity Framework 6 Relationship and Inclusion Issue - entity-framework

I have the following POCOs running on a legacy EF6 API (reduced for brevity).
public class Customer
{
[ForeignKey("Parent")]
public int? ParentId { get; set; }
public Customer Parent { get; set; }
public virtual List<Site> Sites { get; set; }
}
public class Site
{
public List<Department> Departments { get; set; }
[ForeignKey("Customer")]
public int? CustomerId { get; set; }
public Customer Customer { get; set; }
}
I have the following fluent api configuration on the customer entity;
HasOptional(x => x.Sites).WithOptionalDependent().WillCascadeOnDelete(true);
and on the site entity;
HasOptional(r => r.Customer).WithMany().HasForeignKey(r => r.CustomerId);
However, when I run the following query form a customer perspective (and look to include the sites)
context.Where<Customer>(x => x.ParentId == null,
x => x.Sites).ToList();
Where the method Where is;
public List<T> Where<T>(Expression<Func<T, bool>> predicate, params Expression<Func<T, object>>[] includes) where T : class, IEntity
{
return this.AsQueryable(includes).Where(predicate).ToList();
}
I receive the error message;
System.Data.Entity.Core.EntityCommandCompilationException: An error occurred while preparing the command definition. See the inner exception for details. ---> System.Data.Entity.Core.MetadataException: The declared type of navigation property CCS.Data.UCare.Customer.Sites is not compatible with the result of the specified navigation.
The tables generate by EF in the database are as follows;
Can anyone advise on where I am going wrong here please?
UPDATE
I have also tried a fresh rebuild of the database (removed all migrations and run as fresh add-migration and update-database to a new test database). But the problem still persists.

Did you try:
HasOptional(r => r.Customer).WithMany(c => c.Sites).HasForeignKey(r => r.CustomerId);

Related

How can I define a 1:0 relationship using Entity Framework Core 2.1? [duplicate]

This question already has an answer here:
One-to-Zero relationship with Entity Framework Core 2.0
(1 answer)
Closed 4 years ago.
Updating entity Persons I get the following error:
System.InvalidOperationException: The property 'ID' on entity type 'Person' has a temporary value. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
at Microsoft.EntityFrameworkCore....
This has been discussed several places, and a similar issue was reported fixed by the EF Core team. However, one of those posts is about multiple updates to an entity and the one-to-many solution does not work here; additionally, I cannot make one ID column nullable and prefer to use the fluent API configuration. The documentation example does not work either, so I am asking here.
The scenario is that I am upgrading a legacy ASP.NET MVC 4 project to ASP.NET MVC Core, and as a result I am upgrading from EF 6.1 to EF Core 2.1. I will happily move to 2.2 instead if it solves this problem; I think it was still in prerelease when I started.
Here is a (ridiculously) simplified version of my entities:
public class Person
{
public int ID { get; set; }
public string name { get; set; }
public virtual Worker Worker { get; set; }
}
public class Worker
{
public int ID { get; set; }
public string somePersonalDetails { get; set; }
public virtual Person Person { get; set; }
// other relationships exist
}
I am using fluent API configuration:
public class PersonBuilder
{
public PersonBuilder(EntityTypeBuilder<Person> entity)
{
entity.HasKey(k => k.ID);
entity.HasOne(p => p.Worker)
.WithOne(p => p.Person)
.HasForeignKey<Person>(p => p.ID)
//.IsRequired(false) //?
.OnDelete(DeleteBehavior.Cascade);
}
}
public class WorkerBuilder
{
public WorkerBuilder(EntityTypeBuilder<Worker> entity)
{
entity.HasKey(k => k.ID);
// other relationships are defined
}
}
public override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Configurations<Person>().Add(typeof(PersonBuilder));
builder.Configurations<Worker>().Add(typeof(WorkerBuilder));
}
The reason that it's split apart like that is because I adapted it from our leftover EF 4/5/6 configuration. Yay legacy code. Nevertheless it works (for other defined types). The way I am reading that, it says "define a foreign key on the related Worker object pointing to the ID of this object." It does just the opposite.
I have tried:
Defining the key relationship on the WorkerBuilder type instead. This yields SQLite Error 19: 'FOREIGN KEY constraint failed'. Amazingly, however, it still attempts to define the key on the Person entity, which is wrong.
Removing some of the specific expressions in hopes that EF will just figure out the relationship itself. It doesn't; if I provide too little information, it tries to use columns|fields that don't exist (e.g., "PersonID", or is unable to figure out the relationship altogether.
So, I am stumped. Has anyone done this successfully? In plain English,
"A person may or may not have a worker record" (1:0); and,
"if they have a worker record, both records have the same ID." (FK_W_ID__P_ID)
Write your model classes as follows:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual Worker Worker { get; set; }
}
public class Worker
{
public int PersonId { get; set; }
public string somePersonalDetails { get; set; }
public virtual Person Person { get; set; }
// other relationships exist
}
Then in the PersonConfiguration and WorkerConfiguration:
public class PersonConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.HasKey(u => u.PersonId);
}
}
public class WorkerConfiguration : IEntityTypeConfiguration<Worker>
{
public void Configure(EntityTypeBuilder<Worker> builder)
{
builder.HasKey(u => u.PersonId);
builder.HasOne(u => u.Person)
.WithOne(b => b.Worker)
.HasForeignKey<Worker>(b => b.PersonId);
}
}
Then in the OnModelCreating of DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new PersonConfiguration());
modelBuilder.ApplyConfiguration(new WorkerConfiguration());
}

Entity Framework Core - recursive parent/child linking

I have an "account" table that includes a string foreign-key ("parent_guid") to its "parent" account (if one exists). I would like to create an entity that knows its parent, as well as all of its children.
Here is my entity:
[Table(name:"accounts")]
public class Account
{
[Key]
public string Guid { get; set; }
public string Name { get; set; }
[Column("guid")]
public string accountGuid { get; set; }
[Column(name: "parent_guid")]
public string parentGuid { get; set; }
[ForeignKey("parentGuid")]
public Account Parent { get; set; }
[InverseProperty("Parent")]
public ICollection<Account> Children { get; set; }
}
Here's my dbContext:
public DbSet<Split> Splits { get; set; }
public DbSet<Account> Accounts { get; set; }
public ReportContext(DbContextOptions<ReportContext> options)
: base(options)
{ }
My query is through the 'splits' context as the source table, but I end up returning Accounts. Maybe there's a better way?
When I query for an Account by Guid, I get a result, but 'Parent' and 'Children' are always null, even though 'parentGuid' contains the correct value, and I have confirmed that there should be child records.
Any idea how to make this work, either through annotations or fluent API?
Yes, EF Core requires explicit inclusion of relational entities.
var accounts = await dbContext.Accounts.Include(account => account.Parent)
.Include(account => account.Children)
.ToListAsync();
##EDIT
As per the edits to the question, this is one way to Eager Load relational entities, but I cannot speak to the efficiency of this query without knowing the relations and indexes.
public IQueryable<Split>
FindAllByAccountGuidsPostedBefore(IEnumerable<string> accounts,
DateTime endDate) {
using (reportContext) {
return reportContext.Splits.Include(s => s.Account)
.ThenInclude(a => a.Parent)
.ThenInclude(a => a.Children)
.Where(s => accounts.Contains(s.Account.Guid)
&& s.Transaction.postDate < endDate);
}
}
One way to obtain that information is by looking at the console when this query is run to find the SQL statement(s) this produces, or by asking someone who is more experienced in Relational Databases :)

Mapping an entity with a entity reference to a DTO cnot returning the values of the referenced entity - .NET Core, AutoMapper

Recently I have created an entity which has a one-to-one relation with another entity. I am trying to retrieve all the values of the main entity AND all the values of the referenced entity by using a DTO class in my API GET endpoint (of that entity), but with little success
For some other entities I have created several DTO classes and these work fine, however these other entities don't have a referenced entity of which I want to get the values.
The one-to-one (FK) relation is set up as follow:
The main entity:
public class Commodity
{
public Commodity()
{
}
public long CommodityID { get; set; }
public long OMSCommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
public virtual OmsCommodityMaterial OmsCommodityMaterial { get; set; }
}
The referenced entity:
public class OmsCommodityMaterial
{
public OmsCommodityMaterial()
{
}
public long? CommodityMaterialID { get; set; }
public string Name { get; set; }
public long? SortOrder { get; set; }
[JsonIgnore]
public virtual Commodity Commodity { get; set; }
}
With FLUENT API I am defining the one-to-one relation:
modelBuilder.Entity<Commodity>(entity =>
{
entity.Property(e => e.CommodityID)
.HasColumnName("CommodityID")
.ValueGeneratedOnAdd();
entity.Property(e => e.OMSCommodityMaterialID)
.HasColumnName("OMSCommodityMaterialID");
entity.Property(e => e.SpecficWeight)
.HasColumnName("SpecficWeight")
.HasColumnType("decimal(18, 2)");
entity.HasOne(a => a.OmsCommodityMaterial)
.WithOne(b => b.Commodity)
.HasForeignKey<Commodity>(b => b.OMSCommodityMaterialID);
});
Now in my Commodity endpoint (controller) I want to have a GET operator which retrieves all the field/values of the Commodity entity, but also all the values of the (related / referenced) OmsCommodity entity.
I am doing that as follow:
// GET: api/commodities
[HttpGet]
public async Task<IEnumerable<Commodity>> GetCommodities()
{
return await this.Context.Commodity
.Include(i => i.OmsCommodityMaterial)
.ToListAsync();
}
This is working fine, however I don't want to use my entity classes directly in my endpoint, and besides that I don't want to use all the fields of the (referenced) entity.
In some of my other endpoints I used AutoMapper for mapping my entities to a DTO class/ViewModel. I thought it would be fairly simple to do that for the GET as well but I can't seem to get it to work.
I tried to map the Commodity DTO to my Commodity/OmsCommodity as follow:
The DTO class:
public class CommodityDTO
{
public long CommodityID { get; set; }
public long OMSCommodityMaterialID { get; set; }
public decimal? SpecficWeight { get; set; }
// Referenced entity part
public string Name { get; set; }
public long? SortOrder { get; set; }
}
The (adjusted) GET endpoint:
[HttpGet]
public IActionResult GetCommodities()
{
var Commodities = this.Context.Commodity
.Include(i => i.OmsCommodityMaterial);
var commoditeDTO = _mapper.Map<IList<CommodityViewModel>>(Commodities);
return Ok(commoditeDTO);
}
This returns the values / fields of the Commodity entity correctly, however the referenced values (OmsCommodity) all return NULL.
AutoMapper setup is as follow:
public class AutoMapperProfile : Profile
{
public AutoMapperProfile()
{
this.CreateMap<Commodity, CommodityViewModel>();
this.CreateMap<CommodityViewModel, Commodity>();
}
}
Probably I am doing something obvious wrong, but after several tests I can't seem to figure it out. Perhaps I should just do a JOIN on the referenced entity instead of the .Include?
I used the following tutorial as a reference by the way:
http://jasonwatmore.com/post/2018/06/26/aspnet-core-21-simple-api-for-authentication-registration-and-user-management
You have to explicitly configure mapper where it should get the value for mapping from:
this.CreateMap<Commodity, CommodityViewModel>()
.ForMember(dest => dest.Name, opt => opt.MapFrom(m => m.OmsCommodityMaterial.Name));

error when using interfaces for Entity Framework (4.2) entities

I am using the the latest version of Entity Framework (4.2) and trying to implement interfaces for my Entities and for some reason, it isn't compiling. it is throwing an error "Cannot convert expression type ICollection<IOrder> to return type ICollection<Order>". if I don't use interfaces for the entities, then I don't get this error.
I have a separate project for interfaces (for repositories and services etc) and I need to pass the EF entities in those methods as parameters and I don't want to pass the actual entities in them, because that will require the interface project to have a dependency on the EF entities.
my goal is somewhat similar to the one mentioned in this post Can I abstract Entity Framework away from my Entities?
here is the sample. I just put a sample here, my actual entities are different, but the problem is same.
public interface IOrder
{
int OrderId { get; set; }
int CustomerId { get; set; }
ICustomer Customer { get; set; }
}
public class Order : IOrder
{
public int OrderId { get; set; }
public int CustomerId { get; set; }
ICustomer Customer { get; set; }
}
public interface ICustomer
{
int CustomerId { get; set; }
ICollection<IOrder> Orders { get; set; }
}
public class Customer : ICustomer
{
public int CustomerId { get; set; }
ICollection<IOrder> Orders { get; set; }
}
public class OrderMap : EntityTypeConfiguration<Order>
{
this.HasOptional(t => t.Customer)
.WithMany(t => t.Orders) //error comes from this line
.HasForeignKey(d => d.CustomerId);
}
Entity framework is not able to work with interfaces. Your navigation properties must use the real entity types (mapped classes).
"You can add your own partial class files to specify the interfaces to be implemented - and to provide any actual implementation methods you need" - as suggested here

Multiplicity constraint violated SQL Server 2008 - CodeFirst

I'm working to solve a very tedious problem.
I have a class called Nation and a class called NationAlly
public class Nation
{
public int ID {get; set;}
public int name {get;set;}
public List<NationAlly> NationAllies {get;set;}
}
public class NationAlly
{
public int ID {get; set;}
public int level {get;set;}
public Nation toNation {get;set;}
}
I'm using EF 4 and CodeFirst with a DbContext called NationsDB to manage my database on SQL Server 2008.
If I create a new object of type Nation and I try to call nationsDB.SaveChanges, I got the following exception:
"Multiplicity constraint violated. The role 'NationAlly_toNation_Target' of the relationship 'CodeFirstNamespace.NationAlly_toNation' has multiplicity 1 or 0..1."
I tried to save a Nation with NationAllies field null and this exception is not thrown, the nation table in the database gets all the correct values.
In my database the table Nation has 2 fields: ID(primary key), name
The table NationAlly has 3 fields: ID(primary key), level, NationID
The two tables are linked with a relationship where NationAlly.NationID is foreign key and Nation.ID is primary key.
Isn't strange? In my eyes the table NationAlly should have a field called NationID1 and another called NationID2 to create the "relationship" between a nation and a list of other nations.
What did I do wrong?
You are perhaps a victim of the EF Code-First mapping conventions which create automatically a relationship between NationAllies and toNation you don't want to have.
If I understand you correctly (but I am not 100 percent sure, if I do), you actually want to have two relationships and you have exposed only one end of the relationship in each of the entities. So, NationAllies does NOT point to toNation but to an "invisible" Owner nation in your NationAlly entity.
If that is the case you need to explicitly overwrite the convention mappings. In the Fluent API of EF 4.1 this could look like:
public class MyContext : DbContext
{
public DbSet<Nation> Nations { get; set; }
public DbSet<NationAlly> NationAllies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Nation>()
.HasMany(n => n.NationAllies)
.WithRequired()
.Map(conf => conf.MapKey("OwnerID"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<NationAlly>()
.HasRequired(a => a.toNation)
.WithMany()
.Map(conf => conf.MapKey("NationID"))
.WillCascadeOnDelete(false);
}
}
This mapping would create the two foreign keys OwnerID and NationID in the NationAllies table, both pointing to the primary key ID in the Nations table.
Edit
Here is the application I have tested with:
Create a new Console App in VS2010 / .NET 4.0, name it "NationsApp"
Add a reference to "EntityFramework.dll"
Clear the content of "Program.cs" and paste instead the following in:
Content of Program.cs:
using System;
using System.Collections.Generic;
using System.Data.Entity;
namespace NationsApp
{
public class Nation
{
public int ID { get; set; }
public int name { get; set; }
public List<NationAlly> NationAllies { get; set; }
}
public class NationAlly
{
public int ID { get; set; }
public int level { get; set; }
public Nation toNation { get; set; }
}
public class NationsContext : DbContext
{
public DbSet<Nation> Nations { get; set; }
public DbSet<NationAlly> NationAllies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Nation>()
.HasMany(n => n.NationAllies)
.WithRequired()
.Map(conf => conf.MapKey("OwnerID"))
.WillCascadeOnDelete(false);
modelBuilder.Entity<NationAlly>()
.HasRequired(a => a.toNation)
.WithMany()
.Map(conf => conf.MapKey("NationID"))
.WillCascadeOnDelete(false);
}
}
class Program
{
static void Main(string[] args)
{
using (var context = new NationsContext())
{
try
{
// We have three Nations and two Allies
Nation nation1 = new Nation() {
NationAllies = new List<NationAlly>() };
Nation nation2 = new Nation() {
NationAllies = new List<NationAlly>() };
Nation nation3 = new Nation() {
NationAllies = new List<NationAlly>() };
NationAlly ally1 = new NationAlly();
NationAlly ally2 = new NationAlly();
// Nation1 has two Allies
// (Nation1 is the "owner" of both Allies)
nation1.NationAllies.Add(ally1);
nation1.NationAllies.Add(ally2);
// toNation of ally1 refers to Nation2
ally1.toNation = nation2;
// toNation of ally2 refers to Nation3
ally2.toNation = nation3;
context.Nations.Add(nation1);
context.Nations.Add(nation2);
context.Nations.Add(nation3);
context.SaveChanges();
}
catch (Exception e)
{
throw;
}
}
}
}
}
You can set a breakpoint on "throw" to watch possible exceptions in e in the debugger.
This creates a database called NationsApp.NationsContext if you are using SQL Server Express and don't have any further connection strings defined.
It gives two relationships Nation_NationAllies (FK is "OwnerID") and NationAlly_toNation (FK is "NationID"). All columns are non-nullable. The result in the DB is the following:
In case this helps someone getting this error... I was getting this message while doing queries rather than saving to the database. My data design:
public class Base {
public int Id {get; set;}
}
public class Child {
[Key][ForeignKey("Base")] public int Id {get; set;}
public virtual Base Base {get; set;}
public Child() {
Base = new Base();
}
}
The problem was in the constructor. Turns out EF4.1 doesn't like when you initialize associations there! I removed that constructor and things started working again.