EntityFrameworkCore relationship between models and on cascade delete - c#

I have these Classes :
public class A
{
public A()
{
nameA = string.Empty;
ListB = new List<B>();
}
public string nameA { get; set; }
public List<B> ListB { get; set; }
}
public class B
{
public C cObject { get; set; }
public string nameB { get; set; }
}
public class C
{
public string nameC { get; set; }
}
I'm using Microsoft.EntityFrameworkCore to work with the Database for these models, I'm able to generate the DB and the right tables, but I'm missing two things :
The relationship between (A and B), and( B and C).
and the Ability the Add and delete objects on cascade (for example
in my case when deleting an Object A delete the corresponding List
of B objects with it).
Here is the Code in my Context File :
public class Context : DbContext
{
#region Tables
public DbSet<A> As{ get; set; }
public DbSet<B> Bs { get; set; }
public DbSet<C> Cs { get; set; }
#endregion
public Context(DbContextOptions options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//primary key's
modelBuilder.Entity<A>().HasKey(x => x.nameA);
modelBuilder.Entity<B>().HasKey(x => x.nameB);
modelBuilder.Entity<C>().HasKey(x => x.nameC);
//foreign key's
modelBuilder.Entity<A>().HasMany<B>(app => app.ListB);
modelBuilder.Entity<B>().HasOne<C>(app => app.cObject);
base.OnModelCreating(modelBuilder);
}
}

You can use the below code for your requirement. You have to use A table's PK as the foreign key in B and C tables PK as the foreign key on B. If you want the DeleteBehavior.Cascade then you have to use Required. Or you can do this using Fluent API.
public class A
{
public A()
{
nameA = string.Empty;
ListB = new List<B>();
}
public string nameA { get; set; }
public List<B> B{ get; set; }
}
public class B
{
public int CForeignKey { get; set; }
[ForeignKey("CForeignKey")]
public C C { get; set; }
public string nameB { get; set; }
[Required]
public int AForeignKey { get; set; }
[ForeignKey("AForeignKey")]
public A A {get; set;}
}
public class C
{
public string nameC { get; set; }
}
Fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<B>()
.HasOne(p => p.A)
.WithMany(b => b.B)
.OnDelete(DeleteBehavior.Cascade);
}

Related

EF multiple foreign key relationship on same primary key ApplicationUser table

I want to create a one-to-many relationship using EF 6 using a code-first approach.
Let's take simple and classical example. I have two entities Invoice and UserApplication which have a one-to-many relationship:
I also want to have an UpdatedById relationship with the same ApplicationUser table, to be able to show the names in the UI of who added the record and who modified it.
public partial class ApplicationUser : IdentityUser
{
public string FirstName { get; set; };
public string LastName { get; set; };
}
public virtual List<Invoice> Invoices { get; set; }
public class Invoice
{
public int Id { get; set; }
public string Details { get; set; }
public string CreatedById { get; set; }
public string UpdatedById { get; set; }
}
public virtual ApplicationUser CreatedBy { get; set; }
builder.Entity<Invoice>()
.HasOne(f => f.CreatedBy)
.WithMany(mu => mu.Invoices)
.HasForeignKey(f => f.CreatedById);
If you want Navigation Properties on Application user for these relationships, you would need to create and configure seperate ones.
eg
using Microsoft.EntityFrameworkCore;
using System.Linq;
using System;
using System.Collections.Generic;
namespace EfCore6Test
{
public partial class ApplicationUser //: IdentityUser
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Invoice> InvoicesCreated { get; } = new HashSet<Invoice>();
public virtual ICollection<Invoice> InvoicesLastUpdated { get; } = new HashSet<Invoice>();
}
public class Invoice
{
public int Id { get; set; }
public string Details { get; set; }
public int CreatedById { get; set; }
public int UpdatedById { get; set; }
public virtual ApplicationUser CreatedBy { get; set; }
public virtual ApplicationUser LastUpdatdBy { get; set; }
}
public class Db: DbContext
{
public DbSet<Invoice> Invoices{ get; set; }
public DbSet<ApplicationUser> Users{ get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Invoice>()
.HasOne(f => f.CreatedBy)
.WithMany(mu => mu.InvoicesCreated)
.HasForeignKey(f => f.CreatedById)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Invoice>()
.HasOne(f => f.LastUpdatdBy)
.WithMany(mu => mu.InvoicesLastUpdated)
.HasForeignKey(f => f.UpdatedById)
.OnDelete(DeleteBehavior.Restrict);
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=localhost;database=efCore6Test;Integrated Security=true;TrustServerCertificate=true", o => o.UseRelationalNulls(true))
.LogTo(Console.WriteLine, Microsoft.Extensions.Logging.LogLevel.Information);
base.OnConfiguring(optionsBuilder);
}
}
class Program
{
static void Main(string[] args)
{
{
using var db = new Db();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
}
}
}
}
Or simply omit the Navigation Properties on Application User.

