Reputation: 2395
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.
Upvotes: 1
Views: 138
Reputation: 177163
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
.
Upvotes: 1