EF Core self referential many to many both sides - entity-framework-core

I have an entity that has a many to many onto itself
public class Docket
{
public long Id { get; set; }
public string Name { get; set; }
public virtual List<DocketDocket> RelateDockets{ get; set; }
}
public class DocketDocket
{
public int LeftDocketId { get; set; }
public Docket.Docket LeftDocket { get; set; }
public int RightDocketId { get; set; }
public Docket.Docket RightDocket { get; set; }
}
With the following config
modelBuilder.Entity<Joins.DocketDocket>().HasKey(t => new { t.LeftDocketId, t.RightDocketId });
modelBuilder.Entity<Joins.DocketDocket>().HasOne(pt => pt.LeftDocket).WithMany(t => t.RelatedDockets).HasForeignKey(pt => pt.LeftDocketId);
modelBuilder.Entity<Joins.DocketDocket>().HasOne(pt => pt.RightDocket).WithMany().HasForeignKey(pt => pt.RightDocketId).OnDelete(DeleteBehavior.Restrict);
I then manually create the link in my repo as such
await base.Insert(new Joins.DocketDocket() { LeftDocketId = item.Id, RightDocketId = i.RightDocketId });
This works fine but I need this relationship to be double sided so I add the record for the other side
await base.Insert(new Joins.DocketDocket() { LeftDocketId = i.RightDocketId, RightDocketId = item.Id });
and on this second insert I get
Violation of PRIMARY KEY constraint 'PK_RelatedDockets'. Cannot insert duplicate key in object 'dbo.RelatedDockets'. The duplicate key value is (10791, 10790).
Shouldn't EF have my key as (10790, 10791) for the first entry and then (10791,10790) for the second one and therefore NOT duplicate? If not how can I define a unique key for this type of arrengement?

