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"));
Related
I want map Origin.CityId and Origin.StateId properties of
Itinerary class to OriginCityId and OriginStateId properties
of ItineraryModel class.
Ex: Itinerary itinerary = Mapper.Map<Itinerary>(ItineraryModel);
My ViewModel
public class ItineraryModel : BaseModel
{
public int OriginCityId { get; set; }
public int OriginStateId { get; set; }
public bool Published { get; set; }
}
My Entity
public class Itinerary : BaseEntity
{
public City Origin { get; set; }
public bool Published { get; set; }
}
My mapping that tried do
public class MappingProfile : Profile
{
public MappingProfile()
{
CreateMap<ItineraryModel, Itinerary>()
.ForPath(x => x.Origin.CityId, opt => opt.MapFrom(src => src.OriginCityId))
.ForPath(x => x.Origin.StateId, opt => opt.MapFrom(src => src.OriginStateId))
.ReverseMap();
}
}
I would like to too .ReverseMap() but can't find right syntax.
You need to add two mappings for mapping to Itenerary
CreateMap<ItineraryModel, City>()
.ForMember(city => city.CityId, expression => expression.MapFrom(itineraryModel => itineraryModel.OriginCityId))
.ForMember(city => city.StateId, expression => expression.MapFrom(itineraryModel => itineraryModel.OriginStateId));
CreateMap<ItineraryModel, Itinerary>()
.ForMember(itinerary => itinerary.Origin, expression => expression.MapFrom(itineraryModel => itineraryModel));
Similarly you can define reverse mappings manually if needed.
BTW ReverseMap() is not recommended by author
https://jimmybogard.com/automapper-usage-guidelines/
I have a table called contracts which has the id of entity 1 and entity 2. I use this to create links between entities, however I can't seem to get the definition of the dbContext correct. I can get the contracts to load but only one entity. When I view the contract either Entity1 is loaded or Entity2 is loaded but never both.
The models look like this:
public class Contract
{
public int Entity1ID { get; set; }
public int Entity2ID { get; set; }
public Entity Entity1 { get; set; }
public Entity Entity2 { get; set; }
}
My entity class looks like this
public class Entity
{
public int ID { get; set; }
public ICollection<Contract> Contracts1 { get; set; };
public ICollection<Contract> Contracts2 { get; set; };
}
This is my dbContext
public class EntityDbContext : DbContext
{
public EntityDbContext(DbContextOptions<EntityDbContext> options)
: base(options)
{
}
public DbSet<Models.Entity> Entities { get; set; }
public DbSet<Models.Contract> Contracts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID);
}
}
From your model design , there is a self-referencing many-to-many relationship in Entity model , so you should change DeleteBehavior to Restrict(the default is cascade) like below:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Entity>()
.HasKey(n => n.ID);
modelBuilder.Entity<Contract>()
.HasKey(n => new { n.Entity1ID, n.Entity2ID });
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity1)
.WithMany(n => n.Contracts1)
.HasForeignKey(n => n.Entity1ID)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Contract>()
.HasOne(n => n.Entity2)
.WithMany(n => n.Contracts2)
.HasForeignKey(n => n.Entity2ID)
.OnDelete(DeleteBehavior.Restrict);
}
For loading related data , you could use the Include method that Sharif suggested above to specify related data to be included in query results
var result = _context.Contracts
.Include(c => c.Entity1)
.Include(c => c.Entity2)
.ToList();
Result
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
I have an entity which has a self reference such that a Member can have a Witness who has to be a member and may also have a Reference who has to be a member. I modeled this as follows;
public class Member
{
public int Id { get; set; }
//omitted for brevity
public int? WitnessId { get; set; }
public virtual Member Witness { get; set; }
public int? ReferenceId { get; set; }
public virtual Member Reference { get; set; }
}
When I run the update-database on package manager console, I get the following error:
"XXX.Client.Entities.Member' and 'XXX.Client.Entities.Member'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations."
Any idea on how this can be resolved?
Try to define relationship with fluent api this way (works for me):
modelBuilder.Entity<Member>().HasKey(x => x.Id);
modelBuilder.Entity<Member>().HasOptional(x => x.Witness)
.WithMany()
.HasForeignKey(m => m.WitnessId);
modelBuilder.Entity<Member>().HasOptional(x => x.Reference)
.WithMany()
.HasForeignKey(m => m.ReferenceId);
This looks to be working:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Member>().
HasOptional(e => e.Witness).
WithMany().
HasForeignKey(m => m.WitnessID);
modelBuilder.Entity<Member>().
HasOptional(e => e.Reference).
WithMany().
HasForeignKey(m => m.ReferenceID);
base.OnModelCreating(modelBuilder);
}
This will also work for those of us who prefer to have things in the class deriving from the EntityTypeConfiguration
class MemberEntityConfiguration : EntityTypeConfiguration<Member>
{
public MemberEntityConfiguration()
{
HasKey(x => x.Id);
HasOptional(x => x.Witness).WithMany().HasForeignKey(m => m.WitnessId);
HasOptional(x => x.Reference).WithMany().HasForeignKey(m => m.ReferenceId);
}
}
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.