Many to Many mapping not working - EF 4.1 RC - c#

UPDATE: After a bit more research it seems a number of my many-to-many mappings aren't working. Hmmm...
I'm upgrading a data access project from EF 4.1 CTP4 to EF 4.1 RC and I'm having trouble with the new EntityTypeConfiguration<T> setup.
Specifically I'm having an issue with a Many-to-Many relationship. I'm getting a Sequence contains no elements exception when I'm trying to get the .First() item.
The particular exception isn't really that interesting. All it's saying is that there are no items BUT I know there should be items in the collection - so there must be an issue with my new mappings.
Here's the code I have so far:
Product Model
public class Product : DbTable
{
//Blah
public virtual ICollection<Tag> Categories { get; set; }
public Product()
{
//Blah
Categories = new List<Tag>();
}
}
BaseConfiguration
public class BaseConfiguration<T> : EntityTypeConfiguration<T> where T : DbTable
{
public BaseConfiguration()
{
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
this.Property(x => x.UpdatedOn);
this.Property(x => x.CreatedOn);
}
}
ProductConfiguration
public class ProductConfiguration : BaseConfiguration<Product>
{
public ProductConfiguration()
{
this.ToTable("Product");
//Blah
this.HasMany(x => x.Categories)
.WithMany()
.Map(m =>
{
m.MapLeftKey("Tag_Id");
m.MapRightKey("Product_Id");
m.ToTable("ProductCategory");
});
}
}
Previous CTP4 Mapping the worked!
this.HasMany(x => x.Categories)
.WithMany()
.Map("ProductCategory", (p, c) => new { Product_Id = p.Id, Tag_Id = c.Id });
Can anyone see anything that needs fixing? Let me know if you want me to provide more code.
EDIT: More Code
DbTable
public class DbTable : IDbTable
{
public int Id { get; set; }
public DateTime UpdatedOn { get; set; }
public DateTime CreatedOn { get; set; }
}
Tag
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public string Slug { get; set; }
public bool Visible { get; set; }
public virtual TagType TagType { get; set; }
}
TagConfiguration
public class TagConfiguration : EntityTypeConfiguration<Tag>
{
public TagConfiguration()
{
this.ToTable("Tags");
this.HasKey(x => x.Id);
this.Property(x => x.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity).HasColumnName("tag_id");
this.Property(x => x.Name).HasMaxLength(300).HasColumnName("tag_name");
this.Property(x => x.Slug).HasMaxLength(500).HasColumnName("tag_slug");
this.Property(x => x.Visible).HasColumnName("tag_visible");
this.HasRequired(x => x.TagType).WithMany(tt => tt.Tags).Map(m => m.MapKey("tagtype_id"));
}
}
Yes, this is a legacy database with naming conventions up to boohai.
I know the Tag class must be wired up correctly because Product has another property Specialization which is also mapped to Tag and it loads correctly. But do note that it's mapped in a one-to-many manner. So it seems to be the many-to-many with Tag.
I'll start checking out if any many-to-many associations are working.

You need to specify both navigation properties to do many to many mapping.
Try adding the lambda in the WithMany property pointing back to the products:
this.HasMany(x => x.Categories)
.WithMany(category=>category.Products)
.Map(m =>
{
m.MapLeftKey(t => t.TagId, "Tag_Id");
m.MapRightKey(t => t.ProductId, "Product_Id");
m.ToTable("ProductCategory");
});
(crossing fingers...)

I haven't used the Code-First approach yet, but when working with POCOs I had to enable Lazy-Loading, to make Navigation Properties work. This is of course by design, but I don't know if you have to explicitly enable this behavior for Code-First.

Related

One-to-one relationship with EFCore 2.1

