EF 5.0 not removing optional one to one navigation property - entity-framework

Setting an optional one to one navigation property to null in Entity Framework 5 does not seem to make it to the database. Is this expected behavior?
In the example below, Person is a proxy object. I would expect setting the address to null will cause the address to be removed from the database.
The code below works if I lazy load the address before setting it null. But loading the address before
Any help will be greatly appreciated.
namespace ConsoleApplication2
{
using System;
using System.Data.Entity;
internal class Program
{
private static void Main(string[] args)
{
using (PersonContext context = new PersonContext())
{
// Make sure person with Id = 1 exists with an address.
Person person = context.People.Find(1) ?? context.People.Add(new Person { Id = 1 });
if (person.Address == null)
{
person.Address = new Address
{
Street = "123 Main Street",
City = "SomeCity",
State = new State
{
Code = "NY",
Name = "New York"
},
Zip = "11771"
};
}
context.SaveChanges();
}
// Setting address to null should remove relationship
using (PersonContext context = new PersonContext())
{
Person person = context.People.Find(1);
Console.WriteLine("Person is a " + person.GetType());
person.Address = null;
context.SaveChanges();
if (person.Address == null)
{
Console.WriteLine("Success: Person.Address is null.");
}
else
{
Console.WriteLine("Failure: Person.Address is not null.");
}
}
}
}
public class Person
{
public int Id { get; set; }
public virtual Address Address { get; set; }
}
public class Address
{
public int Person_Id { get; set; }
public string Street { get; set; }
public string City { get; set; }
public int StateId { get; set; }
public State State { get; set; }
public string Zip { get; set; }
}
public class State
{
public int Id { get; set; }
public string Code { get; set; }
public string Name { get; set; }
}
public class PersonContext : DbContext
{
public DbSet<Person> People { get; set; }
public DbSet<State> States { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Address>()
.HasKey(x => x.Person_Id);
modelBuilder.Entity<Person>()
.HasOptional<Address>(x => x.Address)
.WithRequired()
.WillCascadeOnDelete();
}
}
}

Where you are not using lazy loading, the related properties such as Address will not be loaded and so will already be null.
To ensure it is always loaded use eager loading :
Person person = context.People.Include(x => x.Address).Single(x => x.Id == 1);

Related

EF6: Single relationship table for multiple related entities