Updating Error in many to many relationships in Entity Framework Core

In my .Net Core project i have two models with many-to many relationships:
public class Book
{
[Key]
public int BookId { get; set; }
public string Name { get; set; }
public string AuthorName { get; set; }
public int YearOfPublishing { get; set; }
public LibraryType Type { get; set; }
public ICollection<BookPublicHouse> BookPublicHouses { get; set; }
public Book()
{
BookPublicHouses = new Collection<BookPublicHouse>();
}
}
public class PublicHouse
{
[Key]
public int PublicHouseId { get; set; }
public string PublicHouseName { get; set; }
public string Country { get; set; }
public ICollection<BookPublicHouse> BookPublicHouses { get; set; }
public PublicHouse()
{
BookPublicHouses = new Collection<BookPublicHouse>();
}
}
And join table for EF Core:
public class BookPublicHouse
{
[Required]
[ForeignKey("Book_Id")]
public virtual Book Book { get; set; }
[Required]
[ForeignKey("Author_Id")]
public virtual PublicHouse PublicHouse { get; set; }
public int BookId { get; set; }
public int PublicHouseId { get; set; }
}
Context:
public class LibraryContext : DbContext
{
public LibraryContext(DbContextOptions<LibraryContext> options) : base(options) { }
public DbSet<Book> Books { get; set; }
public DbSet<PublicHouse> PublicHouses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BookPublicHouse>().HasKey(bp => new { bp.BookId, bp.PublicHouseId });
modelBuilder.Entity<BookPublicHouse>()
.HasOne(bp => bp.Book)
.WithMany(b => b.BookPublicHouses)
.HasForeignKey(bp => bp.BookId);
modelBuilder.Entity<BookPublicHouse>()
.HasOne(bp => bp.PublicHouse)
.WithMany(p => p.BookPublicHouses)
.HasForeignKey(bp => bp.PublicHouseId);
}
}
In my BookRepository i have CRUD operations. But when I'm trying to update my instance of Book which include Public Houses, I have an exception: 'The instance of entity type 'Book' cannot be tracked because another instance with the same key value for {'BookId'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.'
Here is my Update method:
public void Update(Book book)
{
_dbContext.Entry(book).State = EntityState.Modified;
}
I'm trying some others cases:
In this method my list of PublicHouses don't changing:
public void Update(Book book)
{
var current = Get(book.BookId);
_dbContext.Entry(current).CurrentValues.SetValues(book);
}
public Book Get(int id)
{
return _dbContext.Books
.Include(b => b.BookPublicHouses)
.ThenInclude(bph => bph.PublicHouse).FirstOrDefault(x => x.BookId == id);
}
In this case I have the same exception. Where is my problem?
public void Update(Book book)
{
var current = Get(book.BookId);
_dbContext.Books.Remove(current);
_dbContext.Add(book);
}

Why do I get an unneeded column in code first created table?

I don't understand why EF creates a nullable TemplateTask_Id column in my TemplateTaskDependancies table. I thought using a modelbuilder configuration class would solve the problem, but I must be missing something.
My domain classes are as follows.
[Table("TemplateTaskDependancies")]
public class TemplateTaskDependancy : Dependancy<TemplateTask>,
IDependancy<TemplateTask>
{
[Column("TaskId")]
public int TaskId { get; set; }
[Column("NeededTaskId")]
public int NeededTaskId { get; set; }
[ForeignKey("TaskId")]
public override TemplateTask Task { get; set; }
[ForeignKey("NeededTaskId")]
public override TemplateTask NeededTask { get; set; }
}
public abstract class Dependancy<T> : LoggedEntity
where T : LoggedEntity
{
[Column("TaskId")]
public int TaskId { get; set; }
[Column("NeededTaskId")]
public int NeededTaskId { get; set; }
[ForeignKey("TaskId")]
public abstract T Task { get; set; }
[ForeignKey("NeededTaskId")]
public abstract T NeededTask { get; set; }
}
public interface IDependancy<T> where T : LoggedEntity
{
int Id { get; set; }
int TaskId { get; set; }
int NeededTaskId { get; set; }
T NeededTask { get; set; }
T Task { get; set; }
State { get; set; }
}
public abstract class LoggedEntity : IObjectWithState
{
public int Id { get; set; } // primary key
// todo with Julie Lerman's repository pattern
}
In my context I have
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions
.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Configurations
.Add(new TemplateTaskDependancyConfiguration());
}
public class TemplateTaskDependancyConfiguration :
EntityTypeConfiguration<TemplateTaskDependancy>
{
public TemplateTaskDependancyConfiguration()
{
HasRequired(x => x.NeededTask)
.WithMany(y=>y.NeededTasks)
.HasForeignKey(z=>z.NeededTaskId)
.WillCascadeOnDelete(false);
HasRequired(x => x.NeededTask)
.WithMany(y => y.Dependancies)
.HasForeignKey(z => z.TaskId)
.WillCascadeOnDelete(false);
HasRequired(x=>x.Task)
.WithMany(y=>y.NeededTasks)
.HasForeignKey(z=>z.NeededTaskId)
.WillCascadeOnDelete(false);
HasRequired(x => x.Task)
.WithMany(y => y.Dependancies)
.HasForeignKey(z => z.TaskId)
.WillCascadeOnDelete(false);
}
}
Because you have no primary key defined anywhere?
By the way, it's dependEncy.
It turned out that the problem was caused by an unneeded collection of
public List<TemplateTaskDependancy> Tasks
inside my TemplateTask class.
i.e the foreign key table contained an extra collection of objects.

