How to modify this Entity framework code first code? - entity-framework

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.

Related

Foreign table using EF core

I have a advertiser model like:
public class Advertiser
{
public int AdvertiserId { get; set; }
public string Name { get; set; } = string.Empty;
public Address AddressId { get; set; }
}
Inside this class I have a builder as:
public class AdvertiserConfiguration : IEntityTypeConfiguration<Advertiser>
{
public void Configure(EntityTypeBuilder<Advertiser> builder)
{
builder.ToTable("Advertisers");
builder.HasKey(x => x.AdvertiserId);
builder.Property(x => x.Name).IsRequired().HasMaxLength(250);
builder.HasOne(x => x.AddressId);
}
}
And address model like:
public class Address
{
public int AddressId { get; set; }
....
}
So that I want to do is a simple foreign key on the Advertiser table so I check msdn reference
And it says that I should use HasOne and WithMany methods in order to use HasForeignKey, but I do not understand why? it is necessary to use them to do a simple foreign key connection? if yes, what fields should I use on HasOne and WithMany? Thanks!
In ef for a relation you define a "navigation property" on both sides of the related objects and a "foreign key property". So your entities should look like this
public class Advertiser
{
public int AdvertiserId { get; set; }
public Address? Address { get; set; }
public int AddressId { get; set; }
...
}
public class Address
{
public int AddressId { get; set; }
public virtual ICollection<Advertiser>? Advertisers { get; set; }
...
}
and your entity configuration
builder
.HasOne(adv => adv.Address)
.WithMany(adr => adr.Advertisers)
.HasForeignKey(adv => adv.AddressId);
That way you define which properties are the connected objects and how ef should resolve this from the database (by using the foreign key).
Now you can use code like this
foreach(var advertiser in address.Advertisers)
{
...
}
or
var street = advertiser.Address.Street;
...
You won't want to do all the navigation manually by requerying the database e. g. for the connected advertisers after you read an address.
Remember to Include navigation properties in your queries, when they will be used after/outside of the queries.

Include after select not load navigation property in Entity Framework core

I use entity framework core 1.1.
I have a query like below, and I expect to users who have UserProfile by using Include, load UserProfile.
But this query always return UserProfile null .
Query:
var user = dbContext.UserMappers
.Where(e => e.OldUserId == id)
.Select(e => e.User)
.Include(e=>e.UserProfile)
.FirstOrDefault();
Models:
public class UserMapper
{
[Key, ForeignKey(nameof(User))]
public string UserId { get; set; }
public User User { get; set; }
public int OldUserId { get; set; }
}
public class User : IdentityUser
{
public bool Suspended { get; set; }
public string Nickname { get; set; }
public virtual UserProfile UserProfile { get; set; }
}
public class UserProfile
{
[Key, ForeignKey(nameof(User))]
public string UserId { get; set; }
public string Name { get; set; }
public string Family { get; set; }
public string Telephone { get; set; }
}
From the EF Core documentation - Loading Related Data - Ignored includes section (highlight is mine):
If you change the query so that it no longer returns instances of the entity type that the query began with, then the include operators are ignored.
This is different from EF6 where Include works on the final query entity type. I don't know if this is a current limitation or "by design", but for now you have to start your queries with the entity requiring includes.
In your case, it should be something like this:
var user = dbContext.Users
// if you don't have inverse navigation property
.Where(e => dbContext.UserMappers.Any(um => um.UserId == e.Id && um.OldUserId == id))
// if you have inverse collection navigation property
//.Where(e => e.UserMappers.Any(um.OldUserId == id))
// if you have inverse reference navigation property
//.Where(e => e.UserMapper.OldUserId == id)
.Include(e => e.UserProfile)
.FirstOrDefault();

Entity Framework add object to related entity without loading