I have a EF Model with many entities, like Nodes, Attributes, Tags, etc.
There is also an "Alias" entity, and pretty much every other entity else can have a many-to-many relationship with Aliases. One of the undesired things about this is the number of tables that are created to track these relationships (eg. NodeAlias, AttributeAlias, etc.).
Are there any design alternatives that could map an Alias to all of the other entities in a single table? I was thinking maybe something along these lines if it's possible:
+---------+--------+-------------+-----------+
| AliasId | NodeId | AttributeId | TagId |
+---------+--------+-------------+-----------+
| 1 | 1 | 2 | 3 |
+---------+--------+-------------+-----------+
I updated my solution to provide many-to-many relationships between aliases and every other entity.
I intentionally posted this as a separate answer so that my previous answer can also remain here if anyone would need it.
Step #1: I created extension methods for getting and setting property values using reflection in a convenient way:
public static class ObjectExtensions
{
public static TResult GetPropertyValue<TResult>(this object entity, string propertyName)
{
object propertyValue = entity?.GetType().GetProperty(propertyName)?.GetValue(entity);
try
{
return (TResult)propertyValue;
}
catch
{
return default(TResult);
}
}
public static void SetPropertyValue(this object entity, string propertyName, object value)
{
entity?.GetType().GetProperty(propertyName)?.SetValue(entity, value);
}
}
Step #2: I updated the models to provide many-to-many relationship.
public class Node
{
[Key]
public int NodeId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Attribute
{
[Key]
public int AttributeId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class Alias
{
[Key]
public int AliasId { get; set; }
public string Name { get; set; }
public virtual ICollection<AliasMapping> AliasMappings { get; set; }
}
public class AliasMapping
{
[Key]
public int Id { get; set; }
[ForeignKey("Alias")]
public int AliasId { get; set; }
public Alias Alias { get; set; }
[ForeignKey("Node")]
public int? NodeId { get; set; }
public virtual Node Node { get; set; }
[ForeignKey("Attribute")]
public int? AttributeId { get; set; }
public virtual Attribute Attribute { get; set; }
[ForeignKey("Tag")]
public int? TagId { get; set; }
public virtual Tag Tag { get; set; }
}
Step #3: Due to relationship changes the MyDbContext could have been simplified as the [ForeignKey] data annotations are enough.
public class MyDbContext : DbContext
{
public DbSet<Node> Nodes { get; set; }
public DbSet<Attribute> Attributes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<AliasMapping> AliasMappings { get; set; }
}
Step #4: I also updated the extension methods so that you can create and remove alias mappings.
public static class AliasExtensions
{
public static void CreateMapping(this MyDbContext context, object entity, Alias alias)
{
if (entity == null || alias == null)
{
return;
}
string mappingEntityPropertyName = entity.GetType().Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName);
AliasMapping[] mappings =
context
.AliasMappings
.Where(mapping => mapping.AliasId == alias.AliasId)
.ToArray();
if (mappings.Any(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId))
{
// We already have the mapping between the specified entity and alias.
return;
}
bool usableMappingExists = true;
var usableMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == null);
if (usableMapping == null)
{
usableMappingExists = false;
usableMapping = new AliasMapping()
{
Alias = alias
};
}
usableMapping.SetPropertyValue(mappingEntityPropertyName, entity);
usableMapping.SetPropertyValue(entityKeyPropertyName, entityId);
if (!usableMappingExists)
{
context.AliasMappings.Add(usableMapping);
}
// This step is required here, I think due to using reflection.
context.SaveChanges();
}
public static void RemoveMapping(this MyDbContext context, object entity, Alias alias)
{
if (entity == null || alias == null)
{
return;
}
string mappingEntityPropertyName = entity.GetType().Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
int entityId = entity.GetPropertyValue<int>(entityKeyPropertyName);
AliasMapping[] mappings =
context
.AliasMappings
.Where(mapping => mapping.AliasId == alias.AliasId)
.ToArray();
AliasMapping currentMapping = mappings.FirstOrDefault(mapping => mapping.GetPropertyValue<int?>(entityKeyPropertyName) == entityId);
if (currentMapping == null)
{
// There is no mapping between the specified entity and alias.
return;
}
currentMapping.SetPropertyValue(mappingEntityPropertyName, null);
currentMapping.SetPropertyValue(entityKeyPropertyName, null);
// This step is required here, I think due to using reflection.
context.SaveChanges();
}
}
Step #5: Updated the console app steps to align it with the changes.
class Program
{
static void Main(string[] args)
{
// Consider specify the appropriate database initializer!
// I use DropCreateDatabaseAlways<> strategy only for this example.
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
var aliases =
Enumerable
.Range(1, 9)
.Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) })
.ToList();
var attributes =
Enumerable
.Range(1, 5)
.Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) })
.ToList();
var nodes =
Enumerable
.Range(1, 5)
.Select(index => new Node() { Name = String.Format("Node{0:00}", index) })
.ToList();
var tags =
Enumerable
.Range(1, 5)
.Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) })
.ToList();
using (var context = new MyDbContext())
{
context.Aliases.AddRange(aliases);
context.Nodes.AddRange(nodes);
context.Attributes.AddRange(attributes);
context.Tags.AddRange(tags);
// Always save changes after adding an entity but before trying to create a mapping.
context.SaveChanges();
// One Alias To Many Entities
context.CreateMapping(nodes[0], aliases[0]);
context.CreateMapping(nodes[1], aliases[0]);
context.CreateMapping(nodes[2], aliases[0]);
context.CreateMapping(nodes[3], aliases[0]);
context.CreateMapping(attributes[0], aliases[0]);
context.CreateMapping(attributes[1], aliases[0]);
context.CreateMapping(attributes[2], aliases[0]);
context.CreateMapping(tags[0], aliases[0]);
context.CreateMapping(tags[1], aliases[0]);
// One Entity To Many Aliases
context.CreateMapping(nodes[4], aliases[0]);
context.CreateMapping(nodes[4], aliases[1]);
context.CreateMapping(nodes[4], aliases[2]);
context.CreateMapping(attributes[3], aliases[1]);
context.CreateMapping(attributes[3], aliases[3]);
context.CreateMapping(tags[2], aliases[2]);
context.CreateMapping(tags[2], aliases[3]);
// Remove mapping
context.RemoveMapping(nodes[4], aliases[0]);
// Not really needed here as both 'CreateMapping' and 'RemoveMapping' save the changes
context.SaveChanges();
}
Console.Write("Press any key to continue . . .");
Console.ReadKey(true);
}
}
Please note: RemoveMapping() will not delete an AliasMapping even if no entity is associated with it! But CreateMapping() will make use of it later if needed. E.g. look at the screenshot below and check AliasMapping where Id = 5.
Screenshot about the execution result:
You were talking about many-to-many relationship but reading your post I think it is more likely a "special one-to-many" relationship, actually "combined multiple one-to-one" relationship as I see that an Alias can be mapped to a single Node AND/OR to a single Attribute AND/OR to a single Tag.
I think I found a solution for this case.
If it's not the case and an Alias can be mapped to multiple Node AND/OR to multiple Attribute AND/OR to multiple Tag then I think this solution below needs only a small change. :)
Step #1 - These are my example models
public class Node
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Attribute
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Tag
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
public class Alias
{
[Key]
public int AliasId { get; set; }
public string Name { get; set; }
public virtual AliasMapping AliasMapping { get; set; }
}
Step #2 - Creating the custom mapping table
public class AliasMapping
{
[Key]
[ForeignKey("Alias")]
public int AliasId { get; set; }
public Alias Alias { get; set; }
[ForeignKey("Node")]
public int NodeId { get; set; }
public virtual Node Node { get; set; }
[ForeignKey("Attribute")]
public int AttributeId { get; set; }
public virtual Attribute Attribute { get; set; }
[ForeignKey("Tag")]
public int TagId { get; set; }
public virtual Tag Tag { get; set; }
}
Step #3 - Creating the DbContext
public class MyDbContext : DbContext
{
public DbSet<Node> Nodes { get; set; }
public DbSet<Attribute> Attributes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Alias> Aliases { get; set; }
public DbSet<AliasMapping> AliasMappings { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Attribute)
.WithOptionalPrincipal(attribute => attribute.AliasMapping)
.Map(config => config.MapKey("AliasId"));
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Node)
.WithOptionalPrincipal(node => node.AliasMapping)
.Map(config => config.MapKey("AliasId"));
modelBuilder
.Entity<AliasMapping>()
.HasOptional(mapping => mapping.Tag)
.WithOptionalPrincipal(tag => tag.AliasMapping)
.Map(config => config.MapKey("AliasId"));
}
}
Step #4 - Creating extension method so that creating a relationship will be easy
public static class AliasExtensions
{
public static void CreateMapping<TEntity>(this MyDbContext context, TEntity entity, Alias alias)
{
string mappingEntityPropertyName = typeof(TEntity).Name;
string entityKeyPropertyName = String.Concat(mappingEntityPropertyName, "Id");
bool entityExists = true;
var mapping = context.AliasMappings.Find(alias.AliasId);
if (mapping == null)
{
entityExists = false;
mapping = new AliasMapping()
{
Alias = alias
};
}
typeof(AliasMapping)
.GetProperty(mappingEntityPropertyName)
.SetValue(mapping, entity);
typeof(AliasMapping)
.GetProperty(entityKeyPropertyName)
.SetValue(mapping, typeof(TEntity).GetProperty("Id").GetValue(entity));
if (!entityExists)
{
context.AliasMappings.Add(mapping);
}
}
}
Step #5 - Created a console app to see this working
class Program
{
static readonly Random rnd = new Random(DateTime.Now.TimeOfDay.Milliseconds);
static void Main(string[] args)
{
Database.SetInitializer(new DropCreateDatabaseAlways<MyDbContext>());
var aliases =
Enumerable
.Range(1, 9)
.Select(index => new Alias() { Name = String.Format("Alias{0:00}", index) })
.ToList();
var attributes =
Enumerable
.Range(1, 5)
.Select(index => new Attribute() { Name = String.Format("Attribute{0:00}", index) })
.ToList();
var nodes =
Enumerable
.Range(1, 5)
.Select(index => new Node() { Name = String.Format("Node{0:00}", index) })
.ToList();
var tags =
Enumerable
.Range(1, 5)
.Select(index => new Tag() { Name = String.Format("Tag{0:00}", index) })
.ToList();
using (var context = new MyDbContext())
{
context.Aliases.AddRange(aliases);
context.Nodes.AddRange(nodes);
context.Attributes.AddRange(attributes);
context.Tags.AddRange(tags);
context.SaveChanges();
// Associate aliases to attributes
attributes.ForEach(attribute =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Attribute == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(attribute, selectedAlias);
});
// Associate aliases to nodes
nodes.ForEach(node =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Node == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(node, selectedAlias);
});
// Associate aliases to tags
tags.ForEach(tag =>
{
var usableAliases = aliases.Where(alias => alias.AliasMapping?.Tag == null).ToList();
var selectedAlias = usableAliases[rnd.Next(usableAliases.Count)];
context.CreateMapping(tag, selectedAlias);
});
context.SaveChanges();
}
Console.Write("Press any key to continue . . .");
Console.ReadKey(true);
}
}

