EF Core Annotation for OnDelete - c#

I hava an annotation problem:
modelBuilder.Entity<FirstClass>()
.HasOne(f => f.SecondClass)
.WithOne(s => s.FirstClass)
.HasForeignKey<FirstClass>(f => f.SecondClassId)
.OnDelete(DeleteBehavior.Cascade);
How to write this with annotations? I don't find the annotation for OnDelete.

Try this :
Your model
public class FirstClass
{
[Key]
public int Id { get; set; }
public int SecondClassId { get; set; }
[ForeignKey(nameof(SecondClassId))]
[InverseProperty("FirstClasses")]
public virtual SecondClass SecondClass { get; set; }
}
public class SecondClass
{
[Key]
public int Id { get; set; }
[InverseProperty(nameof(FirstClass.SecondClass))]
public virtual ICollection<FirstClass> FirstClasses { get; set; }
}
if you want to have only one first and one second try this code. But I don't recommend it since it will be hard to find the errors.
public class FirstClass
{
[Key]
public int Id { get; set; }
public int SecondClassId { get; set; }
[ForeignKey(nameof(SecondClassId))]
[InverseProperty("FirstClass")]
public virtual SecondClass SecondClass { get; set; }
}
public class SecondClass
{
[Key]
public int Id { get; set; }
[InverseProperty(nameof(FirstClass.SecondClass))]
public virtual FirstClass FirstClass { get; set; }
}
Your db context:
public class FirstClassDbContext : DbContext
{
public FirstClassDbContext()
{
}
public FirstClassDbContext(DbContextOptions<FirstClassDbContext> options)
: base(options)
{
}
public DbSet<FirstClass> FirstClasses { get; set; }
public DbSet<SecondClass> SecondClasses { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=FirstClass;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<FirstClass>(entity =>
{
entity.HasOne(d => d.SecondClass)
.WithMany(p => p.FirstClasses)
.HasForeignKey(d => d.SecondClassId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_FirstClass_SecondClass");
});
// or for one to one you can use yours, but I don't recommend it since
//it will be hard to find the errors and it will not do anything for you.
//Only confusing queries.
modelBuilder.Entity<FirstClass>()
.HasOne(f => f.SecondClass)
.WithOne(s => s.FirstClass)
.HasForeignKey(d => d.SecondClassId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_FirstClass_SecondClass");
}
}

I think it is not necessary.
public class GeneralClass
{
public int Id { get; set; }
}
public class FirstClass : GeneralClass
{
public int? DerivedClassId { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(EFCoreTest.SecondClass.FirstClass))]
public SecondClass SecondClass { get; set; }
public DerivedClass DerivedClass {get; set; }
}
public class SecondClass : GeneralClass
{
public int FirstClassId { get; set; }
public string Url { get; set; }
[ForeignKey(nameof(FirstClassId))]
public FirstClass FirstClass { get; set; }
}
public class ApplicationDbContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=(localdb)\MSSQLLocalDB;Database=sample;Trusted_Connection=True");
}
public DbSet<FirstClass> FirstClasses { get; set; }
public DbSet<SecondClass> SecondClasses {get; set; }
}
This generates the table definition
CREATE TABLE [dbo].[SecondClasses] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[FirstClassId] INT NOT NULL,
[Url] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_SecondClasses] PRIMARY KEY CLUSTERED ([Id] ASC),
CONSTRAINT [FK_SecondClasses_FirstClasses_FirstClassId] FOREIGN KEY ([FirstClassId]) REFERENCES [dbo].[FirstClasses] ([Id]) ON DELETE CASCADE
);
GO
CREATE UNIQUE NONCLUSTERED INDEX [IX_SecondClasses_FirstClassId]
ON [dbo].[SecondClasses]([FirstClassId] ASC)
;

Related

How to make a FK Required when you use conventions?

