How do you map many-to-many relationships? - c#

How does one map many to many relationships?
One-to-one relationships are easy....
Assuming...
public class ProductDTO
{
public int Id { get; set; }
public string Name { get; set; }
public int SupplierId { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Cost { get; set; }
public Supplier Supplier { get; set; }
}
public class Supplier
{
public int Id { get; set; }
public string Name { get; set; }
public int Rating { get; set; }
public ICollection<Product> Products { get; set; }
}
Mapper.CreateMap<Product, ProductDTO>()
.ForMember(d => d.SupplierId, m => m.MapFrom(s => s.Supplier.Id));
Works assuming every product has only one supplier and a supplier can have many products. How do I map it if a product can also have many suppliers?
I changed supplier line in Products to
public ICollection<Supplier> Supplier { get; set; }
and in ProductDTO doing the same
public ICollection<int> SupplierId { get; set; }
How do I alter CreateMap since the Ids are now collections? Autocomplete no longer shows Id and all I get are functions.
I'm new to C# so I many be missing something obvious. Am I supposed to iterate in a for loop and map the ids one by one?

You may try to use:
Mapper.CreateMap<Product, ProductDTO>()
.ForMember(d => d.SupplierIds, m => m.MapFrom(p => p.Suppliers.Select(s => s.Id)));
Another option:
Mapper.CreateMap<Supplier, int>().ConvertUsing(s => s.Id);
Mapper.CreateMap<Product, ProductDTO>()
.ForMember(d => d.SupplierIds, m => m.MapFrom(p => p.Suppliers));
One more thing if you are this using DTO to pass data from Web/WCF service you may consider using
public ICollection<SupplierDTO> Supplier { get; set; }
instead if passing supplier ids only. In most cases it's better (and more effective) to pass more data in one call to the service than doing few calls.

Related

Find entities not in many-to-many relation

I might be looking at this the wrong way but I have a basic many-to-many code-first setup in EF Core 3.1 with Department <-> DepartmentDay <-> Day.
modelBuilder.Entity<DepartmentDay>(entity =>
{
entity.HasKey(dd => new { dd.DepartmentId, dd.DayId });
entity.HasOne(dp => dp.Day)
.WithMany(p => p.DepartmentDays)
.HasForeignKey(d => d.DayId);
entity.HasOne(dp => dp.Department)
.WithMany(p => p.DepartmentDays)
.HasForeignKey(d => d.DepartmentId);
});
First question: Is this relationship optional in the way that I can have days not connected to a department? I need this since this is related to opening hours and want to have generic days that affects all departments without having to make a specific connection to all departments. but as I stated in the beginning, I might look at this the wrong way.
Second question: If question one is true and valid setup, how do I get those days not connected to a department in a Linq-query?
What I have so far is (EDIT: changed allDays from Hashset to List.)
var allDays = await _context.Days.ToListAsync();
var allDepartmentDays = _context.DepartmentDays.Select(dd => dd.DayId).ToHashSet();
var genericDays = allDays.Where(d => !allDepartmentDays.Contains(d.Id));
Or is it better to use an raw query here for performance?
SELECT Id
FROM Day
WHERE Id NOT IN (SELECT DayId FROM DepartmentDay)
Edit 2: Including the whole data model
public class Department
{
public int Id { get; set; }
public int DepartmentNr { get; set; }
public string Service { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public string Address { get; set; }
public string Postal { get; set; }
public string City { get; set; }
public string Url { get; set; }
public string MapUrl { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime? Updated { get; set; }
public string UpdatedBy { get; set; }
public ICollection<DepartmentPeriod> DepartmentPeriods { get; set; }
public ICollection<DepartmentDay> DepartmentDays { get; set; }
}
public class DepartmentDay
{
public int DepartmentId { get; set; }
public int DayId { get; set; }
public Department Department { get; set; }
public Day.Models.Day Day { get; set; }
}
public class Day
{
public int Id { get; set; }
public int PeriodId { get; set; }
public string Service { get; set; }
public string City { get; set; }
public DateTime? Date { get; set; }
public DayOfWeek? DayOfWeek { get; set; }
public DateTime? OpenTime { get; set; }
public DateTime? CloseTime { get; set; }
public bool IsClosed { get; set; }
public string Description { get; set; }
public DateTime Created { get; set; }
public string CreatedBy { get; set; }
public DateTime? Updated { get; set; }
public string UpdatedBy { get; set; }
public ICollection<DepartmentDay> DepartmentDays { get; set; }
public virtual Period.Models.Period Period { get; set; }
}
modelBuilder.Entity<Day>(entity =>
{
entity.HasOne(d => d.Period)
.WithMany(p => p.Days)
.HasForeignKey(d => d.PeriodId);
});
There is another relation not included in the initial question which kinds of answers my first question which is Department 1-M DepartmentPeriod M-1 Period 1-1 Day. So there will be days in the Day table that has no relation to DepartmentDay but only to Period and to neither, correct?
First question: Is it optional? Really a tough question without your data types, but suppose you have the navigation properties correctly in place, you only need to specify the navigation properties if they keys are not named {DataType}Id, so if you name them like that it is not required, otherwise you have to specify which foreign key fields to use and which are the keys. If you do not specify relations, entity framework will generate the tables for you for the many to many relations.
Second question:
In terms of performance the SQL query will always outperform if you are able to write a good query, the one you suggest or even
select a.id from [day] a left join departmentday b on a.id = b.dayid where b.dayid is null
could perform slightly better even so,
However in terms of testability the sql is a problem because we'd want to run an InMemory model of our entity dbcontext and then your sql cannot execute typically at least.
So the question is if you really require that extra performance and is is possible to write a linq query that efficiently enable the linq engine to write a similar query, by making it part of the same expression instead of two or more.
In Your case, I could be missing something in the data model presently not provided, but it seems like you could get what you want like this:
_context.Days.Include(d => d.DepartmentDays).Where(!d.DepartmentDays.Any());
update:
Looking at the model it seems there are some model work missing and that the projection You're looking for can be done much along the lines suggested.
Changes/ addition to model creating:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Department>()
.HasKey(k => k.Id);
modelBuilder.Entity<DepartmentDay>()
.HasKey(k => new { k.DayId, k.Department });
modelBuilder.Entity<Day>()
.HasOne(d => d.Period)
.WithMany(p => p.Days)
.HasPrincipalKey(k => k.PeriodId)
.HasForeignKey(d => d.Id);
//Notice your departmentid is not alone a foreign key in the relationship table, as departments can have one row per day
modelBuilder.Entity<DepartmentDay>()
.HasOne(a => a.Department)
.WithMany(b => b.DepartmentDays)
.HasPrincipalKey(p => p.Id)
.HasForeignKey(f => new { f.DepartmentId, f.DayId });
base.OnModelCreating(modelBuilder);
}
and the in the context we can make such query using:
public List<Day> GetDaysWithoutAnyDepartmentDays()
{
return Days
.Include(i => i.DepartmentDays)
.Where(x => !x.DepartmentDays.Any()
).ToList();
}

Is it possible to use an owned type for many-to-many relations?

In Entity Framework Core version 2.2 or 3.0, is it possible to use owned/complex types in such a way that this kind of configuration is possible:
public class Product {
public int Id { get; set; }
public string Name { get; set; }
public ProductProperties Properties { get; set; }
}
public class ProductProperties {
public List<ProductSize> Sizes { get; set; }
}
public class Size {
public int Id { get; set; }
public string Name { get; set; }
}
public class ProductSize {
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Product)
.WithMany(x => x.Properties.Sizes)
.HasForeignKey(x => x.ProductId);
modelBuilder.Entity<ProductSize>()
.HasOne(x => x.Size)
.WithMany()
.HasForeignKey(x => x.SizeId);
The error message which is seen for this kind of approach usually ends up in:
'x => x.Properties.Sizes' is not a valid property expression. The expression should represent a simple property access: 't => t.MyProperty'
An earlier found answer is almost exactly matching my question, but this was posted in 2013. By the time it was almost certainly not possible.
HasForeignKey relationship through a Complex Type property
The sources on Microsoft are only giving examples for creating an entity with the complex type itself, not for creating relationships between them.
The cause of the issue
In your sample code it's quite clear there is no specific Many to Many relation. To make my argument a bit more convincing what follows is a model of your entities and their relations:
The new class structure
For a Many to Many relation to work in EF the product and size tables need to have an implicit relation with each other through a singular junction table. In my proposed solution I've chosen the ProductProperty table. There I've added the fields from the productsize junction table:
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
public class ProductProperty
{
public int ProductId { get; set; }
public Product Product { get; set; }
public int SizeId { get; set; }
public Size Size { get; set; }
}
public class Size
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<ProductProperty> Properties { get; set; }
}
The functions
modelBuilder.Entity<ProductProperty>()
.HasKey(pp => new { pp.ProductId, pp.SizeId });
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Product)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.ProductId);
modelBuilder.Entity<ProductProperty>()
.HasOne(pp => pp.Size)
.WithMany(p => p.Properties)
.HasForeignKey(pp => pp.SizeId);
Additional advice (EDIT)
Make the "Size" class a generic property class. This way the Many-to-Many relation won't get broken and querying will also be very easy:
public class Property
{
public int Id { get; set; }
public PropertyType propType { get; set; }
public string propValue { get; set; }
}
public enum PropertyType
{
Size,
Fontsize,
...
}
As a final argument this change will make it easier to change existing properties or add new ones
Sources
https://www.learnentityframeworkcore.com/configuration/many-to-many-relationship-configuration
You can check the owned entity types released in 2019 Check documentation here
An example from the link is the following:
public class Distributor
{
public int Id { get; set; }
public ICollection<StreetAddress> ShippingCenters { get; set; }
}
The owns many function should help you like this:
modelBuilder.Entity<Distributor>().OwnsMany(p => p.ShippingCenters, a =>
{
a.WithOwner().HasForeignKey("OwnerId");
a.Property<int>("Id");
a.HasKey("Id");
});
Let me know if I misunderstood your question.