The EntityType does not declare a navigation property with the name 'StateId' [duplicate]

This question already has answers here:
A specified Include path is not valid. The EntityType does not declare a navigation property with the name *
(5 answers)
Closed 3 years ago.
I am getting below error while trying to access below method,
public ZipCode GetByZip(string zip)
{
using (GeoLibDbContext entityContext = new GeoLibDbContext())
{
return entityContext.ZipCodeSet.Include(e => e.StateId).FirstOrDefault(e => e.Zip == zip);
}
}
Below are the POCO and db context classes,
public class State
{
public int StateId { get; set; }
public string Name { get; set; }
}
public class ZipCode
{
public int ZipCodeId { get; set; }
public string City { get; set; }
public int StateId { get; set; }
public string Zip { get; set; }
public string County { get; set; }
public State State { get; set; }
}
public class HelloLibDbContext : DbContext
{
public HelloLibDbContext()
: base("name=HelloLibDbContext")
{
Database.SetInitializer<HelloLibDbContext>(null);
}
public DbSet<ZipCode> ZipCodeSet { get; set; }
public DbSet<State> StateSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ZipCode>().HasKey<int>(e => e.ZipCodeId)
.HasRequired(e => e.State).WithMany().HasForeignKey(e => e.StateId);
modelBuilder.Entity<State>().HasKey<int>(e => e.StateId);
}
}
If I removed ".Include(e => e.StateId)", then I m getting any error, but at the same time "State" property is null, which defines in "ZipCode" class.
What could be the reason? Please suggest!!!
You should include State. Not StateId
public ZipCode GetByZip(string zip)
{
using (GeoLibDbContext entityContext = new GeoLibDbContext())
{
return entityContext.ZipCodeSet.Include(e => e.State).FirstOrDefault(e => e.Zip == zip);
}
}
Include works with navigation property, and in your case navigation property is State.