Assume that I have entities like this.
public class Student
{
public int StudentId { get; set; }
public string StudentName { get; set; }
}
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public string Section { get; set; }
public ICollection<Student> Students { get; set; }
}
Entity framework will generate a FK in Student table for me but It will be nullable. I want it to be required and also I want to use this convention and not the other ones. Is it possible? If it's not possible do you recommend any other technics?
Look at the below samples
public class Student
{
public int Id { get; set; }
public string Name { get; set; }
public int GradeId { get; set; }
public Grade Grade { get; set; }
}
public class Grade
{
public int GradeId { get; set; }
public string GradeName { get; set; }
public ICollection<Student> Student { get; set; }
}
In the above example, the Student entity includes foreign key property GradeId with its reference property Grade. This will create a one-to-many relationship with the NotNull foreign key column in the Students table, as shown below. more here
A complete sample
public partial class Product
{
public int Id { get; set; }
public int? UserId { get; set; } // set nullable FK
public User User { get; set; }
}
public partial class Ticket
{
public int Id { get; set; }
public int UserId { get; set; } // set non nullable FK
public string Title { get; set; }
public User User { get; set; }
}
public partial class User
{
public User()
{
Products = new HashSet<Product>();
Tickets = new HashSet<Ticket>();
}
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
public ICollection<Ticket> Tickets { get; set; }
}
public partial class AngulartestContext : DbContext
{
public AngulartestContext()
{
}
public AngulartestContext(DbContextOptions<AngulartestContext> options)
: base(options)
{
}
public virtual DbSet<Product> Products { get; set; }
public virtual DbSet<Ticket> Tickets { get; set; }
public virtual DbSet<User> Users { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
#warning To protect potentially sensitive information in your connection string, you should move it out of source code. See http://go.microsoft.com/fwlink/?LinkId=723263 for guidance on storing connection strings.
optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=db;Persist Security Info=True;User ID=sa;Password=123456");
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.Property(e => e.Id).ValueGeneratedNever();
entity.HasOne(d => d.User)
.WithMany(p => p.Products)
.HasForeignKey(d => d.UserId)
.HasConstraintName("FK_Products_Users");
});
modelBuilder.Entity<Ticket>(entity =>
{
entity.Property(e => e.Id).ValueGeneratedNever();
entity.Property(e => e.Title).HasMaxLength(50);
entity.HasOne(d => d.User)
.WithMany(p => p.Tickets)
.HasForeignKey(d => d.UserId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Tickets_Users");
});
modelBuilder.Entity<User>(entity =>
{
entity.Property(e => e.Id).ValueGeneratedNever();
entity.Property(e => e.Name).HasMaxLength(50);
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

EntityFrameworkCore relationship between models and on cascade delete

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);
}

Null value for the foreign key relationship in Entity Framework

I am creating a simple register module. I was able to successfully insert data via SQL Management studio but when tried to run the application, it is getting a null value ref. UserxId is 0 but the configuration is set to Identity and i'm not sure why AccessLevel is null. The code is very straightforward and unitofwork and generic repository is standard.
Property:
public class Userx
{
public int UserxId { get; set; }
public int Access_Level_ID { get; set; }
public virtual AccessLevel AccessLevel { get; set; }
public string username { get; set; }
public string password { get; set; }
}
Configuration:
public class UserxConfig : EntityTypeConfiguration<Userx>
{
public UserxConfig()
{
ToTable("Userx");
HasKey(x => new { x.UserxId, x.Access_Level_ID });
Property(x => x.UserxId)
.HasColumnName("UserxId")
.HasColumnType(SqlDbType.Int.ToString())
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(x => x.Access_Level_ID)
.HasColumnName("Access_Level_ID")
.HasColumnType(SqlDbType.Int.ToString())
.IsRequired();
HasRequired(x => x.AccessLevel)
.WithMany()
.HasForeignKey(x => x.Access_Level_ID)
.WillCascadeOnDelete(false);
}
}
Below is the code for the controller
[HttpPost]
public ActionResult Registers(Userx userx)
{
if (ModelState.IsValid)
{
using (var scope = new TransactionScope())
{
var encryptedPw = CustomEncrypt.Encrypt(userx.password);
var user = new Userx
{
Access_Level_ID = userx.Access_Level_ID,
username = userx.username,
password = userx.password
};
_unitOfWork.UserxRepository.Insert(user);
_unitOfWork.Save();
scope.Complete();
}
}
else
{
ModelState.AddModelError("", "One or more fields have been");
}
return View();
}
The view is auto generated.
sample value while debugging, after the line, null value exception appears.
Basically I have two questions,
Why was the UserxId 0 when in fact it should be auto generated and auto incremented
Should the AccessLevel have value? I believe that property is used to map the foreign key.
Update:
Property for AccessLevel:
public class AccessLevel
{
public int Access_Level_ID { get; set; }
[Required]
public string Level { get; set; }
[Required]
public string Description { get; set; }
}
Configuration:
public class AccessLevelConfig : EntityTypeConfiguration<AccessLevel>
{
public AccessLevelConfig()
{
ToTable("AccessLevel");
HasKey(x => x.Access_Level_ID);
Property(x => x.Access_Level_ID)
.HasColumnName("Access_Level_ID")
.HasColumnType(SqlDbType.Int.ToString())
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
DataContext:
public DbSet<AccessLevel> AccessLevel { get; set; }
public DbSet<Userx> Userx { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new AccessLevelConfig());
modelBuilder.Configurations.Add(new UserxConfig());
base.OnModelCreating(modelBuilder);
}
Null Value Reference Exception Screenshot
FIX:
The UserxRepository was returning the null value ref. exception.
Before:
private readonly UnitOfWork _unitOfWork;
public AuthController(UnitOfWork unitOfWork;)
{
_unitOfWork = unitOfWork;
}
After:
private readonly UnitOfWork _unitOfWork;
public AuthController()
{
_unitOfWork = new UnitOfWork;
}
What i was trying to accomplish on the before code was to create an abstraction by having UnitOfWork dependency class in the constructor so the object will be pre-instantiated.
public class Userx
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //TELL EF TO AUTOGENERATE ID
public int UserxId { get; set; }
public int? Access_Level_ID { get; set; } //MAKE ACCESS_LEVEL_ID NULLABLE
public virtual AccessLevel AccessLevel { get; set; }
public string username { get; set; }
public string password { get; set; }
}
Define Primary Key to Userx table on UserxId. It will automatically generate auto increment identity.
public class Userx
{
[Key] //TELL EF TO AUTOGENERATE ID
public int UserxId { get; set; }
public int? Access_Level_ID { get; set; } //MAKE ACCESS_LEVEL_ID NULLABLE
public virtual AccessLevel AccessLevel { get; set; }
public string username { get; set; }
public string password { get; set; }
}

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.

code first many to many set name of table

I'm new to code first and derived from DB Context. Here is a excerpt of my Model.
[Table("pm_Material")]
public class Material
{
public Material()
{
this.ProductionStepLogs = new HashSet<ProductionStepLog>();
}
[Key]
public int MaterialId { get; set; }
public int MaterialTypeId { get; set; }
public string Description { get; set; }
public decimal CostRate { get; set; }
public virtual MaterialType MaterialType { get; set; }
public virtual ICollection<ProductionStepLog> ProductionStepLogs { get; set; }
}
[Table("pm_ProductionStepLog")]
public class ProductionStepLog
{
public ProductionStepLog()
{
this.Materials = new HashSet<Material>();
}
[Key]
public System.Guid ProductionStepLogId { get; set; }
public int ProductionStepId { get; set; }
public System.Guid ProductId { get; set; }
public Nullable<System.DateTime> BeginStep { get; set; }
public Nullable<System.DateTime> EndStep { get; set; }
public int UserId { get; set; }
public virtual Product Product { get; set; }
public virtual ProductionStep ProductionStep { get; set; }
public virtual ICollection<Material> Materials { get; set; }
}
The DB creation works fine, but I want to specify the name of the auto-generated many-to-many table "ProductionStepLogMaterials" using [Table("pm_ProductionStepLogMaterials")].
Is this possible?
You should override your protected override void OnModelCreating(DbModelBuilder modelBuilder) of your own DBContext class like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Material>()
.HasMany(a => a.ProductionStepLog)
.WithMany(a => a.Material)
.Map(x =>
{
x.ToTable("NameOfYourTable");
x.MapLeftKey("MaterialId");
x.MapRightKey("ProductionStepLogId");
});
}
AFAIK, this is impossible with Data Annotations, but it is possible with configuration fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<E1Type>()
.HasMany(e1 => e1.Collection)
.WithMany(e2 => e2.Collection)
.Map(config => config.ToTable("MyTable"));
}

Categories