How to refer two tables, using a column in another table - Entity Framework - asp.net

As per the image above, I have a requirement where, Table C needs to refer both Table A and Table B.
Note: RefId in Table_C is a reference key for both Tables A and B.
Please, refer the code snippets,
Table_A Class
public partial class Table_A
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Table_C> Table_C { get; set; }
}
Table_B Class
public partial class Table_B
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Table_C> Table_C { get; set; }
}
Table_C Class
public partial class Table_C
{
public int Id { get; set; }
public int RefId { get; set; }
public Type Type {get; set; }
public virtual Table_A Table_A { get; set; }
public virtual Table_B Table_B { get; set; }
}
Fluent API
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Table_1>()
.Property(e => e.Name)
.IsFixedLength();
modelBuilder.Entity<Table_1>()
.HasMany(e => e.Table_3)
.WithRequired(e => e.Table_1)
.HasForeignKey(e => e.RefId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Table_2>()
.Property(e => e.Name)
.IsFixedLength();
modelBuilder.Entity<Table_2>()
.HasMany(e => e.Table_3)
.WithRequired(e => e.Table_2)
.HasForeignKey(e => e.RefId)
.WillCascadeOnDelete(false);
}
However, the below error occurs when trying to achieve this requirement using the code shown above.
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_dbo.TableC.TableA_Id". The conflict occurred in database "TestDB",
table "dbo.TableA", column 'Id'. The statement has been terminated
How can this be implemented using Entity Framework 6 (SQL Server 2014, .NET framework 4.6.1)?

Seems to work fine for me:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Data.Entity;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Ef6Test
{
public partial class Table_A
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Table_C> Table_C { get; } = new HashSet<Table_C>();
}
//Table_B Class
public partial class Table_B
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Table_C> Table_C { get; } = new HashSet<Table_C>();
}
//Table_C Class
public partial class Table_C
{
public int Id { get; set; }
public int RefId { get; set; }
public string Type { get; set; }
public virtual Table_A Table_A { get; set; }
public virtual Table_B Table_B { get; set; }
}
class Db: DbContext
{
public DbSet<Table_A> Table_A { get; set; }
public DbSet<Table_B> Table_B { get; set; }
public DbSet<Table_C> Table_C { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Table_A>()
.HasMany(e => e.Table_C)
.WithRequired(e => e.Table_A)
.HasForeignKey(e => e.RefId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Table_B>()
.HasMany(e => e.Table_C)
.WithRequired(e => e.Table_B)
.HasForeignKey(e => e.RefId)
.WillCascadeOnDelete(false);
}
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<Db>());
using (var db = new Db())
{
db.Database.Log = m => Console.WriteLine(m);
db.Database.Initialize(true);
var a = new Table_A();
var b = new Table_B();
var c = new Table_C();
a.Table_C.Add(c);
b.Table_C.Add(c);
db.Table_A.Add(a);
db.Table_B.Add(b);
db.Table_C.Add(c);
db.SaveChanges();
}
Console.ReadKey();
}
}
}

I managed to fulfill this requirement using the comments made by #David Browne - Microsoft. Therefore, usage of multiple columns to refer multiple related tables is the approach for this sort of a scenario. Hope this will help anyone looking for an answer to a question of this nature. Thanks #David Browne - Microsoft for the valuable input.

Related

Entity Framework Core 3.1 Code-First Model - Define model of having multiple many to many relation with self