Entity framework replaces delete+insert with an update. How to turn it off

I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}

Mapping ICollection to IEnumerable using automap issue

Stuck on a strange issue, that works using my get method but fails when returning iqueryable. Some limitation when using projection or iqueryable?
My code looks like below, simplified:
public class SimpleEntity
{
public long Id { get; set; }
public string Name { get; set; }
public virtual SimpleEntity Parent { get; set; }
public virtual ICollection<SimpleEntity> Children { get; set; }
public SimpleEntity()
{
Children = new List<SimpleEntity>();
}
}
public class SimpleEntityResponseDTO
{
public long Id { get; set; }
public string Name { get; set; }
public NameValueItem ParentReferral { get; set; }
public IEnumerable<NameValueItem> ChildReferrals { get; set; }
public NavigationFolderResponseDTO()
{
ChildReferrals = new List<NameValueItem>();
}
}
public class NameValueItem
{
public long Value { get; set; }
public string Name { get; set; }
}
The web api actions:
[HttpGet, Queryable]
public IQueryable<SimpleEntityResponseDTO> List()
{
//Generic crudservice returning an iqueryable based on Set<SimpleEntity>
return _crudService.QueryableList().Project().To<SimpleEntityResponseDTO>();
}
[HttpGet]
public HttpResponseMessage Get(long id)
{
SimpleEntity result = _crudSrv.Get(id);
if (result != null)
return Request.CreateResponse<SimpleEntityResponseDTO>(HttpStatusCode.OK, Mapper.Map<SimpleEntity , SimpleEntityResponseDTO>(result));
else
return Request.CreateResponse(HttpStatusCode.NotFound);
}
And now the mapping:
Mapper.CreateMap<SimpleEntity, SimpleEntityResponseDTO>()
.ForMember(to => to.ParentReferral, opt => opt.MapFrom(from => new NameValueItem { Name = from.Parent.Name, Value = from.Parent.Id }))
.ForMember(to => to.ChildReferrals, opt => opt.MapFrom(from => from.Children.Select(o => new NameValueItem {Name = o.Name, Value = o.Id}).ToList() ));
The parent mapping works no matter what. But the Children mapping is causing below issue.
When retrieving an object through the get method everything works, no matter wich entity i retrieve. When using List i get "Object reference not set to an instance of an object", "d__b.MoveNext()\r\n--- End of stack trace from previous location where exception was thrown ---\r\n". Tried for example adding $filter=Id eq 5 (or whatever id) but results in same issue. Perhaps someone can hint me to what goes wrong here?

