One of the requieremets for change tracking proxies is that a navigation property that represents the "many" end of a relationship must return a type that implements ICollection.
Change tracking proxies also provide classes with automatic relationship fix-up. For example, when someEmployee.Addresses.Add(address); is executed, proxy automatically sets address.EmployeeID to value of 100 and also assigns someEmployee instance to a navigation property address.Employee:
public class Employee
{
public virtual int EmployeeID { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
Employee someEmployee = ...;
Address address = ...;
Console.WriteLine(someEmployee.EmployeeID); // 100
Console.WriteLine(address.EmployeeID); // 20
someEmployee.Addresses.Add(address);
Console.WriteLine(address.EmployeeID); // 100
Console.WriteLine(address.Employee.EmployeeID); // 100
But if we change the definition of Employee class, then for some reason proxy isn't able to automatically fix-up the relationship:
public class Employee
{
private List<Address> _addresses = new List<Address>();
public virtual int EmployeeID { get; set; }
public virtual ICollection<Address> Addresses
{
get { return _addresses; }
set { _addresses = value.ToList(); }
}
}
Console.WriteLine(someEmployee.EmployeeID); // 100
Console.WriteLine(address.EmployeeID); // 20
someEmployee.Addresses.Add(address);
Console.WriteLine(address.EmployeeID); // 20
Console.WriteLine(address.Employee.EmployeeID); // 20
Navigation property Employee.Addresses does return a type that implements ICollection ( List<T> ), so why isn't proxy able to fix-up the relationship?
Thank you
EDIT
It is because the proxy itself doesn't fixup the relation. It replaces your instantiated collections with its own but once you call value.ToList() you are throwing away its implementation with fixup logic.
But if calling value.ToList() is the reason why automatic relationship fix-up doesn't work, then removing the setter method should enable automatic relationship fix-up, but it doesn't:
public class Employee
{
private List<Address> _addresses = new List<Address>();
public virtual int EmployeeID { get; set; }
public virtual ICollection<Address> Addresses
{
get { return _addresses; }
}
}
It is because the proxy itself doesn't fixup the relation. It replaces your instantiated collections with its own but once you call value.ToList() you are throwing away its implementation with fixup logic. Use this instead and it should work as expected:
public class Employee
{
public ICollection<Address> addresses = new List<Address>();
public virtual int EmployeeId { get; set; }
public virtual ICollection<Address> Addresses
{
get { return addresses; }
set { addresses = value; }
}
}
You can also try this:
public class Employee {
private ICollection<Address> _addresses = new HashSet<Address>();
public virtual int EmployeeID { get; set; }
public virtual ICollection<Address> Addresses {
get { return _addresses ?? (_addresses = new HashSet<Address>()); }
protected set { _addresses = value; }
}
}
The advantage is that the Addresses collection in the POCO class will also be automatically instantiated when you create your entity with new (rather than using the CreateObject or Create methods) for use in situations where proxies are undesirable (e.g. serialization).
Another change is that the ICollection is implemented as a HashSet<> instead of a List<>, ensuring uniqueness.
Related
Assume this simple Domain in my core assembly:
public class Country
{
protected ICollection<Province> _provinces = null;
public virtual int Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string IsoCode2 { get; set; }
public virtual string IsoCode3 { get; set; }
public virtual int IsoCodeNumeric { get; set; }
public virtual ICollection<Province> Provinces
{
get { return _provinces ?? (_provinces = new List<Province>()); }
set { _provinces = value; }
}
}
public class Province
{
public virtual int Id { get; protected set; }
public virtual Country Country { get; set; }
public virtual string Name { get; set; }
public virtual string Abbreviation { get; set; }
}
The view models in my presentation layer are almost the same:
public class CountryModel
{
public int Id { get; set; }
public string Name { get; set; }
public string IsoCode2 { get; set; }
public string IsoCode3 { get; set; }
public int IsoCodeNumeric { get; set; }
public int NumberOfProvinces { get; set; }
}
public class ProvinceModel
{
public int Id { get; set; }
public int CountryId { get; set; }
public string Name { get; set; }
public string Abbreviation { get; set; }
}
I am creating some Extension methods for mapping back and forth between domain objects/view models:
public static class Extensions
{
public static Country ToEntity(this CountryModel model, Country entity = null)
{
if (entity == null)
entity = new Country();
entity.Name = model.Name;
entity.IsoCode2 = model.IsoCode2;
entity.IsoCode3 = model.IsoCode3;
entity.IsoCodeNumeric = model.IsoCodeNumeric;
entity.AddressFormat = model.AddressFormat;
entity.CanBillTo = model.CanBillTo;
entity.CanShipTo = model.CanShipTo;
entity.IsPublished = model.IsPublished;
return entity;
}
public static CountryModel ToModel(this Country entity, bool includeProvinceCount = false, CountryModel model = null)
{
if (model == null)
model = new CountryModel();
model.Id = entity.Id;
model.Name = entity.Name;
model.IsoCode2 = entity.IsoCode2;
model.IsoCode3 = entity.IsoCode3;
model.IsoCodeNumeric = entity.IsoCodeNumeric;
model.AddressFormat = entity.AddressFormat;
model.CanBillTo = entity.CanBillTo;
model.CanShipTo = entity.CanShipTo;
model.IsPublished = entity.IsPublished;
if (includeProvinceCount)
model.NumberOfProvinces = entity.Provinces.Count;
return model;
}
public static Province ToEntity(this ProvinceModel model, Province entity = null)
{
if (entity == null)
entity = new Province();
//entity.Country = LoadCountryById(model.CountryId); ???? <-- HERE
entity.Name = model.Name;
entity.Abbreviation = model.Abbreviation;
entity.CanBillTo = model.CanBillTo;
entity.CanShipTo = model.CanShipTo;
entity.IsPublished = model.IsPublished;
return entity;
}
public static ProvinceModel ToModel(this Province entity, ProvinceModel model)
{
if (model == null)
model = new ProvinceModel();
model.Id = entity.Id;
model.CountryId = entity.Country.Id;
model.Name = entity.Name;
model.Abbreviation = entity.Abbreviation;
model.CanBillTo = entity.CanBillTo;
model.CanShipTo = entity.CanShipTo;
model.IsPublished = entity.IsPublished;
return model;
}
}
With Entity Framework, the Province domain object would have had both Country and the corresponding CountryId properties. I could assign the Country by simply setting the CountryId.
With NHibernate, the id of the foreign key is unnecessary when creating the domain. So how do you map the ProvinceModel CountryId back to a Country object?
I've gone through all kinds of steps to abstract things into interfaces and use Dependency Injection. Should I use a service locator from within the mapping extensions and look it up? Should I look up the country outside of the mapping extension and require it as a parameter on the extension method? What are the recommended ways of handing this scenario?
Second, with NHibernate they recommend adding helper functions to the domain objects in order to maintain associations (not positive, but I think EF handles this "automagically" for me). For example, I would add a SetCountry method on Province, and AddProvince and RemoveProvince methods on Country.
Doesn't this hurt performance? Instead of simply setting the Country for a Province (which is where the association is managed), the entire list of the new Country's Provinces are loaded to see if it is already in the list before adding to the collection, then the entire list of the old Country's Provinces are loaded to see if the province needs to be removed from the collection.
[in EF] I could assign the Country by simply setting the CountryId.
This isn't true and in my opinion this is a major defect with Entity Framework. Having both Country and CountryId properties is a hack that allows you to set the Country without retrieving it from the database by setting the CountryId. In a web app this works because the record is saved with the CountryId foreign key set so the next time you load it the Country is populated. NHibernate's solution to this pattern is the ISession.Load method that creates a dynamic proxy.
In your example you would do something like
province.Country = session.Load<Country>(provinceModel.CountryId);
As to your second question, in general I only use methods to encapsulate access to collections. This ensures that the collection itself is not replaced by a setter and allows me to maintain both sides of the relationship. I would model this as:
public class Country
{
private ICollection<Province> _provinces;
public Country()
{
_provinces = new HashSet<Province>();
}
public virtual IEnumerable<Province> Provinces
{
get { return _provinces; }
}
public virtual void AddProvince(Province province)
{
province.Country = this;
_provinces.Add(province);
}
public virtual void RemoveProvince(Province province)
{
province.Country = null;
_provinces.Remove(province);
}
}
As you noted, this does require loading the collection. You have to remember that NHibernate (and Hibernate) were originally designed for stateful applications and many of the usage patterns are not strictly necessary in stateless web applications. However, I would profile performance before deviating from some of these patterns. For example, you may want to validate your objects before committing them and that requires that the in-memory representations are consistent.
I have the following two classes that are able to internally track all changes.
public class Agent
{
public int AgentId { get; set; }
public ICollection Roles { get; set; }
public ICollection DeletedCollectionItems { get; set; }
public ICollection NewCollectionItems { get; set; }
ChangeTrackingState State { get; set; }
....
}
public class Role
{
public int RoleId { get; set; }
public ICollection Agents { get; set; }
public ICollection DeletedCollectionItems { get; set; }
public ICollection NewCollectionItems { get; set; }
ChangeTrackingState State { get; set; }
...
}
1) When an navigation property is set if it was previously set then the original object is added to DeletedCollectionItems and the new object is added to NewCollectionItems.
2) When objects are added or deleted from a collection they also update the relevant collection e.g. when a role is removed from Agent.Roles the role is added to DeletedCollectionItems.
My problem is:
Using EF in a disconnected environment I have to 'replay' all the changes when I attach my root entity (Agent) back to the context. If I'm updating an existing Agent and I've removed a role how can I recreate the relationship that is currently in the database and delete it so that the change is reflected in the database when I call SaveChanges?
I need to do this in a generic way so that I'm not duplicating code and can simply pass and object as the root object. I have based my implementation of Julie Lerman's ApplyChanges method
private static void ApplyChanges<TEntity>(TEntity root)
where TEntity : class, IObjectWithState
{
using (var context = new GeniusContext())
{
if (root.IsNew)
{
context.Set<TEntity>().Add(root);
}
else
{
context.Set<TEntity>().Attach(root);
}
foreach (var entry in context.ChangeTracker
.Entries<IObjectWithState>())
{
IObjectWithState stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
context.SaveChanges();
}
}
I'm trying to insert a new Entity in EF:
public class Ad
{
// Primary properties
public int Kms { get; set; }
// Navigation properties
public virtual Model Model { get; set; }
}
And I receive from the View the model like this (example values):
kms = 222
Model.Id = 3
Then when I do the Add and SaveChanges of the Entity Framework, I get a NULL record inserted in the Model Table (that generated a new ID) and a record in the Ad Table with the new inserted Model Id.
Why is this happening?
Service Layer:
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_adRepository.Add(adDomain);
_adRepository.Save();
}
Repository:
public void Add(T entity)
{
_dbSet.Add(entity);
}
public void Save()
{
try
{
_dataContext.SaveChanges();
}
catch (DbEntityValidationException e)
{
var s = e.EntityValidationErrors.ToList();
throw;
}
}
ViewModel:
public class CreateAdViewModel
{
// Primary properties
public string Version { get; set; }
public int Kms { get; set; }
public int Year { get; set; }
public decimal Price { get; set; }
public int Make_Id { get; set; }
public int Model_Id { get; set; }
// Navigation properties
public IEnumerable<SelectListItem> MakeList { get; set; }
public IEnumerable<SelectListItem> ModelList { get; set; }
}
Dto:
public class CreateAdDto
{
// Primary properties
public int Kms { get; set; }
public int Model_Id { get; set; }
}
The Mapping:
Mapper.CreateMap<CreateAdDto, Ad>().ForMember(dest => dest.Model, opt => opt.MapFrom(src => new Model { Id = src.Model_Id }));
If you just call the Add method on the Ad instance, all the related entities are treated as new entities. Therefore you can attach the Model instance first and add the Ad.
context.Models.Attach(ad.Model);
context.Ads.Add(ad);
context.SaveChanges();
SOLUTION BASED ON #Eranga Answer (thanks man!)
Hope this can help somebody else as it help me thanks to #Eranga.
What it was happening is that in the Mapping from DTO to DOMAIN, the Model entity was maping from an Model_Id coming from a Dropdownlist in the View to an Entity Model (as you can see in the mapping line in the question).
Then, when it was added to the database trough Entity Framework, the EF was not aware of the existence of the Model navigation property in the Ad Domain Entity.
So what I had to create to solve this was adding a new method to my repository to handle the possibility to attach the Model Entity to the Ad Entity context:
public void Attach(T entity)
{
_dbSet.Attach(entity);
}
and ad the attach in the Ad Service Create method:
private readonly IRepository<Ad> _adRepository;
private readonly IRepository<Search> _searchRepository;
private readonly IRepository<Model> _modelRepository;
public AdService(IRepository<Ad> adRepository, IRepository<Search> searchRepository, IRepository<Model> modelRepository)
{
_adRepository = adRepository;
_searchRepository = searchRepository;
_modelRepository = modelRepository;
}
public void CreateAd(CreateAdDto adDto)
{
var adDomain = Mapper.Map<CreateAdDto, Ad>(adDto);
_modelRepository.Attach(adDomain.Model);
_adRepository.Add(adDomain);
_adRepository.Save();
}
So now, when the adDomain object arrives to the EF trough the _adRepository.Add, it already knows about the existence of the navigation property Model, and can add the new adDomain ojbect.
Before, what it was happening is that, when the adDomain object was arriving at the EF, the EF was not aware of the Model existence so it was creating an null record.
Hope this help anybody else.
Regards.
I have a class with a relationship to another table.
public class MyClass
{
[Key]
public Guid Id {get; set; }
public virtual OtherClass OtherClass { get; set; }
}
I hook this up to a controller and create views for CRUD - all works fine.
In the DB a OtherClass_OtherClassId column is created, but this is not in the model.
How can I put a reference in this Id column during the controller's Create method?
How can I force this relationship to be [Required] without having to create a brand new OtherClass each time?
Annotated class with some description:
public class MyClass
{
// [Key] - Don't actually need this attribute
// EF Code First has a number of conventions.
// Columns called "Id" are assumed to be the Key.
public Guid Id {get; set; }
// This reference creates an 'Independent Association'. The Database
// foreign key is created by convention and hidden away in the code.
[Required]
public virtual OtherClass OtherClass { get; set; }
// This setup explicitly declares the foreign key property.
// Again, by convention, EF assumes that "FooId" will be the key for
// a reference to object "Foo"
// This will still be required and a cascade-on-delete property
// like above - an int? would make the association optional.
public int OtherClass2Id { get; set; }
// Leave the navigation property as this - no [Required]
public virtual OtherClass2 { get; set; }
}
So which is better? Independent associations or declaring the foriegn key?
Independent associations match object programming closer. With OOP, one object doesn't really care much about the Id of a member. ORM's try to cover these relationships up, with varying degrees of success.
Declaring the foreign key puts database concerns into your model, but there are scenarios where this makes dealing with EF much easier.
Example - when updating an object with a required independent association, EF will want to have the entire object graph in place.
public class MyClass
{
public int Id {get; set; }
public string Name { get; set; }
[Required] // Note the required. An optional won't have issues below.
public virtual OtherClass OtherClass { get; set; }
}
var c = db.MyClasses.Find(1);
c.Name = "Bruce Wayne";
// Validation error on c.OtherClass.
// EF expects required associations to be loaded.
db.SaveChanges();
If all you want to do is update the name, you'll either have to pull OtherClass from the database as well since it's required for entity validation or attach a stubbed entity (assuming you know the id). If you explicitly declare foreign key, then you won't run into this scenario.
Now with foreign keys, you run into a different issue:
public class MyClass
{
public Guid Id {get; set; }
public string Name { get; set; }
public int OtherClassId { get; set }
public virtual OtherClass OtherClass { get; set; }
}
var c = db.MyClasses.Find(1);
// Stepping through dubugger, here, c.OtherClassId = old id
c.OtherClass = somethingElse;
// c.OtherClassId = old id - Object and id not synced!
db.SaveChanges();
// c.OtherClassId = new id, association persists correctly though.
In summary -
Independent associations
Good: Match OOP and POCO's better
Bad: Often requires a full object graph, even if you're only updating one or two properties. More EF headaches.
Foreign Keys
Good: Easier to work with sometimes - less EF headaches.
Bad: Can be out of sync with their object
Bad: Database concerns in your POCO's
EF generally require handholding with the model configuration. This should get you started. However doing a good tutorial on EF Code First and DB first would be greatly beneficial.
Following has:
Order with multiple OrderItems
single User
and single OrderType made by keeping the identity OrderTypeId and the actual OrderType ref object.
public class Order
{
public Order()
{
OrderItems = new OrderItemCollection();
}
public int OrderID { get; set; }
public DateTime OrderDate { get; set; }
public string OrderName { get; set; }
public int UserId { get; set; }
public virtual User OrderUser { get; set; }
public virtual OrderItemCollection OrderItems { get; set; }
public int? OrderTypeId { get; set; }
public OrderType OrderType { get; set; }
public override int GetHashCode() { return OrderID.GetHashCode();}
}
public class OrderConfiguration : EntityTypeConfiguration
{
public OrderConfiguration()
{
this.ToTable("ORDERS");
this.HasKey(p => p.OrderID);
this.Property(x => x.OrderID).HasColumnName("ORDER_ID");
this.Property(x => x.OrderName).HasMaxLength(200);
this.HasMany(x => x.OrderItems).WithOptional().HasForeignKey(x => x.OrderID).WillCascadeOnDelete(true);
this.HasRequired(u => u.OrderUser).WithMany().HasForeignKey(u => u.UserId);
this.Property(x => x.OrderTypeId).HasColumnName("ORDER_TYPE_ID");
this.HasOptional(u => u.OrderType).WithMany().HasForeignKey(u => u.OrderTypeId);
}
}
public class OrderContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new OrderConfiguration());
}
}
'
I'm guessing this is impossible, but I'll throw it out there anyway. Is it possible to use CreateSourceQuery when programming with the EF4 CodeFirst API, in CTP4? I'd like to eagerly load properties attached to a collection of properties, like this:
var sourceQuery = this.CurrentInvoice.PropertyInvoices.CreateSourceQuery();
sourceQuery.Include("Property").ToList();
But of course CreateSourceQuery is defined on EntityCollection<T>, whereas CodeFirst uses plain old ICollection (obviously). Is there some way to convert?
I've gotten the below to work, but it's not quite what I'm looking for. Anyone know how to go from what's below to what's above (code below is from a class that inherits DbContext)?
ObjectSet<Person> OSPeople = base.ObjectContext.CreateObjectSet<Person>();
OSPeople.Include(Pinner => Pinner.Books).ToList();
Thanks!
EDIT: here's my version of the solution posted by zeeshanhirani - who's book by the way is amazing!
dynamic result;
if (invoice.PropertyInvoices is EntityCollection<PropertyInvoice>)
result = (invoices.PropertyInvoices as EntityCollection<PropertyInvoice>).CreateSourceQuery().Yadda.Yadda.Yadda
else
//must be a unit test!
result = invoices.PropertyInvoices;
return result.ToList();
EDIT2:
Ok, I just realized that you can't dispatch extension methods whilst using dynamic. So I guess we're not quite as dynamic as Ruby, but the example above is easily modifiable to comport with this restriction
EDIT3:
As mentioned in zeeshanhirani's blog post, this only works if (and only if) you have change-enabled proxies, which will get created if all of your properties are declared virtual. Here's another version of what the method might look like to use CreateSourceQuery with POCOs
public class Person {
public virtual int ID { get; set; }
public virtual string FName { get; set; }
public virtual string LName { get; set; }
public virtual double Weight { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class Book {
public virtual int ID { get; set; }
public virtual string Title { get; set; }
public virtual int Pages { get; set; }
public virtual int OwnerID { get; set; }
public virtual ICollection<Genre> Genres { get; set; }
public virtual Person Owner { get; set; }
}
public class Genre {
public virtual int ID { get; set; }
public virtual string Name { get; set; }
public virtual Genre ParentGenre { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
public class BookContext : DbContext {
public void PrimeBooksCollectionToIncludeGenres(Person P) {
if (P.Books is EntityCollection<Book>)
(P.Books as EntityCollection<Book>).CreateSourceQuery().Include(b => b.Genres).ToList();
}
It is possible to add a method to you derived context that creates a source query for a given navigation on an entity instance. To do this you need to make use of the underlying ObjectContext which includes a relationship manager which exposes underlying entity collections/references for each navigation:
public ObjectQuery<T> CreateNavigationSourceQuery<T>(object entity, string navigationProperty)
{
var ose = this.ObjectContext.ObjectStateManager.GetObjectStateEntry(entity);
var rm = this.ObjectContext.ObjectStateManager.GetRelationshipManager(entity);
var entityType = (EntityType)ose.EntitySet.ElementType;
var navigation = entityType.NavigationProperties[navigationProperty];
var relatedEnd = rm.GetRelatedEnd(navigation.RelationshipType.FullName, navigation.ToEndMember.Name);
return ((dynamic)relatedEnd).CreateSourceQuery();
}
You could get fancy and accept a Func for the navigation property to avoid having to specify the T, but here is how the above function is used:
using (var ctx = new ProductCatalog())
{
var food = ctx.Categories.Find("FOOD");
var foodsCount = ctx.CreateNavigationSourceQuery<Product>(food, "Products").Count();
}
Hope this helps!
~Rowan
It is definately possible to do so. If you have marked you collection property with virtual keyword, then at runtime, you actual concrete type for ICollection would be EntityCollection which supports CreateSourceQuery and all the goodies that comes with the default code generator. Here is how i would do it.
public class Invoice
{
public virtual ICollection PropertyInvoices{get;set}
}
dynamic invoice = this.Invoice;
dynamic invoice = invoice.PropertyInvoices.CreateSourceQuery().Include("Property");
I wrote a blog post on something similar. Just be aware that it is not a good practice to rely on the inner implementation of ICollection getting converted to EntityCollection.
below is the blog post you might find useful
http://weblogs.asp.net/zeeshanhirani/archive/2010/03/24/registering-with-associationchanged-event-on-poco-with-change-tracking-proxy.aspx