Entity Framework Code First Optional One to Many Relationship on implied Composite Key

Update: Created a sample project on GitHub.
In my existing database I have a generic audit table that has no keys, but I'd like to insert into this audit table with EF code first.
Existing reports pull audits based on AffectedId and EntityId where EntityId is hardcoded in a bunch of places.
I reversed engineered the database, but there is no explicit relationship between these tables...so here are the base POCO objects (I can't make a relationship either, its an existing system)
public class Audit
{
public int Id { get; set; }
public int EntityId { get; set; }
public string AffectedId { get; set; }
public string NewValue { get; set; }
}
public class Action1
{
public int Id { get; set; }
public string Desc { get; set; }
}
public class Action2
{
public int Id { get; set; }
public string Desc { get; set; }
}
I think I want the POCO objects to look like this
public class Audit
{
public int Id { get; set; }
public int EntityId { get; set; }
public string AffectedId { get; set; }
public string NewValue { get; set; }
public virtual Action1 Action1 { get; set; } // but I don't want this to change audit table
public virtual Action2 Action2 { get; set; } // but I don't want this to change audit table
}
public class Action1
{
public Action1() {this.Audits = new List<Audit>();}
public int Id { get; set; }
public string Desc { get; set; }
public virtual ICollection<Audit> Audits { get; set; }
}
public class Action2
{
public Action2() {this.Audits = new List<Audit>();}
public int Id { get; set; }
public string Desc { get; set; }
public virtual ICollection<Audit> Audits { get; set; }
}
But I can't seem to get the fluent mapping to allow me to insert an Action (1 or 2) that has populated AffectedId's in the Audit. This is what I was thinking on the mapping objects, but can't seem to get the hardcoded EntityId key to work correctly.
public class AuditMap : EntityTypeConfiguration<Audit>
{
public AuditMap()
{
// Primary Key
this.HasKey(t => t.Id);
this.HasOptional(t => t.Action1)
.WithMany(t => t.Audits)
.HasForeignKey(t => new {EntityId = 3, AffectedId = t.Id});
this.HasOptional(t => t.Action2)
.WithMany(t => t.Audits)
.HasForeignKey(t => new {EntityId = 5, AffectedId = t.Id});
}
}
public class Action1 : EntityTypeConfiguration<Action1>
{
public Action1Map()
{
// Primary Key
this.HasKey(t => t.Id);
}
}
public class Action2 : EntityTypeConfiguration<Action2>
{
public Action2Map()
{
// Primary Key
this.HasKey(t => t.Id);
}
}
Any suggestions on how to change the C# and not the SQL would be appreciated.
Mapping will not handle this scenario, but you can modify your domain model and business rules to do so. And having an enum for your magic values for EntityId will be much better.
public class Audit
{
public int Id { get; set; }
public int EntityId { get; set; }
public string AffectedId { get; set; }
public string NewValue { get; set; }
}
public abstract class AuditableAction{
public AuditableAction() {this.Audits = new List<Audit>();}
public int Id { get; set; }
public string Desc { get; set; }
public virtual IList<Audit> Audits { get; set; }
public void Audit(string description){
this.Audits.Add(new Audit(){
EntityId = this.GetEntityId(),
AffectedId = this.Id,
NewValue = description
});
}
public abstract int GetEntityId();
}
public class Action1 : AuditableAction
{
public override int GetEntityId(){
return AuditCode.Magic3.GetHashCode();
}
}
public class Action2 : AuditableAction
{
public override int GetEntityId(){
return AuditCode.Magic5.GetHashCode();
}
}
public enum AuditCode{
Magic3 = 3,
Magic5 = 5
}
Usage based on GitHub code dump
using (var context = new yunoworkContext())
{
var a = new Action1() {Id = 4, Desc = Guid.NewGuid().ToString()};
// This audit should insert an audit with EntityId = 3 and AffectedId = {primary key of the assiciated Action1}
a.Audit("Console!");
context.SaveChanges();
}

EntityFramework Object Not Set to Instance of Object in Model Creation

I have been struggling for a while now to convert a fairly large EntityFramework database created in model first to codefirst. I have a problem that I cant seem to resolve. I am getting an Object reference not set to an instance of an object with the following procedures on the call stack.
ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Configure
ModelConfiguration.Configuration.ModelConfiguration.ConfigureEntities
to simplify the post I have created a test project that boils the problem down to its simplest form.
I have 3 classes
a which has an has an optional b and an optional c
b which has a collection of a's, and a colleciton of c's
c which has an optional b and a collection of a's
public class a
{
public int Id { get; set; }
[Required]
public string name { get; set; }
public virtual b b { get; set; }
[ForeignKey("b")]
public int? b_Id { get; set; }
public virtual c c { get; set; }
[ForeignKey("c")]
public int? c_Id { get; set; }
}
public class b
{
public int Id { get; set; }
public string name { get; set; }
public virtual ICollection<a> a_s { get; set; }
public virtual ICollection<c> c_s { get; set; }
}
public class c
{
public int Id { get; set; }
public virtual b b { get; set; }
[ForeignKey("b")]
public int? b_Id { get; set; }
public virtual ICollection<a> a_s { get; set; }
}
public class MyContext : DbContext
{
public DbSet<a> a { get; set; }
public DbSet<b> b { get; set; }
public DbSet<c> c { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<a>()
.HasOptional(m => m.b)
.WithMany(m => m.a_s);
modelBuilder.Entity<b>()
.HasMany(m => m.c_s)
.WithRequired(m => m.b);
modelBuilder.Entity<c>()
.HasMany(m => m.a_s)
.WithOptional(m => m.c);
base.OnModelCreating(modelBuilder);
}
}
when I execute the code var a = from o in db.a select o, I get the error described above. There is absolutely no information on what is hapenning, so I really dont know where to turn. Can anyone help me solve this problem, as I really want to move away from Model First.
namespace MvcApplication2.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
var db = new MyContext();
var a = from o in db.a select o;
return View();
}
}
}
The mixup of fluent configuration and data annotation caused this problem. EF team should have handled this exception and given a meaningful error message.
Remove data annotations and use fluent configuration as follows
public class a
{
public int Id { get; set; }
[Required]
public string name { get; set; }
public virtual b b { get; set; }
public int? b_Id { get; set; }
public virtual c c { get; set; }
public int? c_Id { get; set; }
}
public class b
{
public int Id { get; set; }
public string name { get; set; }
public virtual ICollection<a> a_s { get; set; }
public virtual ICollection<c> c_s { get; set; }
}
public class c
{
public int Id { get; set; }
public virtual b b { get; set; }
public int? b_Id { get; set; }
public virtual ICollection<a> a_s { get; set; }
}
public class NreContext : DbContext
{
public DbSet<a> a { get; set; }
public DbSet<b> b { get; set; }
public DbSet<c> c { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<a>()
.HasOptional(m => m.b)
.WithMany(m => m.a_s)
.HasForeignKey(m => m.b_Id);
modelBuilder.Entity<a>()
.HasOptional(m => m.c)
.WithMany(m => m.a_s)
.HasForeignKey(m => m.c_Id);
modelBuilder.Entity<c>()
.HasOptional(m => m.b)
.WithMany(m => m.c_s)
.HasForeignKey(m => m.b_Id);
base.OnModelCreating(modelBuilder);
}
}
Try putting 'a' into local memory:
var a = from o in db.a.ToList() select o;

Categories