I like to create a DB model in ASP.Net Core 3.1. I am using Code First approach with EF Core 3.1.
I like to create a model for this relationship-
So, there is one Employee table and every employee has multiple bosses and each has multiple sub-ordinates. But every boss and every subordinate are employees also. What I have done is something like this-
Employee Model-
public class Employee
{
[HiddenInput(DisplayValue = false), Display(Name = "ID")]
[Key()]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column(Order = 1)]
public Guid? Id { get; set; } = Guid.NewGuid();
[Column("Name"), Required(ErrorMessage = "Term Name should be given"), Display(Name = "Term Name", Prompt = "Please Give Term Name")]
[DataType(DataType.Text)]
public string Name { get; set; }
public ICollection<Boss> Bosses { get; set; }
public ICollection<Subordinate> Subordinates { get; set; }
............
............
}
But I am getting this error during creating the DB model by the command Add-Migration <MigrationName>-
Unable to determine the relationship represented by navigation property 'Employee.Bosses' of type 'ICollection'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Can anyone please help?
direct many-to-many relations are not supported with ef core 3.1.
See this: https://learn.microsoft.com/de-de/ef/core/what-is-new/ef-core-5.0/whatsnew
If you cannot use ef core >= 5, than you have to create a navigation property to the EmployeeBoss entity.
Try this:
public partial class Employee
{
public Employee()
{
EmployeeBossEmployees = new HashSet<EmpoyeeBoss>();
EmployeeBossBosses = new HashSet<EmpoyeeBoss>();
}
[Key]
public int Id { get; set; }
[InverseProperty(nameof(EmpoyeeBoss.Employee))]
public virtual ICollection<EmpoyeeBoss> EmployeeBossEmployees { get; set; }
[InverseProperty(nameof(EmpoyeeBoss.Boss))]
public virtual ICollection<EmpoyeeBoss> EmployeeBossBosses { get; set; }
}
public partial class EmpoyeeBoss
{
[Key]
public int Id { get; set; }
public int BossId { get; set; }
public int EmployeeId { get; set; }
[ForeignKey(nameof(EmployeeId))]
[InverseProperty("EmployeeBossEmployees")]
public virtual Employee Employee { get; set; }
[ForeignKey(nameof(BossId))]
[InverseProperty("EmployeeBossBosses")]
public virtual Employee Boss { get; set; }
}
and include in your dbcontext:
public virtual DbSet<Employee> Employees { get; set; }
public virtual DbSet<EmpoyeeBoss> EmployeeBosses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<EmpoyeeBoss>(entity =>
{
entity.HasOne(d => d.Employee)
.WithMany(p => p.EmployeeBossEmployees)
.HasForeignKey(d => d.EmployeeId)
.OnDelete(DeleteBehavior.ClientSetNull);
entity.HasOne(d => d.Boss)
.WithMany(p => p.EmployeeBossBosses)
.HasForeignKey(d => d.BossId);
});

No suitable constructor for entity type 'LineString'

Trying to migrate integration tests from the in-memory database to the PostGIS one, I always catch that exception when I call EnsureDeleted or EnsureCreated methods:
System.InvalidOperationException
No suitable constructor found for entity type 'LineString'. The following constructors had parameters that could not be bound to properties of the entity type: cannot bind 'points' in 'LineString(Coordinate[] points)'; cannot bind 'points', 'factory' in 'LineString(CoordinateSequence points, GeometryFactory factory)'.
With in-memory tests, all is working fine.
The only model with spatial data is this:
public class SurveilledArea
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[Column(TypeName = "geometry (polygon)")]
public Polygon Polygon { get; set; }
public string AvigilonAlertId { get; set; }
public int MunicipalityId { get; set; }
public virtual Municipality Municipality { get; set; }
}
and my DbContext is this
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> dbContextOptions) : base(dbContextOptions)
{
}
public virtual DbSet<SurveilledArea> Areas { get; set; }
public virtual DbSet<Municipality> Municipalities { get; set; }
public virtual DbSet<Report> Reports { get; set; }
public virtual DbSet<Device> Devices { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("postgis");
modelBuilder.Entity<SurveilledArea>()
.HasOne(p => p.Municipality)
.WithMany(p => p.SurveilledAreas)
.HasForeignKey(p => p.MunicipalityId)
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Report>()
.HasOne(p => p.Device)
.WithMany(p => p.Reports)
.HasForeignKey(p => new { p.DeviceType, p.DeviceAdId })
.OnDelete(DeleteBehavior.Cascade);
modelBuilder.Entity<Municipality>()
.HasIndex(p => p.ShortCode)
.IsUnique();
modelBuilder.Entity<Device>()
.HasKey(device => new {device.Type, device.AdvertisingId});
base.OnModelCreating(modelBuilder);
}
}
What am I doing wrong?
I had a similar problem with a class that contained a Polygon property
public NetTopologySuite.Geometries.Polygon Polygon { get; set; }
You may need to add a package ref to Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite
Then in your data context builder implement something like this
public static MyDataContext CreateContext(string sqlConnectionStr) {
var optionsBuilder = new DbContextOptionsBuilder<MyDataContext>();
optionsBuilder.UseSqlServer(sqlConnectionStr, x => x.UseNetTopologySuite()
return new MyDataContext(optionsBuilder.Options);
}

Many to many relation between Identity and custom table. EF7 - Code first

How can I make many to many relation between AspNetRoles from Identity 3.0 and my custom table? I want simple 3 table, with both PermissionId and RoleId, something like AspNetUsersRole. I have something like this:
public class Permission
{
public int PermissionId { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationRole> Roles { get; set; }
}
public class ApplicationRole : IdentityRole
{
public virtual ICollection<Permission> Permissions { get; set; }
}
But when I want to add migration, I got error:
Unable to determine the relationship represented by navigation property 'ApplicationRole.Permissions' of type 'ICollection<Permission>'. Either manually configure the relationship, or ignore this property from the model.
EF Core (EF7) does not currently support many to many relationship without a join entity. (Reference)
So, what you should do is to create an entity class for the join table and mapping two separate one-to-many relationships. Like;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PostTag>()
.HasKey(t => new { t.PostId, t.TagId });
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Post)
.WithMany(p => p.PostTags)
.HasForeignKey(pt => pt.PostId);
modelBuilder.Entity<PostTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.PostTags)
.HasForeignKey(pt => pt.TagId);
}
public class PostTag
{
public int PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Regarding to this question answer, it can be done more easily like this-
class Photo
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
class PersonPhoto
{
public int PhotoId { get; set; }
public Photo Photo { get; set; }
public int PersonId { get; set; }
public Person Person { get; set; }
}
class Person
{
public int Id { get; set; }
public ICollection<PersonPhoto> PersonPhotos{ get; set; }
}
Be sure to configure PersonPhoto with a composite key:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonPhoto>().HasKey(x => new { x.PhotoId, x.PersonId });
}
To navigate, use a Select:
// person.Photos
var photos = person.PersonPhotos.Select(c => c.Photo);
Add This namespace-
using Microsoft.AspNetCore.Identity;
public class Permission
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int PermissionId { get; set; }
public string Name { get; set; }
public string UserIdFK { get; set; } //Foreign Key of Identity tbl
[ForeignKey("UserIdFK")]
public IdentityUser UserDetail { get; set; }
}
That's it, Happy coding :)

