EF Core 3.1 Fluent API

Viewed 192

I have two entities:

The Audit class:

public class Audit
{
  public string AuditId{get;set;}
  public int EmployeeId{get;set;}
  public virtual ModEmployee{get;set;}
}

The Employee class:

public class Employee
{
  public int EmployeeId{get;set}
}

While loading Set<Audit>, I want the Employee property of the Audit class to be populated. This is clearly a case of One to Many relation, where one Audit will have one Employee but an Employee can be in several Audits.

My Fluent API looks like below:

protected override OnModelCreating(ModelBuilder modelBuilder)
{
  modelBuilder.Entity<Audit>()
 .HasOne(a=>a.ModEmployee)
 .WithMany()
 .HasForeignKey("EmployeeId");//a=>a.EmployeeId wont work.

}

I have been searching for a solution for sometime and took some inputs from this answer but its for One-To-One. Then this resource shows a way but that will require me to have a collection of Audits in the Employee class.


I cant change the existing models or add annotations as they are already being used by some other entity framework version (not core).


Presently I am getting the following error:

System.InvalidOperationException: 'Unable to determine the relationship represented by navigation property 'Employee.ModEmployee' of type 'Employee'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.'

If I add a ignore method on the builder of Audit the error goes away, like so: .Ignore(a=>a.ModEmployee) but I won't get the entity object inside Audit. Any help will be appreciated. I am on EFCore 3.1.10. Thanks for reading. Any help will be appreciated.


3 Answers

Try changing your type from int to Employee in the Audit class. Should work fine because that's the correct way to relation a foreign key in the fluent API.

PS: the FK attribution string shall match the exact same inside the Employee, what's already happening.

I recently had the same issue. This worked for me:

Method 1: Define your Employee class with the reference to the Audit collection:

public class Employee
{
  public int EmployeeId { get; set }
  public virtual ICollection<Audit> Audits { get; set; }
}

Then in your model builder define the relation this way:

builder.Entity<Audit>().HasOne(e => e.ModEmployee).WithMany(ae => ae.Audits).HasForeignKey(k => k.EmployeeId);

You always need to define the collection in every class.

EDIT!!

Method 2:

public class Audit
{
  public string AuditId{get;set;}
  public Employee Employee { get; set; }
}

I know this will need you to edit your class but trust me it will make the things a lot easier.

If you cannot modify your model classes.

You can simply Add in your context a method to retrieve audits.

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EFCoreFluentApi1.Context
{
    public class MyContext : DbContext
    {
        public DbSet<Audit> Audit { get; set; }
        public DbSet<Employee> Employee { get; set; }

        public MyContext(DbContextOptions options) : base(options)
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            base.OnConfiguring(optionsBuilder);

        }
        protected override void OnModelCreating(ModelBuilder builder)
        {
            base.OnModelCreating(builder);

            builder.Entity<Employee>().HasKey(e => e.EmployeeId);
            builder.Entity<Employee>().Property(e => e.EmployeeId);

            builder.Entity<Audit>().HasKey(e => e.AuditId);
            builder.Entity<Audit>().Property(e => e.AuditId);
            builder.Entity<Audit>().HasOne(e => e.ModEmployee).WithMany().HasForeignKey(e => e.EmployeeId);

            builder.Entity<Employee>().HasData(new Employee[]
            {
                new Employee{ EmployeeId = 1 },
                new Employee{ EmployeeId = 2 },
                new Employee{ EmployeeId = 3 },
                new Employee{ EmployeeId = 4 }
            });

            builder.Entity<Audit>().HasData(
                new Audit[]
                {
                    new Audit
                    {
                        AuditId = "Audit1",
                        EmployeeId = 1
                    },
                    new Audit
                    {
                        AuditId = "Audit2",
                        EmployeeId = 1
                    },
                    new Audit
                    {
                        AuditId = "Audit3",
                        EmployeeId = 1
                    },
                    new Audit
                    {
                        AuditId = "Audit4",
                        EmployeeId = 2
                    },
                    new Audit
                    {
                        AuditId = "Audit5",
                        EmployeeId = 3
                    },
                    new Audit
                    {
                        AuditId = "Audit6",
                        EmployeeId = 3
                    },
                    new Audit
                    {
                        AuditId = "Audit7",
                        EmployeeId = 4
                    },
                    new Audit
                    {
                        AuditId = "Audit8",
                        EmployeeId = 4
                    },
                    new Audit
                    {
                        AuditId = "Audit9",
                        EmployeeId = 4
                    },
                    new Audit
                    {
                        AuditId = "Audit10",
                        EmployeeId = 4
                    }

                });
        }

        public ICollection<Audit> GetAudits(int employeeId)
        {
            return Audit.Where(e => e.EmployeeId == employeeId).ToList();
        }

    }
}

If you need populate ModEmployee, you can use the Include method.

using EFCoreFluentApi1.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Linq;
using System.Text.Json;

namespace EFCoreFluentApi1
{
    class Program
    {
        static void Main(string[] args)
        {
            IConfiguration configuration = new ConfigurationBuilder().
                    SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", false, true)
                    .Build();


            DbContextOptions options = new DbContextOptionsBuilder<MyContext>()
                .UseSqlServer(configuration.GetConnectionString("MyConnectionString"))
                .EnableSensitiveDataLogging(true).Options;

            using (var context = new MyContext(options))
            {
                var result = context.Audit.Include(e => e.ModEmployee).ToList(); //ModEmployee is populated 
                //var result = context.Audit.ToList(); //ModEmployee = null
                foreach (var value in result)
                {
                    Console.WriteLine($"█ Audit: {value.AuditId}");
                    Console.WriteLine(JsonSerializer.Serialize(value));
                    Console.WriteLine();
                }
                Console.ReadLine();
            }
        }
    }
}

Check my example in https://github.com/JomaStackOverflowAnswers/EFCoreFluentApi1 This project is <TargetFramework>netcoreapp3.1</TargetFramework>

Check IsRequired explicit https://docs.microsoft.com/en-us/ef/core/modeling/entity-properties?tabs=fluent-api%2Cwithout-nrt#explicit-configuration

Related