The following code was working with EFCore 2.0.
Since the 2.1 update, I get a blocking bug:
The child/dependent side could not be determined for the one-to-one relationship
between 'Entity2.Main' and 'Entity1.Metadata'.
To identify the child/dependent side of the relationship, configure the foreign key property.
If these navigations should not be part of the same relationship configure them without specifying
the inverse. See http://go.microsoft.com/fwlink/?LinkId=724062 for more details.
The tables are something like (they share the same id, but on different tables):
Table_Entity1:
- Id
- Name
- Description
Table_Entity2:
- Id
- Flag1
- Flag2
Entities are like:
public class Entity1
{
public long Id {get;set;}
public string Name {get;set;}
public string Description {get;set;}
public Entity2 Metadata {get;set;}
}
public class Entity2
{
public long Id {get;set;}
public bool Flag1 {get;set;}
public bool Flag2 {get;set;}
public Entity1 Main {get;set;}
}
They are declared as follow:
builder.Entity<Entity1>(b =>
{
b.HasKey(e => e.Id);
b.Property(e => e.Id).ValueGeneratedNever();
b.HasOne<Entity2>(e => e.Metadata)
.WithOne(e => e.Main)
.HasForeignKey<Entity2>(e => e.Id)
.HasPrincipalKey<Entity1>(e=>e.Id);
b.ToTable("Table_Entity1");
});
builder.Entity<Entity2>(b =>
{
b.HasKey(e => e.Id);
b.ToTable("Table_Entity2");
});
How can I solve this? I have tried all HasOne, WithOne, HasForeignKey combinations, nothing seem to work...
By looking at your models, it seems to me Entity 1 owns Entity 2. Have you followed what's suggested in the Microsoft Document Owned Entity Types section: https://learn.microsoft.com/en-us/ef/core/modeling/owned-entities?
You can try to change the models to:
public class Entity2
{
public bool Flag1 { get; set; }
public bool Flag2 { get; set; }
}
public class Entity1
{
public long Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public Entity2 Metadata { get; set; }
}
Then on the configurations:
builder.Entity<Entity1>(b =>
{
b.HasKey(e1 => e1.Id);
b.OwnsOne(e1 => e1.Metadata, md => {
// I think the example on the Microsoft Doc is wrong but need to verify.
// I opened an issue here:
// https://github.com/aspnet/EntityFramework.Docs/issues/772
md.ToTable("Table_Entity2");
});
b.ToTable("Table_Entity1");
});
Disclaim: I wrote anything by hand hence they're not tested.
I have solved it by adding OwnsOne:
builder.Entity<Entity1>(b =>
{
b.HasKey(e => e.Id);
b.Property(e => e.Id).ValueGeneratedNever();
b.OwnsOne<Entity2>(e => e.Metadata);
b.HasOne<Entity2>(e => e.Metadata)
.WithOne(e => e.Main)
.HasForeignKey<Entity2>(e => e.Id);
b.ToTable("Table_Entity1");
});
builder.Entity<Entity2>(b =>
{
b.HasKey(e => e.Id);
b.ToTable("Table_Entity2");
});

How to join information whose relationship is repeated between two tables using EF Core

