I am using EF5.0 CF let's consider theses entities (simplified here):
public class Catalog
{
public int Id { get; set; }
public bool IsActive { get; set; }
public string Name { get; set; }
public ICollection<PricedProduct> Products { get; set; }
}
public class PricedProduct
{
public int Id { get; set; }
public bool IsActive { get; set; }
public Product Product { get; set; }
public Price Price { get; set; }
}
public class Price
{
public int Id { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name {get; set;}
}
They are configured with the fluent API :
//For the Catalog entity
ToTable("Catalog", "Catalog");
this.Property(t => t.Name).HasColumnType("nvarchar").HasMaxLength(100).IsRequired();
this.HasMany<PricedProduct>(t => t.Products).WithMany().
Map(mc =>
{
mc.ToTable("CatalogPricedProduct", "Catalog");
mc.MapLeftKey("PricedProductID");
mc.MapRightKey("CatalogID");
});
//For the PricedProduct entity
ToTable("PricedProducts", "Catalog");
HasRequired(t => t.Product).WithOptional().Map(m=>m.MapKey());
HasRequired(t => t.Price).WithOptional().Map(m => m.MapKey());
//For the Product entity
ToTable("Products", "Catalog");
this.Property(t => t.Name).HasColumnType("nvarchar").HasMaxLength(100).IsRequired();
//For the Price entity
ToTable("Prices", "Catalog");
So basically I have a catalog which have n:n relationship with PricedProduct that have two 1:1 relationship with Product and Price
I get those entities with this linq query :
var qy = from cata in this.Set<Catalog>().Include("Products")
.Include("Products.Product")
.Include("Products.Price")
where cata.Name == "name"
select cata;
return qy.FirstOrDefault();
Everything works well as long as two PricedProduct does not share the same product or the same price.
Meaning that, in the PricedProducts table the PriceProduct are retrieved and materialized correctly as long as the Product or the Price FK are "unique", if another PricedProduct have the same FK value on price for instance, price wont be loaded in the concerned PricedProduct.
I have quickly check the SQL query generated and it looks fine, it feels like EF fail to materialize two instances of the same object in a same graph ??
Anyone knows what to do or what is wrong with my code ?
thank a lot
That is because your understanding of your model is not correct. If multiple PricedProduct can have same Price or same Product you cannot map it as one-to-one relationship but as one-to-many (one price can be assigned to many priced products - same for product). You need:
ToTable("PricedProducts", "Catalog");
HasRequired(t => t.Product).WithMany();
HasRequired(t => t.Price).WithMany();
Related
The context is : A Student can make appointments to Mentor and Mentor can have many appointments. Mentor may be student or lecturer.
I don't separate Student and Mentor but merge them into one entities User
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Avatar { get; set; }
public string MeetUrl { get; set; }
public bool? IsMentor { get; set; }
public bool IsActive { get; set; }
public ICollection<Appointment> Appointments { get; set; }
}
public class Appointment
{
public string Id { get; set; }
public string StudentId { get; set; }
public User Student { get; set; }
public string MentorId { get; set; }
//public User Mentor { get; set; }
public bool IsApproved { get; set; }
}
I don't know how to config the relation of these entities with fluent-api to fit with the context.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Appointment>()
.HasOne<User>(a => a.Student)
.WithMany(st => st.Appointments)
.HasForeignKey(st => st.StudentId);
builder.Entity<Appointment>()
.HasOne<User>(a => a.Mentor)
.WithMany(m => m.Appointments)
.HasForeignKey(a => a.MentorId);
}
Should I separate User into 2 entities Student, Mentor?
What you are trying to do here with fluent syntax is set up "navigation properties".
Consider the following piece your code:
builder.Entity<Appointment>()
.HasOne<User>(a => a.Student)
.WithMany(st => st.Appointments)
.HasForeignKey(st => st.StudentId);
Here you are saying Appointments and User have a one-to-many relation where StudentId is your FK and Student represents the one User item this relation is linked to. You are also saying Appointments represents that many appointments that are linked to the User record the via the FK StudentId.
In other words, in this piece of code, you have specified that Appointments represents all the appointments of the current User as a student and not as a mentor. However, in the next fluent syntax, you are similarly trying to set the same property Appointments to represent the list of appointments that the current user has as a mentor.
You can't have the same property for both these use cases. You would need two properties in your User table like AppointmentsAsStudent and AppointmentsAsMentor.
I am struggling to write the code to generate a foreign key relationship between a one-to-one relationship of Bill and Pay:
public class Bill
{
public string Identifier { get; set; }
public Guid WebSiteId { get; set; }
}
public class Pay
{
public string Account { get; set; }
public Guid WebSiteId { get; set; }
}
The naming was done poorly - Identifier is Account, but it's probably not worth the refactor at this moment (there's static raw SQL queries in the codebase referencing the names directly).
The way to join these two tables in SQL is like this:
SELECT *
FROM Bills b
JOIN Pay p ON (b.Identifier = p.Account AND b.WebSiteId = p.WebSiteId)
And it will guarantee a one-to-one relationship between Bill and Pay.
How do I get EF core to understand this relationship?
I would like to use .Include for my joins:
context.Bills
.Include(x => x.Pays)
Meaning my models would look something like this
public class Bill
{
public string Identifier { get; set; }
public Guid WebSiteId { get; set; }
public virtual Pay Pay { get; set; }
}
public class Pay
{
public string Account { get; set; }
public Guid WebSiteId { get; set; }
public virtual Bill Bill { get; set; }
}
If you need both properties to uniquely identify a Pay, then I presume that you have a composite primary key on Pay entity.
builder.Entity<Pay>().HasKey(p => new { p.Account, p.WebSiteId });
In which case, you can configure the relationship using fluent API:
builder.Entity<Bill>()
.HasOne<Pay>(b => b.Pay)
.WithOne<Bill>(p => p.Bill)
.HasForeignKey<Bill>(b => new { b.Identifier, b.WebSiteId });
Consider the following 3 entities A, B, and C. A-to-C is a many-to-many relationship. B-to-A is a one-to-many relationship (B has many A, which implies B-to-C is also many-to-many).
If these overcomplicated relationships seems too vague to you, please consider this example:
a Track (A) has multiple Artists (C), an Artist has multiple Tracks (Many-to-Many)
an Album (B) has multiple Tracks. (One-to-Many, a Track cannot belong to multiple Albums)
so that (implication):
an Album has multiple Artists, and an Artist has multiple Albums (Many-to-Many)
Question: How to entities with such relationships? With the following code, I've established relationships between Album-to-Artist, Track-to-Artist. How to create Artist-Album relationship as it seems to require joining 4 tables/entities.
public class Context: DbContext
{
// irrelevant code omitted...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TracksToArtists>()
.HasKey(r => new {r.TrackId, r.ArtistId});
modelBuilder.Entity<TracksToArtists>()
.HasOne(r => r.Track)
.WithMany(t => t.Artists)
.HasForeignKey(r => r.TrackId);
modelBuilder.Entity<TracksToArtists>()
.HasOne(r => r.Artist)
.WithMany(a => a.Tracks)
.HasForeignKey(r => r.ArtistId);
}
}
public class Track
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid AlbumId { get; set; }
public Album Album { get; set; }
public List<TracksToArtists> Artists { get; set; }
}
public class Album
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Track> Tracks { get; set; }
// TODO: create album-to-artist many-to-many relationship
// public List<SomethingArtistMaybe?> Artists { get; set; }
}
public class Artist
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<TracksToArtists> Tracks { get; set; }
// TODO: create album-to-artist many-to-many relationship
// public List<SomethingAlbumMaybe?> Albums { get; set; }
}
// many-to-many relationship
public class TracksToArtists
{
public Guid TrackId { get; set; }
public Track Track { get; set; }
public Guid ArtistId { get; set; }
public Artist Artist { get; set; }
}
It's quite straightforward if just using raw SQL queries, yet with ORM everything becomes a bit painful.
Optimally, I'd like to avoid introducing AlbumToArtist table as it may create inconsistency in data. It should be something like this:
If I understand you correctly, Artist doesn't have a direct relationship to Album, the relationship is only via Track.
Consider putting the Album in the center, so that each Artist has multiple Album and vice versa (many-to-many), then have Album navigate to many Tracks (one-to-many).
Anyway, whether you choose to keep your model as in your question or as I suggested, accessing the graph from point to another (in your question Albums of each Artist), use eager loading:
public async Task<IEnumerable<Album>> GetAlbums(Artist artist)
{
return await myDbContext.Artists
.Include(artist => artist.Tracks)
.ThenInclude(track => track.Album)
.Where(a.Id == artist.Id)
.Select(artist =>
artist.Tracks.Select(track =>
track.Album))
.Distinct()
.ToListAsync();
}
You can also enable lazy-loading so you don't have to explicitly include the navigation properties in your query, but there is a performance cost for that.
I am brand new to Entity Framework code first, so any help or direction would be much appreciated.
I currently have the following classes:
public partial class Customer
{
public int Id { get; set; }
private ICollection<Address> _addresses;
}
public partial class Address
{
public int Id { get; set; }
public string Street { get; set; };
public string City { get; set; };
public string Zip { get; set; };
}
and the following
public partial class CustomerMap : EntityTypeConfiguration<Customer>
{
public CustomerMap()
{
this.ToTable("Customer");
this.HasKey(c => c.Id);
this.HasMany<Address>(c => c.Addresses)
.WithMany()
.Map(m => m.ToTable("CustomerAddresses"));
}
}
This works as I would expect, and creates a Customer, Address and CustomerAddresses table for the mapping. Now for my question.. what would I do if I needed to modify the code to produce the following...
I want to add a CompanyCode attribute to the "CustomerAddresses" table... and then instead of constructing a collection of addresses.. i want to be able to construct a hashtable, where the key is the CompanyCode, and the value is the collection of addresses.
So if I had the following:
Customer
ID C1
Address
ID A1
ID A2
CustomerAddresses
CustomerID C1
AddressID A1
CompanyCode ABC
CustomerID C1
AddressID A2
CompanyCode ABC
CustomerID C1
AddressID A2
CompanyCode XYZ
so then, Customer.Addresses["ABC"] would return a collection of addresses with ID, A1 and A2. Whereas Customer.Addresses["XYZ"] would return a collection of addresses with ID A2.
Any direction/help would be much appreciated... thanks.
As far as I can tell it isn't possible to introduce such a navigation property with an indexer. Your indexer is actually a query and you must express this as a query. The only way I see is that you leave the navigation collection as is and introduce a second (not mapped) property that uses the navigation collection for the filter. The big drawback is that such a filter would happen in memory with LINQ-to-Objects and requires that you always load the full collection first from the database (by eager or lazy loading for example) before you filter the collection.
I would probably leave such a filter out of the entity itself and implement it in a repository or service class or generally the place/module where you load the entities from the database.
The first thing you need to do is exposing the CustomerAddresses table as an entity in your model because with your additional custom property CompanyCode you can't use a many-to-many relationship anymore, instead you need two one-to-many relationships. The new entity would look like this:
public partial class CustomerAddress
{
public int CustomerId { get; set; }
// public Customer Customer { get; set; } // optional
public int AddressId { get; set; }
public Address Address { get; set; }
public string CompanyCode { get; set; }
}
And the Customer needs to be changed to:
public partial class Customer
{
public int Id { get; set; }
public ICollection<CustomerAddress> CustomerAddresses { get; set; }
}
You need to change the mapping to:
public CustomerMap()
{
this.ToTable("Customer");
this.HasKey(c => c.Id);
this.HasMany(c => c.CustomerAddresses)
.WithRequired() // or .WithRequired(ca => ca.Customer)
.HasForeignKey(ca => ca.CustomerId);
}
And create a new mapping for the new entity:
public CustomerAddressMap()
{
this.ToTable("CustomerAddresses");
this.HasKey(ca => new { ca.CustomerId, ca.AddressId, ca.CompanyCode });
// or what is the PK on that table?
// Maybe you need an Id property if this key isn't unique
this.HasRequired(ca => ca.Address)
.WithMany()
.HasForeignKey(ca => ca.AddressId);
}
Now, in some service class you could load the filtered addresses:
public List<Address> GetAddresses(int customerId, string companyCode)
{
return context.CustomerAddresses.Where(ca =>
ca.CustomerId == customerId && ca.CompanyCode == companyCode)
.ToList();
}
Or, if you want to load the customer together with the filtered addresses:
public Customer GetCustomer(int customerId, string companyCode)
{
var customer = context.Customer.SingleOrDefault(c => c.Id == customerId);
if (customer != null)
context.Entry(customer).Collection(c => c.CustomerAddresses).Query()
.Where(ca => ca.CompanyCode == companyCode)
.Load();
return customer;
}
The last example are two database queries.
In the Customer entity you could use a helper property that projects the addresses out of the CustomerAddresses collection:
public partial class Customer
{
public int Id { get; set; }
public ICollection<CustomerAddress> CustomerAddresses { get; set; }
public IEnumerable<Address> Addresses
{
get
{
if (CustomerAddresses != null)
return CustomerAddresses.Select(ca => ca.Address);
return null;
}
}
}
Keep in mind that this property does not query the database and the result relies on what is already loaded into CustomerAddresses.
I have following classes
public class Employer
{
[Key]
public Int64 EmployerID { get; set; }
public String CompanyName { get; set; }
public virtual List<Employee> Employees { get; set; }
}
public class Employee
{
[Key]
public Int64 EmployeeID { get; set; }
public String EmployeeName { get; set; }
public virtual Employer EmployerInfo { get; set; }
}
In the Database context I have set the relation as
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired()
.Map(x => x.MapKey("EmployerID"));
After executing some actions, database gets created with Employee table having EmployerID as foreign key and one extra key EmployerInfo_EmployerID.
Now when I fetch employer data, I am getting employee details with it.
But when I tried to fetch employee data I am getting EmployerInfo as null. This is because I need relationship from Employee to EmployerInfo.
How do I set the bi-directional relationship in this context?
You need to update your fluent so your relationship mapping contains both ends:
modelBuilder.Entity<Employer>()
.HasMany(p => p.Employees)
.WithRequired(e => e.EmployerInfo)
.Map(x => x.MapKey("EmployerID"));