My collections / model look fine after saving, but when loading entity I do not get the data back out. What am I doing wrong?

Here is my source code for my model:
public class User
{
public User()
{
GUID = Guid.NewGuid();
Account = new Account();
Location = new Location();
}
public long UserID { get; set; }
public Guid GUID { get; set; }
public string DisplayName { get; set; }
public Location Location { get; set; }
public virtual Account Account { get; set; }
}
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
HasKey(x => x.UserID);
}
}
[ComplexType]
public class Location
{
[MaxLength(2)]
public string CountryCode { get; set; }
[MaxLength(2)]
public string StateCode { get; set; }
public string City { get; set; }
}
public class Account
{
public Account()
{
if (EmailAddresses == null) EmailAddresses = new Collection<EmailAddress>();
}
[ForeignKey("User")]
public long AccountID { get; set; }
public ICollection<EmailAddress> EmailAddresses { get; set; }
public virtual User User { get; set; }
}
public class AccountConfiguration : EntityTypeConfiguration<Account>
{
public AccountConfiguration()
{
HasKey(x => x.AccountID);
HasMany(x => x.EmailAddresses).WithRequired(x => x.Account);
}
}
public class EmailAddress
{
[Key]
public string Email { get; set; }
public EmailTypes Type { get; set; }
public long AccountID { get; set; }
public virtual Account Account { get; set; }
}
public class EmailAddressConfiguration : EntityTypeConfiguration<EmailAddress>
{
public EmailAddressConfiguration()
{
HasKey(x => x.Email);
HasRequired(x => x.Account).WithMany(x => x.EmailAddresses).HasForeignKey(x => x.AccountID);
}
}
And here is my Entity Class:
public class MyEntities : DbContext
{
public MyEntities()
{
Database.SetInitializer<MyEntities>(new DropCreateDatabaseAlways<MyEntities>());
}
public DbSet<User> Users { get; set; }
public DbSet<Account> Accounts { get; set; }
public DbSet<EmailAddress> EmailAddresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UserConfiguration());
modelBuilder.Configurations.Add(new AccountConfiguration());
modelBuilder.Configurations.Add(new EmailAddressConfiguration());
base.OnModelCreating(modelBuilder);
}
}
And finally my code that runs in a test console application:
static void Main(string[] args)
{
var id = CreateUser();
using (MyEntities db = new MyEntities())
{
var a = db.Users.Find(id);
var b = a.Account.EmailAddresses;
var c = db.Accounts.Find(id);
var d = db.EmailAddresses.Where(x => x.Account.AccountID == id).ToList();
}
}
private static long CreateUser()
{
using (MyEntities db = new MyEntities())
{
var u = new User();
u.DisplayName = "TEST";
u.Location.CountryCode = "US";
u.Location.StateCode = "HI";
u.Location.City = "Kauai";
EmailAddress e = new EmailAddress();
e.Email = DateTime.UtcNow.Ticks + "#microsoft.com";
e.Type = EmailTypes.Current;
u.Account.EmailAddresses.Add(e);
db.Users.Add(u);
var cnt = db.SaveChanges();
// Here I get a return of the 4 entities saved, and my model looks correct.
return u.UserID;
}
}
Once the model was saved (CreateUser), I was able to navigate the model and everything looked perfect.
The issue arises when I try to pull the data back out.
My variables:
a -- navigating to email adderess shows 0 records.
b -- this too shows 0 records in the collection.
c -- navigating to email adderess shows 0 records.
d -- here I can get email addresses (but not by navigating the model)
Your test code to access the navigation properties relies on lazy loading. But your Account.EmailAddresses collection is not marked as virtual:
public ICollection<EmailAddress> EmailAddresses { get; set; }
Navigation properties must be virtual (like your User.Account property) in order to make lazy loading possible.
As a side note: I recommend to remove the instantiation of the Account navigation property...
Account = new Account();
...from the User constructor. This is a known source for trouble:
What would cause the Entity Framework to save an unloaded (but lazy loadable) reference over existing data?
EF 4.1 Code First: Why is EF not setting this navigation property?
Instantiating Location is fine because it's a complex type and not a navigation property.