You need to change your config and need to modify Docker entity.
Config:
modelBuilder.Entity<DocketDocket>().HasKey(t => new { t.LeftDocketId, t.RightDocketId });
modelBuilder.Entity<DocketDocket>().HasOne(m => m.LeftDocket).WithMany(t => t.LeftDockets).HasForeignKey(m => m.LeftDocketId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DocketDocket>().HasOne(m => m.RightDocket).WithMany(t => t.RightDockets).HasForeignKey(m => m.RightDocketId).OnDelete(DeleteBehavior.Restrict);
Entity:
public class Docket {
public int Id { get; set; }
public string Name { get; set; }
public virtual List<DocketDocket> LeftDockets { get; set; } = new List<DocketDocket>();
public virtual List<DocketDocket> RightDockets { get; set; } = new List<DocketDocket>();
public virtual List<DocketDocket> AllDockets => LeftDockets.Union(RightDockets).ToList();
}
Example: https://dotnetfiddle.net/m6tgDk

Related

Configuring multiple one-to-many relations between same entities when the relations propagate deeper

I have 6 classes where the first class Money, goes deep 5 levels with objects. Whatever I try I cannot get this representation, so I hope someone would be kind to offer some help. At least for first 2,3 levels then I can continue.
public class Money
{
public Money()
{
Dollars = new HashSet<MoneyDetails>();
Pesos = new HashSet<MoneyDetails>();
Pounds = new HashSet<MoneyDetails>();
}
public int Id { get; set; }
public virtual ICollection<MoneyDetails> Dollars { get; }
public virtual ICollection<MoneyDetails> Pesos { get; }
public virtual ICollection<MoneyDetails> Pounds { get; }
public string Note { get; }
}
public class MoneyDetails
{
public MoneyDetails()
{
Valuations = new HashSet<Valuations>();
}
public int Id { get; set; }
public string Description { get; set; }
public double Value { get; set; }
public virtual ICollection<Valuation> Valuations { get; set; }
}
public class Valuations
{
public Valuations ()
{
Lows = new HashSet<Deep>();
Highs = new HashSet<Deep>();
}
public int Id { get; set; }
public string Sum { get; set; }
public virtual ICollection<Deep> Lows { get; set; }
public virtual ICollection<Deep> Highs { get; set; }
}
public class Deep
{
public Deep()
{
Shallows = new HashSet<Shallow>();
}
public int Id { get; set; }
public object Data { get; set; }
public virtual ICollection<Shallow> Shallows { get; set; }
}
EDIT :
I'm using Entity Framework Core.
Following is the configuration I tried myself.
You can see below how I started, I just don't know how to go deeper into objects and make relationships between them so they are connected.
public void Configure(EntityTypeBuilder<Money> builder)
{
builder.ToTable("Money");
builder.HasKey(e => e.Id);
builder.HasMany(s => s.Dollars)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
builder.HasMany(s => s.Pesos)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
builder.HasMany(s => s.Pounds)
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
}
You are using -
.WithOne(ad => ad.Money)
.HasForeignKey(i => i.MoneyId);
in your configuration code, but you don't have a Money navigation property or a MoneyId foreign-key property in MoneyDetails.
Since you are not using navigation and foreign-key properties in any of your entity models, I'd suggest not to configure the relations manually. Configure other properties in the Configure method if you need, but do not configure the relations. That way, EF will automatically create nullable foreign-keys in your tables and use them as Shadow Property.
EDIT - A better solution for you :
Even though the suggestion above will create all your tables with all the relations, I'm confused about how you plan to use those relations. For example, in the Money entity model Dollars, Pesos, Pounds are all collections of MoneyDetails. Therefore, all the following queries -
var money = myDbContext.Money.Include(p=> p.Dollars).FirstOrDefault(p=> p.Id == someId);
var money = myDbContext.Money.Include(p=> p.Pesos).FirstOrDefault(p=> p.Id == someId);
var money = myDbContext.Money.Include(p=> p.Pounds).FirstOrDefault(p=> p.Id == someId);
will give you the same result - the Money with the specified Id, with a list of all related MoneyDetails. So, there's no point of having three collection properties and three different relations.
Try the following approach to filter related data (you need EF Core 5.0) -
Create enum to identify the entity type -
public enum MoneyDetailsType { Dollar = 1, Peso = 2, Pound = 3 }
public enum DeepType { High = 1, Low = 2 }
Modify your entity models like -
public class Money
{
public Money()
{
MoneyDetails = new HashSet<MoneyDetails>();
}
public int Id { get; set; }
public string Note { get; }
public virtual ICollection<MoneyDetails> MoneyDetails { get; set; }
}
public class MoneyDetails
{
public MoneyDetails()
{
Valuations = new HashSet<Valuations>();
}
public int Id { get; set; }
public string Description { get; set; }
public double Value { get; set; }
public MoneyDetailsType Type { get; set; } // added - type
public int MoneyId { get; set; } // added - foreign-key
public Money Money { get; set; } // added - navigation property (optional)
public virtual ICollection<Valuations> Valuations { get; set; }
}
public class Valuations
{
public Valuations ()
{
Deeps = new HashSet<Deep>();
}
public int Id { get; set; }
public string Sum { get; set; }
public int MoneyDetailsId { get; set; } // added - foreign-key
public MoneyDetails MoneyDetails { get; set; } // added - navigation property (optional)
public virtual ICollection<Deep> Deeps { get; set; }
}
public class Deep
{
public int Id { get; set; }
public string Data { get; set; }
public DeepType Type { get; set; } // added - type
public int ValuationsId { get; set; } // added - foreign-key
public Valuations Valuations { get; set; } // added - navigation property (optional)
}
Notice, in Deep entity, currently you have Data as of type object which is not allowed. You have to change it to some primitive type. I'm using it as string. I have also omitted the Shallows property since you haven't added the Shallow model.
Your configuration methods should look like -
public void Configure(EntityTypeBuilder<Money> builder)
{
builder.ToTable("Money");
builder.HasMany(p => p.MoneyDetails)
.WithOne(p => p.Money)
.HasForeignKey(p => p.MoneyId);
}
public void Configure(EntityTypeBuilder<MoneyDetails> builder)
{
builder.ToTable("MoneyDetails");
builder.Property(p => p.Type).IsRequired(true).HasConversion(new EnumToStringConverter<MoneyDetailsType>());
builder.HasMany(p => p.Valuations)
.WithOne(p => p.MoneyDetails)
.HasForeignKey(p => p.MoneyDetailsId);
}
public void Configure(EntityTypeBuilder<Valuations> builder)
{
builder.ToTable("Valuations");
builder.HasMany(p => p.Deeps)
.WithOne(p => p.Valuations)
.HasForeignKey(p => p.ValuationsId);
}
public void Configure(EntityTypeBuilder<Deep> builder)
{
builder.ToTable("Deep");
builder.Property(p => p.Type).IsRequired(true).HasConversion(new EnumToStringConverter<DeepType>());
}
If you don't want to include the navigation properties in your entity models, then you can just keep the .WithOne() method empty in your configuration, like -
builder.HasMany(p => p.MoneyDetails)
.WithOne()
.HasForeignKey(p => p.MoneyId);
Now you can query like -
var money = myDbContext.Money
.Include(p=> p.MoneyDetails.Where(r=> r.Type == MoneyDetailsType.Dollar))
.FirstOrDefault(p=> p.Id == someId);
and it will give you the Money with a list of only Dollar type MoneyDetails.

EF6 Ignoring related data

Scenario
public class Product : Entity, IAggregateRoot
{
public string Name { get; set; }
public string Dimension { get; set; }
public decimal Volume { get; set; }
public bool Featured { get; set; }
public Farm Farm { get; set; }
public int FarmId { get; set; }
/// <summary>
/// Sell Price
/// </summary>
public decimal BidPrice { get; set; }
public int QuantityAvaliable { get; set; }
public ICollection<Image> Images { get; set; }
public string Description { get; set; }
public Category Category { get; set; }
public int CategoryId { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class Category : Entity, IAggregateRoot
{
public string Title { get; set; }
public string CategoryImage { get; set; }
public Category Parent { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
Relationship setup
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
HasKey(x => x.Id);
Property(x => x.Created).HasColumnType("DateTime");
Property(x => x.Modified).HasColumnType("DateTime");
Property(x => x.BidPrice).HasColumnType("Decimal");
#region RELATIONSHIP
//BelongsTo
HasRequired(x => x.Farm);
HasRequired(x => x.Category);
HasMany(x => x.Images);
#endregion
}
So I have this two model where I need to bring the data from Product model with Category information
I have checked my database, the data is consistent, the Product record have the FK for the Category record.
but when I try to get Product Data using EF6, the category information doesnt come, I get a null object.
Because of = () =>
{
_product = _repository.Find(p => p.Id == 1, p => p.Category);
};
It should_not_be_bull = () =>
_product.Category.ShouldNotBeNull();
the response from data base is for Category is null. but the record is there.
I had it working properly before. for some random magic reason it just stop working.
THE FIND method
public virtual TEntity Find(Expression<Func<TEntity, bool>> predicate = null, params Expression<Func<TEntity, object>>[] includes)
{
var set = CreateIncludedSet(includes);
return (predicate == null) ?
set.FirstOrDefault() :
set.FirstOrDefault(predicate);
}
the CreateIncludeSet
private IDbSet<TEntity> CreateIncludedSet(IEnumerable<Expression<Func<TEntity, object>>> includes)
{
var set = CreateSet();
if (includes != null)
{
foreach (var include in includes)
{
set.Include(include);
}
}
return set;
}
the CreateSet method
private IDbSet<TEntity> CreateSet()
{
return Context.CreateSet<TEntity>();
}
MY DbContext implementation is here
https://github.com/RobsonKarls/FreedomWebApi/blob/dev/Source/Freedom.Infrastructure.DataAccess/Factories/FreedomDbContext.cs
all project is there too for further analisys
any help is valuable.
Thank you
The problem in your code is in this line in CreateIncludedSet method:
set.Include(include);
Yes, you include the data but you do not change you set. You should change it to something like:
set = set.Include(include);
Your code is a bit unclear, but try something like this....
_product = _repository.Include(p => p.Category).SingleOrDefault(x => x.Id == 1);
also see...
https://stackoverflow.com/a/7348694/6200410

EF Code First 1 to 1 mapping using Fluent API

How am I going to create the mapping using fluent API if the database architecture is something like this?
public class Users
{
public Users()
{
PermanentAddresses = new PermanentAddresses();
TemporaryAddresses = new TemporaryAddresses();
}
public int Id { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public virtual PermanentAddresses PermanentAddresses { get; set; }
public virtual TemporaryAddresses TemporaryAddresses { get; set; }
}
public class PermanentAddresses
{
// Primary Key and Foreign Key from Users class
public string Id { get; set; }
// Primary Key and Foreign Key from Addresses class
public int AddressId { get; set; }
public virtual Users Users { get; set; }
public virtual Addresses Addresses { get; set; }
}
public class TemporaryAddresses
{
// Primary Key and Foreign Key from Users class
public string Id { get; set; }
// Primary Key and Foreign Key from Addresses class
public int AddressId { get; set; }
public virtual Users Users { get; set; }
public virtual Addresses Addresses { get; set; }
}
public class Addresses
{
public Addresses()
{
PermanentAddresses = new PermanentAddresses();
TemporaryAddresses = new TemporaryAddresses();
Company = new Company();
}
public int Id { get; set; }
public string CompleteAddress { get; set; }
public virtual PermanentAddresses PermanentAddresses { get; set; }
public virtual TemporaryAddresses TemporaryAddresses { get; set; }
public virtual Company Company { get; set; }
}
I am getting this error.
Unable to determine the principal end of an association between the types 'PermanentAddresses' and 'Addresses'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
Your pluralization makes it seem like you are dealing with collections, but that aside you can try something like:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PermanentAddresses>()
.HasRequired(pa => pa.Users)
.WithRequiredDependent(u => u.PermanentAddresses);
modelBuilder.Entity<TemporaryAddresses>()
.HasRequired(pa => pa.Users)
.WithRequiredDependent(u => u.TemporaryAddresses);
modelBuilder.Entity<PermanentAddresses>()
.HasRequired(pa => pa.Addresses)
.WithRequiredDependent(u => u.PermanentAddresses);
modelBuilder.Entity<TemporaryAddresses>()
.HasRequired(pa => pa.Addresses)
.WithRequiredDependent(u => u.TemporaryAddresses);
}
https://www.safaribooksonline.com/library/view/programming-entity-framework/9781449317867/ch04s07.html
Thank you for your reply.
I added this config
modelBuilder.Entity<PermanentAddresses>().ToTable("PermanentAddresses", "user");
modelBuilder.Entity<PermanentAddresses>().HasKey(x => new { x.Id, x.AddressId });
modelBuilder.Entity<TemporaryAddresses>().ToTable("TemporaryAddresses", "user");
modelBuilder.Entity<TemporaryAddresses>().HasKey(x => new { x.Id, x.AddressId });
Kindly check this
generated Tables and Columns
I want to point the "Addresses_Id" to "AddressId"
and the "Users_Id" to "Id"
However, if I try to add
.Map(p => p.MapKey("Id"));
.Map(p => p.MapKey("AddressId"));
like this
modelBuilder.Entity<PermanentAddresses>().HasRequired(pa => pa.Users).WithRequiredDependent(u => u.PermanentAddresses).Map(p => p.MapKey("Id"));
modelBuilder.Entity<PermanentAddresses>().HasRequired(pa => pa.Addresses).WithRequiredDependent(u => u.PermanentAddresses).Map(p => p.MapKey("AddressId"));
modelBuilder.Entity<TemporaryAddresses>().HasRequired(pa => pa.Users).WithRequiredDependent(u => u.TemporaryAddresses).Map(p => p.MapKey("Id"));
modelBuilder.Entity<TemporaryAddresses>().HasRequired(pa => pa.Addresses).WithRequiredDependent(u => u.TemporaryAddresses).Map(p => p.MapKey("AddressId"));
I get an error.
One or more validation errors were detected during model generation:
AddressId: Name: Each property name in a type must be unique. Property name 'AddressId' is already defined.
Id: Name: Each property name in a type must be unique. Property name 'Id' is already defined.
AddressId: Name: Each property name in a type must be unique. Property name 'AddressId' is already defined.
Id: Name: Each property name in a type must be unique. Property name 'Id' is already defined.

How to join two model and display them in view in mvc 3.0 EF 5

I have two tables which have primary and foriegn key concept. I want to get the combined data on behalf of those keys. i don't know how to bind both the table into single model and display it into view.
Model
public class TVSerialModel
{
public Int32 Serial_ID { get; set; } // primary key
public string Serial_Name { get; set; }
public int? Release_Year { get; set; }
}
public class TVSerialEpisodeModel
{
public Int64 Video_ID { get; set; }
public Int32 Serial_ID { get; set; }// foriegn key
public string Episode_Name { get; set; }
public string Description { get; set; }
public DateTime Uploaded_Time { get; set; }
}
public class TVSerial_Episode_VM
{
public IEnumerable<TVSerialEpisodeModel> tvserialEpisode { get; set; }
public IEnumerable<TVSerialModel> Tvserial { get; set; }
}
Controller
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.
Join(tvContext.dbTvSerials, p => p.Serial_ID, r => r.Serial_ID,(p, r) => new { p, r }).
Select(o => new TVSerial_Episode_VM
{ ****what should i write here to get all columns from both table**** }).
Take(9).ToList();
return View(tves);
}
Expected Result
If TVSerialEpisode has a property TVSerial, you can just dot through your foreign keys.
cDBContext.dbTvSerialEpisode
.Select(t =>
new {
t.TVSerial.Serial_ID,
t.TVSerial.Serial_Name,
t.Episode_Name
})
.Take(9)
.ToList();
You need to improve little bit the models you used with EF. You must include the reference object in model.
Like this
public virtual TVSerialModel TVSerialModel { get; set; }
in main table. This way you can select referred table too.
EF Include
public ActionResult NewEpisodeReleased()
{
cDBContext tvContext = new cDBContext();
TVSerial_Episode_VM tves=new TVSerial_Episode_VM();
tves= tvContext.dbTvSerialEpisodes.Include("TVSerialEpisodeModel")
.Include("TVSerialModel").ToList();
return View(tves);
}

Entity Framework Code First 5 Cascade Delete on many to many tables error

I've this model
public class State
{
public State()
{
this.Promotions = new List<Promotion>();
this.Branches = new List<Branch>();
this.Stores = new List<Store>();
}
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Promotion> Promotions { get; set; }
public virtual ICollection<Store> Stores { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
}
public class Store
{
public Store()
{
this.Promotions = new List<Promotion>();
this.Branches = new List<Branch>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Promotion> Promotions { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
public int StateId { get; set; } // Foreign key
public virtual State State { get; set; } // Navigation Property
}
public class Branch
{
public Branch()
{
this.Promotions = new List<Promotion>();
}
public int Id { get; set; }
public string Name { get; set; }
public int StoreId { get; set; } // Foreign key
public int StateId { get; set; } // Foreign key
public virtual Store Store { get; set; } // Navigation Property
public virtual State State { get; set; } // Navigation Property
public virtual ICollection<Promotion> Promotions { get; set; }
}
public class Promotion
{
public Promotion()
{
this.Stores = new List<Store>();
this.Branches = new List<Branch>();
this.Productos = new List<Producto>();
}
public int Id { get; set; }
public string Name { get; set; }
public int StateId { get; set; }
public virtual ICollection<Store> Stores { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
public virtual ICollection<Product> Products { get; set; }
public virtual State State { get; set; }
}
And this in my context:
// State
modelBuilder.Entity<State>()
.HasMany(p => p.Promotions)
.WithRequired(e => e.State)
.WillCascadeOnDelete(false);
modelBuilder.Entity<State>()
.HasMany(s => s.Branches)
.WithRequired(e => e.State)
.WillCascadeOnDelete(false);
modelBuilder.Entity<State>()
.HasMany(e => e.Stores)
.WithRequired(e => e.State)
.WillCascadeOnDelete(true);
// Store
modelBuilder.Entity<Store>()
.HasMany(b => b.Branches)
.WithRequired(s => s.Store)
.WillCascadeOnDelete(true);
// Many to many
modelBuilder.Entity<Store>().
HasMany(p => p.Promotions).
WithMany(s => s.Stores).
Map(
m =>
{
m.MapLeftKey("StoreId");
m.MapRightKey("PromotionId");
m.ToTable("Store_Promotion");
});
modelBuilder.Entity<Promotion>().
HasMany(e => e.Products).
WithMany(p => p.Promotiones).
Map(
m =>
{
m.MapLeftKey("PromotionId");
m.MapRightKey("ProductoId");
m.ToTable("Promotion_Producto");
});
modelBuilder.Entity<Branch>().
HasMany(p => p.Promotiones).
WithMany(b => b.Branches).
Map(
m =>
{
m.MapLeftKey("BranchId");
m.MapRightKey("PromotionId");
m.ToTable("Branch_Promotion");
});
Now if I turn on more than one WillCascadeOnDelete of the State (first three in the fluent mapping) I get the error
Test method Proj.Data.Tests.UnitTest1.TestPromotion threw exception:
System.Data.SqlClient.SqlException: Introducing FOREIGN KEY constraint 'FK_dbo.Branch_dbo.Store_StoreId' on table 'Branch' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
I know that, and I've read from Julie Lerman's book:
Some databases (including SQL Server) don’t support multiple relationships that specify
cascade delete pointing to the same table
As it happens since the many to many relationship table has cascade delete coming from both related tables.
So, my question is: The only choice here is to turn off cascade delete on parent tables and handle the deletion on relationship table manually? Isn't there any workaround from Entity Framework 5 for this?
Ok, I understood the problem. It is not to have a many to many relationship, the problem is this
State -> Promotion -> PromotionStore
State -> Branch -> BranchPromotion
State -> Store -> StorePromotion
and then Store, Branch and Store have FK to State. So if I delete a State PromotionStore can be reached by 1st and 3rd possibilities.
What I ended up doing is turning off cascade delete for State and deleting the related records manually like this:
public override void Delete(State state)
{
DbContext.Entry(state).Collection(x => x.Promotions).Load();
DbContext.Entry(state).Collection(x => x.Stores).Load();
DbContext.Entry(state).Collection(x => x.Branches).Load();
var associatedPromotions = state.Promotions.Where(p => p.StateId == state.Id);
associatedPromotions.ToList().ForEach(r => DbContext.Set<Promotion>().Remove(r));
var associatedStores = state.Stores.Where(e => e.StateId == state.Id);
associatedStores.ToList().ForEach(e => DbContext.Set<Store>().Remove(e));
var associatedBranches = state.Branches.Where(s => s.StateId == state.Id);
associatedBranches.ToList().ForEach(s => DbContext.Set<Branch>().Remove(s));
base.Delete(state);
}