Entity Framework Core 7 relationship [duplicate] - sql

I am new in EF. And I ran into a problem with creation many-to-many self referencing relation.
I've tried to use solution from: Entity Framework Core: many-to-many relationship with same entity
my entities :
public class WordEntity
{
public long Id { get; set; }
public string Name { get; set; }
public string Json { get; set; }
public virtual List<WordSinonymEntity> Sinonyms { get; set; }
}
public class WordSinonymEntity
{
public long WordId { get; set; }
public virtual WordEntity Word { get; set; }
public long SinonymId { get; set; }
public virtual WordEntity Sinonym { get; set; }
}
and next configuration:
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany(p => p.Sinonyms)
.HasForeignKey(pt => pt.SinonymId);
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);`
but it leads to next exception.
System.InvalidOperationException: 'Cannot create a relationship between 'WordEntity.Sinonyms' and 'WordSinonymEntity.Word', because there already is a relationship between 'WordEntity.Sinonyms' and 'WordSinonymEntity.Sinonym'. Navigation properties can only participate in a single relationship.'
Does anyone can help me or may be suggest some examples to learn ?
Thanks.

The post you are following is definitely wrong.
Every collection or reference navigation property can only be a part of a single relationship. While many to many relationship with explicit join entity is implemented with two one to many relationships. The join entity contains two reference navigation properties, but the main entity has only single collection navigation property, which has to be associated with one of them, but not with both.
One way to resolve the issue is to add a second collection navigation property:
public class WordEntity
{
public long Id { get; set; }
public string Name { get; set; }
public string Json { get; set; }
public virtual List<WordSinonymEntity> Sinonyms { get; set; }
public virtual List<WordSinonymEntity> SinonymOf { get; set; } // <--
}
and specify the associations via fluent API:
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany(p => p.SinonymOf) // <--
.HasForeignKey(pt => pt.SinonymId)
.OnDelete(DeleteBehavior.Restrict); // see the note at the end
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);
Another way is to leave the model as is, but map the WordSinonymEntity.Sinonym to unidirectional association (with refeference navigation property and no corresponding collection navigation property):
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Sinonym)
.WithMany() // <--
.HasForeignKey(pt => pt.SinonymId)
.OnDelete(DeleteBehavior.Restrict); // see the note at the end
modelBuilder.Entity<WordSinonymEntity>()
.HasOne(pt => pt.Word)
.WithMany(t => t.Sinonyms)
.HasForeignKey(pt => pt.WordId);
Just make sure that WithMany exactly matches the presence/absence of the corresponding navigation property.
Note that in both cases you have to turn the delete cascade off for at least one of the relationships and manually delete the related join entities before deleting the main entity, because self referencing relationships always introduce possible cycles or multiple cascade path issue, preventing the usage of cascade delete.

Related

Related table displaying nothing instead of name after attempting to look up name from related table

EDIT:
Changed a couple of classes compared to what they are below to match the Microsoft guide on Relationships.
Devices.cs
public partial class Devices
{
public int DeviceType { get; set; }
...
public DeviceTypes DeviceTypes { get; set; }
}
DeviceTypes.cs
public partial class DeviceTypes
{
public int DeviceType { get; set; }
public string Name { get; set; }
public Devices Devices { get; set; }
}
I then changed my view to start reading off of DeviceTypes.Name.
Index.cshtml
#Html.DisplayFor(modelItem => item.DeviceTypes.Name)
dbContext.cs
modelBuilder.Entity<Devices>(entity =>
{
entity.HasKey(e => e.DeviceId);
...
entity.Property(e => e.DeviceType).HasComment("ID of the type of devices. Primary key in DeviceTypes table.");
...
entity
.HasOne(p => p.DeviceTypes)
.WithOne(p => p.Devices);
});
....
modelBuilder.Entity<DeviceTypes>(entity =>
{
entity.HasKey(e => e.DeviceType);
entity.HasComment("The types of devices.");
entity.Property(e => e.DeviceType)
.HasComment("Id of the device.")
.ValueGeneratedNever();
entity.Property(e => e.Name)
.IsRequired()
.HasMaxLength(50)
.HasComment("Name of the device.");
entity
.HasOne(b => b.Devices)
.WithOne(i => i.DeviceTypes)
.HasForeignKey("DeviceType");
});
I also added to the model builder, but got stuck again not understanding. I've spent a few more hours trying but I can't figure it out. The current error I get is:
InvalidOperationException: You are configuring a relationship between 'DeviceTypes' and 'Devices' but have specified a foreign key on 'DeviceType'. The foreign key must be defined on a type that is part of the relationship.
This is a question about using related tables in my CRUD editors in .NET Core 3.1. I want to use the Name instead of the ID as a display on the index/details/delete pages. And on the create/edit pages I want to use a dropdown to select the value from a list.
There are quite a few examples I have found, but nothing I have tried yet has worked. I had too many different ideas mixed in my code before so I tossed all changes and I am trying again, but got stuck.
I am starting simple with adding my model again and trying to simply get it to display in my paginated list. Currently, the paged list displays no value at all rather than a name or ID. Obviously I am trying to get the Name to display. Then I will move toward something more complicated like getting this value in a dropdown on the create page.
I know I am not figuring out the part about linking the data from the database back into my view model, but I have spent so many hours on this today and haven't quite found the missing piece. I have had so many variations on this that didn't work and this was trying to start by keeping it as simple as I could and then ask you all for advice.
Here is the code I am working with:
Devices.cs - DeviceType is really the ID
public partial class Devices
{
[MaxLength(10)]
public string DeviceId { get; set; }
public int DeviceType { get; set; }
...
[NotMapped]
public DeviceTypes DeviceTypes { get; set; }
}
DeviceTypes.cs - This is what I want to look up by DeviceType (int) and use the name in the editors
public partial class DeviceTypes
{
public int DeviceType { get; set; }
public string Name { get; set; }
}
DevicesViewModel.cs
public class DevicesViewModel
{
public Devices Devices { get; set; }
public PaginatedList<Devices> DevicesPagedList { get; set; }
}
DevicesController.cs - This is in the Index method where I get the data and then later what I am passing into my view
var devices = from dev in _context.Devices
select dev;
...
return base.View(new DevicesViewModel { DevicesPagedList = await PaginatedList<Devices>.CreateAsync(devices.AsNoTracking(), currentPage, resultsPerPage ?? 10), ResultsPerPageList = SelectListItemHelper.ResultsPerPageList().Where(l => l.Text != (resultsPerPage ?? 10).ToString()) });
}
Finally, here is where I am trying to display the value in Index.cshtml
#model DevicesViewModel
...
#foreach (var item in Model.DevicesPagedList)
{
<tr>
<td>
#Html.DisplayFor(modelItem => item.DeviceTypes.Name)
</td>
...

Multiple one to many relations of the same entity type code first

I have problems defining an entity that has two one-to-many relations (two lists)
public class Calendar
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public Guid ID { get; set; }
public virtual IList<Appointment> FreeSlots { get; set; }
public virtual IList<Appointment> AppointmentsList { get; set; }
...
}
public class Appointment
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int AppointmentID { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public String Type { get; set; }
public String Info { get; set; }
public Guid CalendarID { get; set; }
public virtual Calendar Calendar { get; set; }
}
And code-first code:
modelBuilder.Entity<Appointment>().HasKey(u => new {u.AppointmentID, u.CalendarID });
modelBuilder.Entity<Appointment>().HasRequired(u => u.Calendar).WithMany(c => c.FreeSlots).HasForeignKey(f => f.CalendarID).WillCascadeOnDelete(true);
modelBuilder.Entity<Appointment>().HasRequired(u => u.Calendar).WithMany(c => c.AppointmentsList).HasForeignKey(f => f.CalendarID).WillCascadeOnDelete(true);
Appointment has two PK because I want the appointment to be deleted if the calendar is deleted.
When I try to add a new appointment to the FreeSlot, I get the following error:
An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity cannot be identified as the source of the exception.
I have tried with this mapping too with no luck:error 0040: Type Calendar_FreeSlots is not defined in the namespace Project.DAL (Alias=Self).
modelBuilder.Entity<Appointment>().HasKey(u => new {u.AppointmentID, u.CalendarID });
modelBuilder.Entity<Calendar>().HasMany(c => c.FreeSlots).WithRequired(c => c.Calendar).HasForeignKey(c => c.CalendarID).WillCascadeOnDelete();
modelBuilder.Entity<Calendar>().HasMany(c => c.AppointmentsList).WithRequired(c => c.Calendar).HasForeignKey(c => c.CalendarID).WillCascadeOnDelete();
I guess the problem is that I have two one-to-many relations to the same type of entity but I do not know the way to make it correctly.
The mistake in your mapping is that you use Appointment.Calendar twice for two different relationships. That's not possible. You would need a second pair of FK and navigation properties in Appointment (or map one relationship without inverse properties):
modelBuilder.Entity<Calendar>()
.HasMany(c => c.FreeSlots)
.WithRequired(c => c.Calendar1)
.HasForeignKey(c => c.Calendar1ID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Calendar>()
.HasMany(c => c.AppointmentsList)
.WithRequired(c => c.Calendar2)
.HasForeignKey(c => c.Calendar2ID)
.WillCascadeOnDelete(true);
(For at least one relationship you must disable cascading delete. Otherwise you'll end up with a multiple cascading delete exception.)
However, I have the feeling that you actually should have only one relationship and collection Calendar.Appointments and a kind of status in Appointment, like a bool IsFree property. You could then always extract the free slots of a calendar entry with calendar.Appointments.Where(a => a.IsFree).

Entity Framework 5 One to One relationship (e.g. User -> Profile) - ModelBuilder ASP.NET MVC4

I am trying to do a similar thing to what this previous answer had here:
How to declare one to one relationship using Entity Framework 4 Code First (POCO)
The problem is, im very new to this and am using Entity Framework 5 code first and the HasConstraint doesnt exist anymore, not to mention Im not good at lamda. I was wondering if anyone could help expand on this so I can map a User class to a Profile class effectively and easily? I need to know how to do this for the configuration files and model builder
Each user has one profile
Also, another quick question, say the profile model had Lists in this, how would I put these effectively in the model builder and configuration files?
Thank you
e.g.
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public Profile Profile { get; set; }
// public int ProfileId { get; set; }
}
public class Profile
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PostalCode { get; set; }
}
// ...
modelBuilder.Entity<User>()
.HasOptional(x => x.Profile)
.WithRequired();
ProfileId is useless, FK is on the 'other side of the fence' (in Profile).
(this makes most sense IMO)
If you do need an Id in User (e.g. to be able to fill in Profile just by its ID when adding User - which if one-to-one is not really used - as you create both profile and user), then you can reverse...
modelBuilder.Entity<User>()
.HasRequired(x => x.Profile)
.WithOptional();
...and your ProfileId is actually in the Id (pk -> pk).
That solution worked for me
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ApplicationUser>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.HasOne(d => d.Profile).WithOne(p => p.User);
});
modelBuilder.Entity<UserProfile>(entity =>
{
entity.HasKey(e => e.Id);
entity.Property(e => e.Id).HasMaxLength(450);
entity.Property(e => e.Type)
.HasMaxLength(10)
.HasColumnType("nchar");
entity.HasOne(d => d.User).WithOne(p => p.Profile);
});
}

Fluent NHibernate one to many not saving children

I am using Fluent NHibernate. This is a classic case of a one to many relationship. I have one Supply parent with many SupplyAmount children.
The Supply parent object is saving with correct info, but the amounts are not getting inserted into the db when I save the parent. What am I doing for the cascade not to work?
The entities are as follows:
public class Supply : BaseEntity
{
public Guid SupplyId { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public string Comments { get; set; }
public virtual IList<SupplyAmount> Amounts { get; set; }
public Supply()
{
Amounts = new List<SupplyAmount>();
}
public virtual void AddAmount(SupplyAmount amount)
{
amount.Supply = this;
Amounts.Add(amount);
}
}
public class SupplyAmount : BaseEntity
{
public virtual Guid SupplymountId { get; set; }
public virtual Supply Supply { get; set; }
public virtual int Amount { get; set; }
}
And the mapping as follows:
public class SupplyMap : ClassMap<Supply>
{
public SupplyMap()
{
Id(x => x.SupplyId);
Map(x => x.LastName);
Map(x => x.FirstName);
Map(x => x.Comments);
HasMany<SupplyAmount>(x => x.Amounts)
.Inverse().Cascade.SaveUpdate()
.KeyColumn("SupplyAmountId")
.AsBag();
}
}
public class SupplyAmountMap : ClassMap<SupplyAmount>
{
public SupplyAmountMap()
{
Id(x => x.SupplyAmountId);
References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Map(x => x.Amount);
}
}
And this is how I call it:
public SaveIt()
{
Supply sOrder = Supply();
sOrder.FirstName = "TestFirst";
sOrder.LastName = "TestLast";
sOrder.Comments = "TestComments";
for (int i = 0; i < 5; i++)
{
SupplyAmount amount = new SupplyAmount();
amount.Amount = 50;
amount.Supply = sOrder;
sOrder.AddAmount(amount);
}
// This call saves the Supply to the Supply table but none of the Amounts
// to the SupplyAmount table.
AddSupplyOrder(sOrder);
}
I know this is an old post but why not...
// This call saves the Supply to the Supply table but none of the Amounts
This comment in SaveIt() indicates you call the save on the Supply and not the amounts.
In this case you have your logic the wrong way around.
So to fix this:
SupplyMap -> The Inverse shouldn't be there for Amounts.
HasMany<SupplyAmount>(x => x.Amounts).Cascade.SaveUpdate();
SupplyAmountMap ->
remove References(x => x.Supply, "SupplyId").Cascade.SaveUpdate();
Replace it with
References<Supply>(x=>x.Supply);
You should now be right to call the save on your supply object only and it will cascade down to the amounts.
Session.Save(supply);
In your test after you have arrange the supply and supplyamount make sure you call a
Session.Flush()
after your save to force it in.
This isn't as important in code as you will usually run in transactions before recalling the supply object.
Cheers,
Choco
Also as a side note it usually not a good idea to be to verbose with fluentmappings. let the default stuff do it thing which is why I would recommend against the column naming hints.

How to Map a ValueObject Collection with Foreign Keys in FluentNHibernate

I've been looking all over for an example of this, but it seems pretty uncommon. Hopefully some NHibernate guru will know.
I have the following class which, by my understanding of Value Objects, is a Value Object. Assume every user has the ability to assign one or more tags to any Question (think Stack Overflow). The Tags don't need a primary key, but they do hold references to the User and Question, unlike most of the examples of ValueObjects I see out there.
public class Tag : ValueObject
{
public virtual User User { get; set; }
public virtual Question Question { get; set; }
public virtual string TagName { get; set; }
}
public class User
{
public virtual IList<Tag> Tags { get; set; }
}
public class Question
{
public virtual IList<Tag> Tags { get; set; }
}
Anyway, I am getting the following error:
{"The entity 'Tag' doesn't have an Id mapped. Use the Id method to map your identity property. For example: Id(x => x.Id)."}
I have the following Fluent NHibernate mapping for User and Question:
public void Override(AutoMapping<XXX> mapping)
{
mapping.HasMany(x => x.Tags).Component(c =>
{
c.Map(x => x.TagName);
c.Map(x => x.Question);
c.Map(x => x.User);
});
}
As always, any thought greatly appreciated.
Late Update: Okay, so, maybe this isn't a value object. It doesn't need an identity, but I guess it's not something that could be used in multiple places, either. Any way to handle this without forcing a useless Id field on my object?
Try this:
public void Override(AutoMapping<XXX> mapping)
{
mapping.HasMany(x => x.Tags).AsBag().Component(c =>
{
c.Map(x => x.TagName);
c.References(x => x.Question);
c.References(x => x.User);
});
}
but you cant query (list all) tags then because its a value object.