Many to many relationships in EF5 Code First, how can I specify table name?

I'm quite new to EF, and I'm not really sure how to do this.
I have a many-to-many relationship, exactly like this:
When I try to add a resource (Recurso) to a profile (Perfil), I get the following error:
Invalid object name 'dbo.RecursoPerfils
Where the hell did RecursoPerfils come from?
How can I specify (preferably through attribute annotation) the table name for this relationship?
See the models below:
[Table("Perfil")]
public class Perfil
{
public Perfil()
{
this.Usuarios = new List<Usuario>();
this.Recursos = new List<Recurso>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int IdPerfil { get; set; }
[Required]
public string Descricao { get; set; }
public virtual ICollection<Usuario> Usuarios { get; set; }
public virtual ICollection<Recurso> Recursos { get; set; }
}
[Table("Recurso")]
public class Recurso
{
public Recurso()
{
this.Perfis = new List<Perfil>();
}
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int IdRecurso { get; set; }
[Required]
public string NomeRecurso { get; set; }
[Required]
public string Descricao { get; set; }
public virtual ICollection<Perfil> Perfis { get; set; }
}
You need to use Fluent API to configure the table name of the join table.
public class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Perfil>()
.HasMany(p => p.Recursos)
.WithMany(r => r.Perfis)
.Map(mc =>
{
mc.MapLeftKey("IdPerfil");
mc.MapRightKey("IdRecurso");
mc.ToTable("PerfilRecurso");
});
}
}
You can go through this Fluent API relationship mapping tutorial for more info

EF 4.1 RC Code First - Mapping to existing database & specifying foreign key name