I have a double relationship between the Person and PersonCompany tables where a Person can be any individual or legal person or a Person who is registered as a Company.
When I need to fetch a Person (Person table) with Id 2 from the bank, the EF should return the People and PersonsCompany that relate to the Person table, but this is not happening ... I believe the problem occurs because the Person and PersonCompany properties are from same type as Person. This makes EF understand that they are the same thing and returns values ​​that do not match the related PersonCompany.
Do I have to do some sort of "Select" within the PersonsCompan navigation property? Does anyone know how to help me?
//Get value of table Person
public Pessoa GetById(int id)
{
return DbSet
.Include(pe => pe.Persons)
.ThenInclude(p => p.Person)
.Include(pe => pe.PersonsCompany)
.ThenInclude(pe => pe.PersonCmpany)
//(... other related tables )
.FirstOrDefault(x => x.PersonId == id);
}
public void Configure(EntityTypeBuilder<PersonEntity> builder)
{
builder.ToTable("PersonEntity");
builder.HasKey(pg => new { pg.PersonId, pg.PersonType});
builder
.HasOne(p => p.Person)
.WithMany(pg => pg.Persons)
.HasForeignKey(pg => pg.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder.Property(pg => pg.PersonId)
.HasColumnName("PersonId")
.HasColumnType("integer")
.IsRequired();
builder.Property(pg => pg.PersonType)
.HasColumnName("PersonTypeId")
.HasColumnType("integer")
.IsRequired();
builder.Property(pg => pg.IdGeneral)
.HasColumnName("IdGeneral")
.HasColumnType("integer")
.IsRequired();
builder
.HasOne(f => f.PersonCompany)
.WithMany(pg => pg.PersonsCompany)
.HasForeignKey(pg => pg.PersonCompanyId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder.Property(pg => pg.PersonCompanyId)
.HasColumnName("PersonCompanyId")
.HasColumnType("integer")
.IsRequired();
}
public class Person : Entity
{
public virtual ICollection<PersonEntity> Persons { get; private set; }
public virtual ICollection<PersonEntity> PersonsCompany { get; private set; }
}
public class PersonEntity
{
public int Id { get; private set; }
public int PersonId { get; private set; }
public int PersonCompanyId { get; private set; }
public virtual PersonType PersonType { get; private set; }
public virtual Person Person { get; private set; }
public virtual Person PersonCompany { get; private set; }
}
If I understand correctly, the problem is similar to Entity Framework Core: many-to-many self referencing relationship, so is the solution.
The confusion in your case comes from the collection navigation property names and their mappings to the reference navigation properties:
Person.Persons -> PersonEntity.Person
and
Person.PersonsCompany -> PersonEntity.PersonCompany
You should probably rename them like this:
Person.PersonCompanies -> PersonEntity.Person
and
Person.CompanyPersons -> PersonEntity.PersonCompany
so the other reference navigation property represents the intended link.
E.g.
Model:
public class Person : Entity
{
public virtual ICollection<PersonEntity> PersonCompanies { get; private set; }
public virtual ICollection<PersonEntity> CompanyPersons { get; private set; }
}
Relationship configuration:
builder
.HasOne(p => p.Person)
.WithMany(pg => pg.PersonCompanies)
.HasForeignKey(pg => pg.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull);
builder
.HasOne(f => f.PersonCompany)
.WithMany(pg => pg.CompanyPersons)
.HasForeignKey(pg => pg.PersonCompanyId)
.OnDelete(DeleteBehavior.ClientSetNull);
Usage:
.Include(p => p.PersonCompanies)
.ThenInclude(pe => pe.PersonCompany)
.Include(p => p.CompanyPersons)
.ThenInclude(pe => pe.Person)

EF Core - Many to many relationship on a class

User-Friend relationship
I find an answer
Entity Framework Core: many-to-many relationship with same entity
and try like this.
Entitys:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
public class Friend
{
public int MainUserId { get; set; }
public User ManUser { get; set; }
public int FriendUserId { get; set; }
public User FriendUser { get; set; }
}
The fluent API:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.ManUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
When I Add-Migration, the error message is
Cannot create a relationship between 'User.Friends' and 'Friend.FriendUser', because there already is a relationship between 'User.Friends' and 'Friend.ManUser'.
Navigation properties can only participate in a single relationship.
What should I do? Or I should create an Entity FriendEntity:User?
The problem is that you can't have one collection to support both one-to-many associations. Friend has two foreign keys that both need an inverse end in the entity they refer to. So add another collection as inverse end of MainUser:
public class User
{
public int UserId { get; set; }
public virtual ICollection<Friend> MainUserFriends { get; set; }
public virtual ICollection<Friend> Friends { get; set; }
}
And the mapping:
modelBuilder.Entity<Friend>()
.HasKey(f => new { f.MainUserId, f.FriendUserId });
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany(mu => mu.MainUserFriends)
.HasForeignKey(f => f.MainUserId).OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany(mu => mu.Friends)
.HasForeignKey(f => f.FriendUserId);
One (or both) of the relationships should be without cascading delete to prevent multiple cascade paths.
It's not mandatory the second collection. You only need to left de .WithMany() empty like this:
modelBuilder.Entity<Friend>()
.HasOne(f => f.MainUser)
.WithMany()
.HasForeignKey(f => f.MainUserId);
modelBuilder.Entity<Friend>()
.HasOne(f => f.FriendUser)
.WithMany()
.HasForeignKey(f => f.FriendUserId);
look at this : https://github.com/aspnet/EntityFramework/issues/6052

Unable to query over many-to-many relationship

does anybody know how to query DB in EF Core for many-to-many relationship, but more like left outer join from one side?
Let me explain what I mean.
Currency.cs
public class Currency
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid UID { get; set; } = Guid.NewGuid();
public string ISOCode { get; set; }
public string Symbol { get; set; }
[JsonIgnore]
public List<RegionCurrency> RegionCurrencies { get; set; }
}
RegionCurrency.cs
public class RegionCurrency
{
public Guid CurrencyUID { get; set; }
public Guid RegionUID { get; set; }
[ForeignKey("CurrencyUID")]
public Currency Currency { get; set; }
[ForeignKey("RegionUID")]
public Region Region { get; set; }
}
Region.cs
public class Region
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid UID { get; set; } = Guid.NewGuid();
[StringLength(8)]
public string CountryISOCode { get; set; }
public List<RegionCurrency> RegionCurrencies { get; set; }
}
MyContext.cs
public class LookupTablesContext : DbContext
{
public virtual DbSet<Currency> Currecies { get; set; }
public virtual DbSet<RegionCurrency> RegionCurrency { get; set; }
public virtual DbSet<Region> Regions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema(SchemaName);
modelBuilder.Entity<RegionCurrency>()
.HasKey(t => new { t.CurrencyUID, t.RegionUID })
.HasName("PK_RegionCurrency");
modelBuilder.Entity<RegionCurrency>()
.HasOne(pt => pt.Region)
.WithMany(p => p.RegionCurrencies)
.HasForeignKey(pt => pt.RegionUID);
modelBuilder.Entity<RegionCurrency>()
.HasOne(pt => pt.Currency)
.WithMany(p => p.RegionCurrencies)
.HasForeignKey(pt => pt.CurrencyUID);
modelBuilder.Entity<Currency>()
.HasIndex(c => c.ISOCode)
.HasName("UX_Currency_ISOCode")
.IsUnique();
modelBuilder.Entity<Region>()
.HasIndex(c => c.CountryISOCode)
.HasName("UX_Region_CountryISOCode")
.IsUnique();
}
}
My query:
var result = ctx.Currencies
.Include(c => c.RegionCurrencies)
.ThenInclude(rc => rc.Select(rcs => rcs.Regions)) // This seems to be wrong
.SingleOrDefault(c => c.ISOCode == "EUR");
I also tried to use includes as you can see below on the picture:
Please note, that RegionCurrencies table can contain 0-N relations and I want to get Currency entity even there's no record in RegionCurrency table.
This (and similar tries) ended up in exception like this:
An exception of type 'System.ArgumentException' occurred in Microsoft.EntityFrameworkCore.dll but was not handled in user code
Additional information: The property expression 'rc => {from RegionCurrency rc in rcs select [pts].Regions}' is not valid. The expression should represent a property access: 't => t.MyProperty'. For more information on including related data, see http://go.microsoft.com/fwlink/?LinkID=746393.
Dependencies:
"Microsoft.EntityFrameworkCore": "1.0.1",
"Microsoft.EntityFrameworkCore.SqlServer": "1.0.1",
I cannot find any working example. But certainly I'm just blind.
Thanks for any help.
You can do it as shown below.
var tag = ctx.Tags.Include(t => t.PostTags)
.ThenInclude(p => p.Post).FirstOrDefault(d => d.TagId == "2");
var posts = tag.PostTags.Select(c => c.Post).ToList();
Note : Sometimes VS doesn't show intellisense properly. So beware of intellisense :D . One solution may be for that is: close VS and start a new instance of it.
For example : intellisense is working fine for this .Include(t => t.PostTags).But beware on this .ThenInclude(p => p.Post).You have to write it without relying on intellisense. Hope Microsoft will fix this issue on the future releases of VS.
Result :
value of tag :
values of posts :
Test data :
Update :
It's working.Please see the code.
var currency = db.Currecies.Include(t => t.RegionCurrencies)
.ThenInclude(p => p.Region)
.FirstOrDefault(t => t.UID == Guid.Parse("0f8fad5b-d9cb-469f-a165-70867728950e"));
var regions = currency.RegionCurrencies.Select(c => c.Region).ToList();
Result :
value of currency :
values of regions :
Git Repo : EfCoreManyToMany