Is it possible to set up a navigation property without the foreign key?

Is it possible to have two (or more) navigation properties of the same type?
My model looks like this...
public class Agreement
{
public int Id { get; set; }
public Guid? BuyerId { get; set; }
public Guid? SellerId { get; set; }
public AgreementInfo ByerAgreementInfo { get; set; }
public AgreementInfo SellerAgreementInfo { get; set; }
}
public class AgreementInfo
{
// PK is AgreementId and OwnerActorId combined.
public int AgreementId { get; set; }
public Guid OwnerActorId { get; set; }
public string Name { get; set; }
}
... and i'm trying to include the navigation properties by matching the AgreementId and the ByerId/SellerId...
modelBuilder.Entity<Agreement>().HasOne(x => x.ByerAgreementInfo).WithOne().HasForeignKey<Agreement>(x => new {x.Id, x.ProviderId});
modelBuilder.Entity<Agreement>().HasOne(x => x.SellerAgreementInfo).WithOne().HasForeignKey<Agreement>(x => new { x.Id, x.RequesterId });
... but this results in a circural dependecy.
Is there any way to include these properties without using a foreign key? Or is there another solution (except for adding an id-column to the info table) that allows me to use the info-table rows as navigation properties in the agreement class?
... but this results in a circural dependecy
Yes! It will. To overcome this you have to specify .OnDelete(DeleteBehavior.Restrict); in your Fluent API configuration as follows but first you have to write your Agreement model class as follows too:
public class Agreement
{
public int Id { get; set; }
public Guid? BuyerId { get; set; }
public Guid? SellerId { get; set; }
public int AgreementIdForBuyer { get; set; }
public Guid OwnerActorIdForBuyer { get; set; }
public int AgreementIdForSeller { get; set; }
public Guid OwnerActorIdForSeller { get; set; }
public AgreementInfo ByerAgreementInfo { get; set; }
public AgreementInfo SellerAgreementInfo { get; set; }
}
Now in Fluent API configuration:
modelBuilder.Entity<Agreement>()
.HasOne(x => x.ByerAgreementInfo)
.WithOne()
.HasForeignKey<Agreement>(p => new {p.AgreementIdForBuyer, p.AgreementIdForBuyer})
.OnDelete(DeleteBehavior.Restrict); // <-- Here it is
modelBuilder.Entity<Agreement>()
.HasOne(x => x.SellerAgreementInfo)
.WithOne()
.HasForeignKey<Agreement>(p => new {p.AgreementIdForSeller, p.OwnerActorIdForSeller})
.OnDelete(DeleteBehavior.Restrict); // <-- Here it is
There are more than a handful of ways to achieve what you want. You could use Data Annotations with InverseProperty attribute on your child, ForeignKey attribute on your parent, or fluent syntax either way in your model builder. I tend to use attributes (Data Annotations) where I can (it's just my personal taste), I find it need to see the relationships in the actual mode itself (though others might not).
Using ForeignKey attribute:
public class Agreement
{
...
[ForeignKey("ByerAgreementInfo ")]
public int ByerAgreementInfoId { get; set; }
public AgreementInfo ByerAgreementInfo { get; set; }
[ForeignKey("SellerAgreementInfo ")]
public int SellerAgreementInfoId { get; set; }
public AgreementInfo SellerAgreementInfo { get; set;
}
Using InverseProperty attribute:
public class AgreementInfo
{
...
[InverseProperty("ByerAgreementInfo ")]
public ICollection<Agreement> Sellers { get; set; }
[InverseProperty("SellerAgreementInfo ")]
public ICollection<Agreement> Buyers { get; set; }
}
If you want to use Fluent Syntax, I believe the following will work (though I haven't used it for a while):
modelBuilder.Entity<Agreement>()
.HasOne(x => x.ByerAgreementInfo)
.WithOne()
.HasForeignKey<Agreement>(p => p.ByerAgreementInfoId);
modelBuilder.Entity<Agreement>()
.HasOne(x => x.SellerAgreementInfo)
.WithOne()
.HasForeignKey<Agreement>(p => p.SellerAgreementInfoId);
Note: I'm think you may need the actual Id's in your model, though I can't remember.

query through five tables with nested Any operator

I'm trying to create a single query to get all the users that belong to a certain announcement, but I cannot seem to get my query to work.
I'm getting the following error:
Unable to create a constant value of type 'RequestSupplierEntity'.
Only primitive types or enumeration types are supported in this
context.
I do not know exactly what the error message means, or what I should do/avoid to prevent it. Any help or insight into the problem, or even a working query, would be greatly appreciated.
It seems to me that a single T-SQL query should be possible using a EXIST subquery, I just don't know if entity framework is able to construct it in this case.
My query statement:
Users.Where(u => notification.Announcement.RequestSuppliers.Any(rs => rs.Supplier.OrganisationId == u.OrganisationId));
Users is a DBSet. notification is the entity instance I'm trying to find the associated users for. I have the feeling that the problem lies with using the NotificationEntity instance within the where method of the DBSet, but I don't see how I should do this otherwise.
The POCO entities relate as follows:
User has a Organisation
Supplier is a Organisation
RequestSupplier is a Supplier
RequestSupplier has a Announcement
Notification has a Announcement
So what I'm trying to do is to get the Users with OrganisationId's that belong to the Suppliers that are associated with the Announcement through the RequestSupplier.
The associated FluentAPI is:
modelBuilder.Entity<NotificationEntity>()
.HasOptional<AnnouncementEntity>(n => n.Announcement)
.WithMany(a => a.Notifications)
.HasForeignKey(n => n.AnnouncementId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<RequestSupplierEntity>()
.HasRequired<SupplierEntity>(rs => rs.Supplier)
.WithMany(s => s.RequestSuppliers)
.WillCascadeOnDelete(false);
modelBuilder.Entity<RequestSupplierEntity>()
.HasKey(rs => new { rs.SupplierId });
modelBuilder.Entity<RequestSupplierEntity>()
.HasRequired<AnnouncementEntity>(rs => rs.Announcement)
.WithMany(a => a.RequestSuppliers)
.HasForeignKey(rs => rs.AnnouncementId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<SupplierEntity>()
.HasRequired<OrganisationEntity>(s => s.Organisation)
.WithMany(o => o.Suppliers)
.HasForeignKey(s => s.OrganisationId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<UserEntity>()
.HasOptional<OrganisationEntity>(u => u.Organisation)
.WithMany(o => o.Users)
.WillCascadeOnDelete(true);
The notification I'm doing this query for always has an associated Announcement.
Entities:
[Table("Announcement")]
public class AnnouncementEntity
{
public int Id { get; set; }
public virtual ICollection<RequestSupplierEntity> RequestSuppliers { get; set; }
public virtual ICollection<NotificationEntity> Notifications { get; set; }
}
[Table("Notification")]
public class NotificationEntity
{
public int Id { get; set; }
public int? AnnouncementId { get; set; }
public virtual AnnouncementEntity Announcement { get; set; }
}
[Table("Organisation")]
public class OrganisationEntity
{
public int Id { get; set; }
public virtual ICollection<SupplierEntity> Suppliers { get; set; }
public virtual ICollection<UserEntity> Users { get; set; }
}
[Table("RequestSupplier")]
public class RequestSupplierEntity
{
public int SupplierId { get; set; }
public int AnnouncementId { get; set; }
public virtual SupplierEntity Supplier { get; set; }
public virtual AnnouncementEntity Announcement { get; set; }
}
[Table("Supplier")]
public class SupplierEntity
{
public int Id { get; set; }
public int OrganisationId { get; set; }
public virtual OrganisationEntity Organisation { get; set; }
public virtual ICollection<RequestSupplierEntity> RequestSuppliers { get; set; }
}
[Table("User")]
public class UserEntity
{
public int Id { get; set; }
public int? OrganisationId { get; set; }
public virtual OrganisationEntity Organisation { get; set; }
}
I'm not sure but try this
db.Users.Where(u => db.Notifications.Select(n=>n.Announcement).SelectMany(a=>a.RequestSuppliers).Any(rs => rs.Supplier.OrganisationId == u.OrganisationId));
I guess that you try to make a collection of collections of RequestSuppliers which is selected via Annoncement from of collection of Notification inside Where() method
in this case you have to use SelectMany
And in any case may use join for this
var users=(from u in db.Users
join s in db.Suppliers
on u.OrganisationId equals s.OrganisationId
join rs in db.RequestSuppliers
on s.Id equals rs.SupplierId
join a in db.Announcements
on rs.AnnouncementId equals a.Id
join n in db.Notifications
on a.Id equals n.AnnouncementId
select u);

Entity Framework with Condition Relationship

Is it possible to have a relationship that is based on a condition in Entity Framework? My model looks something like this...
public class Document
{
public int Id { get; set; }
public string Name { get; set; }
public OwnerType OwnerType { get; set; }
public int OwnerId { get; set; }
public virtual Organization OrganizationOwner { get; set; }
public virtual User UserOwner { get; set; }
}
public enum OwnerType
{
Organization = 1,
User = 2
}
public class Organization
{
public int Id { get; set; }
public string Name { get; set; }
//[other properties specific to Organization]
public virtual List<Documents> Documents { get; set; }
}
public class User
{
public int Id { get; set; }
public string Name { get; set; }
//[other properties specific to User]
public virtual List<Documents> Documents { get; set; }
}
So, what I'd like to is set up a relationship so that the OrganizationOwner property of a Document instance automatically gets populated when the OwnerType == OwnerType.Organization, and the UserOwner property is populated when OwnerType == OwnerType.User.
Is this possible to set up this kind of relationship in EntityFramework - Code First? Something like this in the mapping...
EntityTypeConfiguration<Document>.HasOptional(d => d.OrganizationOwner)
.WithMany(o => o.Documents)
.HasForeignKey(d => d.OwnerId)
.Where(d => d.OwnerType == OwnerType.Organization);
EntityTypeConfiguration<Document>.HasOptional(d => d.UserOwner)
.WithMany(u => u.Documents)
.HasForeignKey(d => d.OwnerId)
.Where(d => d.OwnerType == OwnerType.User);
I would like to be able to leverage joins on the OrganizationOwner and UserOwner when setting up my Linq queries on the context so that I don't have to do a separate selects on those entities for each Document. Is this type of relationship supported or is there a better way to do this? Thanks.

Categories