I have two classes. A Company has a County set against it:
public class Company
{
public int Id { get; set; }
public string CompanyName { get; set; }
public Country HomeCountry { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
I am trying to map to an existing database where the Company table contains the foreign key of the Country record. So I presumably need to tell code first the name of the foreign key column.
Below is the complete code example. It's currently failing with different exceptions based on different things that I try. There's seems to be a lack of cohesive documentation on this as yet.
So using Code First Fluent API how do I define the name of the foreign key column?
Test app:
Create database as follows:
CREATE DATABASE CodeFirst;
GO
Use CodeFirst
create table Companies
(
Id int identity(1,1) not null,
HomeCountryId int not null,
Name varchar(20) not null,
constraint PK_Companies primary key clustered (Id)
)
create table Countries
(
Id int identity(1,1) not null
, Code varchar(4) not null
, Name varchar(20) not null
, constraint PK_Countries primary key clustered (Id)
)
alter table Companies
add
constraint FK_Company_HomeCountry foreign key (HomeCountryId)
references Countries (Id) on delete no action
Now run the following C# app:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity.ModelConfiguration;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data;
namespace CodeFirstExistingDatabase
{
class Program
{
private const string ConnectionString = #"Server=.\sql2005;Database=CodeFirst;integrated security=SSPI;";
static void Main(string[] args)
{
// Firstly, add a country record, this works fine.
Country country = new Country();
country.Code = "UK";
country.Name = "United Kingdom";
MyContext myContext = new MyContext(ConnectionString);
myContext.Countries.Add(country);
myContext.Entry(country).State = EntityState.Added;
myContext.SaveChanges();
Console.WriteLine("Saved Country");
// Now insert a Company record
Company company = new Company();
company.CompanyName = "AccessUK";
company.HomeCountry = myContext.Countries.First(e => e.Code == "UK");
myContext.Companies.Add(company);
myContext.Entry(company).State = EntityState.Added;
myContext.Entry(country).State = EntityState.Unchanged;
myContext.SaveChanges();
Console.WriteLine("Saved Company"); // If I can get here I'd he happy!
}
}
public class MyContext
: DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<Country> Countries { get; set; }
public MyContext(string connectionString)
: base(connectionString)
{
Database.SetInitializer<MyContext>(null);
Configuration.LazyLoadingEnabled = false;
Configuration.ProxyCreationEnabled = false;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new CountryConfiguration());
modelBuilder.Configurations.Add(new CompanyConfiguration());
base.OnModelCreating(modelBuilder);
}
}
public class CompanyConfiguration
: EntityTypeConfiguration<Company>
{
public CompanyConfiguration()
: base()
{
HasKey(p => p.Id);
Property(p => p.Id)
.HasColumnName("Id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired();
Property(p => p.CompanyName)
.HasColumnName("Name")
.IsRequired();
ToTable("Companies");
}
}
public class CountryConfiguration
: EntityTypeConfiguration<Country>
{
/// <summary>
/// Initializes a new instance of the <see cref="CountryConfiguration"/> class.
/// </summary>
public CountryConfiguration()
: base()
{
HasKey(p => p.Id);
Property(p => p.Id)
.HasColumnName("Id")
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity)
.IsRequired();
Property(p => p.Code)
.HasColumnName("Code")
.IsRequired();
Property(p => p.Name)
.HasColumnName("Name")
.IsRequired();
ToTable("Countries");
}
}
public class Company
{
public int Id { get; set; }
public string CompanyName { get; set; }
public Country HomeCountry { get; set; }
}
public class Country
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
}
The above fails with the following when saving the country:
Invalid column name 'HomeCountry_Id
Any help would be very much appreciated!!
Thanks, Paul.
public CompanyConfiguration()
{
//...
HasRequired(x => x.HomeCountry).WithMany()
.Map(x => x.MapKey("HomeCountryId"));
}
We are moving a Web Forms app to MVC3 using Code First against an existing db without any problems. Here are 2 sample Models and the DbContext I'm using. prDepartments & prCategories map to tables in the db and ApplicationDBContext matches the connection string in Web.config
The DeptID field in prCategory is a Foreign Key to prDepartment - Everything works great
public class prCategory
{
[Key]
public int CatgID { get; set; }
public int DeptID { get; set; }
[Required(ErrorMessage="Category Description Is Required")]
[DisplayName("Desc Name")]
[CssClass("ui-Field-Name")]
public string Description { get; set; }
public string Route { get; set; }
public string OrderBy { get; set; }
public virtual prDepartment Department { get; set; }
public virtual List<prProduct> prProducts { get; set; }
}
public class prDepartment
{
[Key]
public int DeptID { get; set; }
[Required(ErrorMessage = "Department Description Is Required")]
[RequiredMessage("This is the Required Message")]
public string Description { get; set; }
public string Route { get; set; }
public string OrderBy { get; set; }
public virtual List<prCategory> prCategories { get; set; }
}
public class ApplicationDbContext : DbContext
{
public DbSet<prDepartment> prDepartments { get; set; }
public DbSet<prCategory> prCategories { get; set; }
public DbSet<prProduct> prProducts { get; set; }
}

Resources