EF Optional One-To-One Within Itself

I'm new to Entity Framework and have come across a problem while trying to map my entity.
Basically I have a Location entity which can have an optional parent location. So what I'd like on my Location object is to have a collection of child locations along with the parent of the current location. Below is my current Location entity:
public class Location : BaseEntity
{
private ICollection<Location> _childLocations;
public virtual ICollection<Location> ChildLocations
{
get { return _childLocations ?? (_childLocations = new List<Location>()); }
set { _childLocations = value; }
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual Location ParentLocation { get; set; }
}
However, when it comes to mapping this, I'm getting pretty lost. The below is my attempt so far:
public partial class LocationMap : EntityTypeConfiguration<Location>
{
public LocationMap()
{
this.ToTable("Location");
this.HasKey(l => l.Id);
this.Property(l => l.Name).HasMaxLength(100);
this.HasMany(l => l.ChildLocations)
.WithMany()
.Map(m => m.ToTable("Location"));
this.HasOptional(l => l.ParentLocation)
.WithOptionalDependent()
.Map(m => m.ToTable("Location"));
}
}
Could anyone point me in the right direction?
You want something like:
this.HasOptional(l => l.ParentLocation)
.WithMany(l => l.ChildLocations)
.Map(m => m.ToTable("Location"));
But not two declarations of the relationship, ie the above replaces both of the below in your example
this.HasMany(l => l.ChildLocations)
.WithMany()
.Map(m => m.ToTable("Location"));
this.HasOptional(l => l.ParentLocation)
.WithOptionalDependent()
.Map(m => m.ToTable("Location"));

Categories

Resources