I would like to add an object to a related entity without loading them.
I have Company entity defined like this:
public class Company
{
public int ID { get; set; }
public List<Employee> EmployeeList{ get; set; }
}
And Employee entity like this
public class Employee
{
public int ID { get; set; }
public String Name{ get; set; }
}
I want to add an employee to a list placed in that company object without loading all the employees.
I know I can use this expression
Company myCompany= systemBD.Companies.Include("EmployeeList").Find(1) myCompany.EmployeeList.Add(newEmployee)
but I'm afraid that this would consume a lot of time since I have thousands of employees in my database.
Is there a way to add a new employee to an existing company without loading the list of Employees?
I was looking into the Attach method but it does not seem to work.
using (var systemDB = new CompanyDB())
{
Employee employee = new Employee ();
Company companySearch = systemDB.Companies.Where(d => d.Name.Equals("John")).SingleOrDefault();
if (companySearch != null)
{
if (companySearch.EmployeeList != null)
{
systemDB.Companies.Attach(companySearch );
companySearch.EmployeeList.Add(employee);
systemDB.SaveChanges();
}
}
I tried that code but it doesn't work.
Assuming you have your Company and Employee entities defined to have both a navigation property from a Company to the collection of all of its associated Employees and a property from an Employee to its single associated Company, you can accomplish creating a new Employee and associating it with an existing Company from the Employees DB set.
[Table("Company")]
public partial class Company
{
public Company()
{
this.Employees = new HashSet<Employee>();
}
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
[Table("Employee")]
public partial class Employee
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
public partial class Database : DbContext
{
public Database()
: base("name=Database")
{
}
public virtual DbSet<Company> Companies { get; set; }
public virtual DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Company>()
.HasMany(e => e.Employees)
.WithRequired(e => e.Company)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Employee>()
.Property(e => e.Name)
.IsUnicode(false);
}
}
Then assuming you already have a Company in the system with an Id of 1, you can do the following:
using (var database = new Database())
{
var company = database.Companies.Find(1);
if (company != null)
{
var employee = new Employee
{
Name = "John Doe",
Company = company
};
database.Employees.Add(employee);
database.SaveChanges();
}
}
OR...if you are sure that Company Id 1 definitely exists...
using (var database = new Database())
{
var employee = new Employee
{
Name = "John Doe",
CompanyId = 1
};
database.Employees.Add(employee);
database.SaveChanges();
}
I think you would need to change your Database Design to accomplish what you want.
Employee table
ID (Primary key)
Name
Company table
ID (Primary key)
Name
EmployeeCompany table
IDCompany (Foreign Key)
IDEmployee (ForeignKey)
This way you will accomplish what you want

Entity Framework 1:0 or 1 Relationship

I have the following model:
public class User
{
public int Id { get; set; }
public Customer Customer { get; set; }
...
}
public class Customer
{
public int Id { get; set; }
public int UserId { get; set; }
public User User { get; set; }
...
}
What I want to have is the Customer has to have an User, but can only have one, and the User does not have to have a Customer.
I would like to do it with the Fluent API, but I can't manage to get it to work so that both Customer and User have their Id properties be Identity Fields.
When you are configuring an one-to-one relationship, Entity Framework requires that the primary key of the dependent also be the foreign key, in your case it would be:
public class User
{
public int Id { get; set; }
public virtual Customer Customer { get; set; }
...
}
public class Customer
{
[Key, ForeignKey("User")]
public int UserId { get; set; }
public virtual User User { get; set; }
...
}
But you want each entities with its own PK, so, EF lets you do that but you should delete the UserId property from Customer entity, because, as I said before, in this kind of relationship the FK must be PK too. To configure properly your relationship use the Required data annotation as #Claies recommend you in his comment:
public class User
{
public int Id { get; set; }
public virtual Customer Customer { get; set; }
...
}
public class Customer
{
[Key]
public int Id { get; set; }
[Required]
public virtual User User { get; set; }
...
}
Or you can use Fluent Api, the configuration would be:
modelbuilder.Entity<Customer>().HasRequired(c=>c.User).WithOptional(u=>u.Customer);
Another thing, I recommend you define the navigation properties as virtual. This way, when you consult those properties the first time, they will be lazy loaded. Check this post for more info.
Update 1:
When the key property is an integer, Code First defaults to
DatabaseGeneratedOption.Identity. If you want, you can configure explicitly what you need using the [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)] attributes on the Customer Id.
public class Customer
{
[Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
}
Or you can use Fluent Api:
modelbuilder.Entity<Customer>().HasKey(t => t.Id)
.Property(t => t.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Update 2:
I don't understand why is throwing you that exception. I just tested with both variants (Data Annotations and Fluent Api) and everything works well. This is the code generated by Migrations:
public partial class changeCustomerIdToIdentity : DbMigration
{
public override void Up()
{
DropIndex("dbo.Customers", new[] { "Id" });
DropPrimaryKey("dbo.Customers");
AlterColumn("dbo.Customers", "Id", c => c.Int(nullable: false, identity: true));
AddPrimaryKey("dbo.Customers", "Id");
CreateIndex("dbo.Customers", "Id");
}
public override void Down()
{
DropIndex("dbo.Customers", new[] { "Id" });
DropPrimaryKey("dbo.Customers");
AlterColumn("dbo.Customers", "Id", c => c.Int(nullable: false));
AddPrimaryKey("dbo.Customers", "Id");
CreateIndex("dbo.Customers", "Id");
}
}
I'm afraid your error is happened due to your DB schema. The Id on your Customers table must be FK too. The error means that you have some relation between your entities where foreign key property in dependent entity is defined as store generated, and that is because you are trying change the Id of your Customer entity as Identity, which is FK in your DB.

EF5 List of one to one dont materialized every entities

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();