Why does the entity framework need an ICollection for lazy loading? - entity-framework

I want to write a rich domain class such as
public class Product
{
public IEnumerable<Photo> Photos {get; private set;}
public void AddPhoto(){...}
public void RemovePhoto(){...}
}
But the entity framework (V4 code first approach) requires an ICollection type for lazy loading! The above code no longer works as designed since clients can bypass the AddPhoto / RemovePhoto method and directly call the add method on ICollection. This is not good.
public class Product
{
public ICollection<Photo> Photos {get; private set;} //Bad
public void AddPhoto(){...}
public void RemovePhoto(){...}
}
It's getting really frustrating trying to implement DDD with the EF4. Why did they choose the ICollection for lazy loading?
How can i overcome this? Does NHibernate offer me a better DDD experience?

I think i found the solution...See here for more details: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/47296641-0426-49c2-b048-bf890c6d6af2/
Essentially you want to make the ICollection type protected and use this as the backing collection for the public IEnumerable
public class Product
{
// This is a mapped property
protected virtual ICollection<Photo> _photos { get; set; }
// This is an un-mapped property that just wraps _photos
public IEnumerable<Photo> Photos
{
get { return _photos; }
}
public void AddPhoto(){...}
public void RemovePhoto(){...}
}
For lazy loading to work the type must implement ICollection and the access must be public or protected.

You can't insert into an IEnumerable. This applies to the EF just as much as it does to your clients. You don't have to use ICollection, though; you can use IList or other writeable types. My advice to get the best of both worlds is to expose DTOs rather than entities to your clients.

You can overcome this by using the ReadOnlyCollection(Of T)
public class Product
{
private IList<Photo> _photos;
public IList<Photo> Photos {
get
{
return _photos.AsReadOnly();
}
private set { _photos = value; }
}
public void AddPhoto(){...}
public void RemovePhoto(){...}
}
EDIT:
ICollection<T> => IList<T>
Hope that is what you were looking for.

Related

Entity Framework Set with Generic Class

Ok, I might be punching above my pay grade here, but I'm trying to create a generic CRUD routine for and EF project. I've got most of it working but I'm flailing around on one point.
Normally you do something like this to add an entity through a context-
DBContext.MyClass.Add( p ); // p = instance of MyClass
That works fine, but since in a global method to handle all adds regardless of what class they are I'm passing in a Model as an object it would look more like this-
DBContext<whateverobject>.Add(whateverobject); // my objects is an object param passed into the method
I've tried doing a bunch of typeofs and there where T : class stuff but I'm having no luck. Any pointing in the right direction would help me out.
I'm using EF Core 2 so my options might also be more limited than EF 6.
Thanks.
The method you're looking for is DbContext's Set<T>()
Your generic repository for your generic CRUD would look something like this:
public class Repo<T> where T: class
{
private readonly DbSet<T> _set;
public Repo(DbContext dbContext)
{
_set = dbContext.Set<T>();
}
public void Add(T entity) => _set.Add(entity);
}
This example includes a maybe unusual thing:
where T: class: we have to specify that T has to be a reference type because DbSet<T> expects T to be a reference type
For generic querying you might want to use extension methods.
In order to implement a ById method you'd have to specify that the type T must have an Id property using an interface. That would look something like this:
public interface IEntity
{
int Id { get; set; }
}
public class User : IEntity
{
public int Id { get; set; }
}
public static class DbSetExtensions
{
public static T ById<T>(this DbSet<T> dbSet, int id) where T: class =>
dbSet.FirstOrDefault(entity => entity.Id == id);
}

EntityFramework how to not map a class but do map it's inherited properties

We use EntityFramework 6.1 with CodeFirst in our web mvc application (StdWebApp). Now we want to make a new custom version of this application (CustomWebApp) .
The CustomWebApp will use most of the code of the standard one, in it's domain model it will extend the Person class.
In CustomDomain we make implement a new DbContext that must connect with the database of the custom app (CustomSqlDb).
In (C#) code there is no problem that there is a Person in Domain and in CustomDomain. However we have not been able to devise a mapping for Person in the Custom DbContext that will:
Create a single "Person" table.
Contains fields form "CustomDomain.Person" AND those from "Domain.Person".
We tried some variants like this:
modelBuilder.Entity<Person>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Person");
}
);
using this document as our inspiration msdn mapping types
But EF complains about the simple name beeing equal.
Obviously we could rename the "Person" in "CustomDomain" to "PersonCustom" but that could lead to a lot of silly names if we have to do this again in the future like "PersonCustomExtraSpecial" etc.
Thoughts anyone?
UPDATE
we tried the solution suggested by mr100, here is the complete code:
namespace Domain
{
public class Person
{
public int Id { get; set; }
public string Stuff { get; set; }
}
}
namespace CustomDomain
{
public class Person : Domain.Person
{
public string ExtraStuff { get; set; }
}
}
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
This still result in the error
The type 'CustomDomain.Person' and the type 'Domain.Person' both have the same simple name of 'Person' and so cannot be used in the same model. All types in a given model must have unique simple names. Use 'NotMappedAttribute' or call Ignore in the Code First fluent API to explicitly exclude a property or type from the model.
So we added the following code:
namespace CustomDomain
{
public class DbModel : DbContext
{
DbSet<CustomDomain.Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Ignore<Domain.Person>();
modelBuilder.Entity<CustomDomain.Person>().Map(m => m.ToTable("Person"));
}
}
}
Still same result.
To achieve this your DbContext class in CustomWebApps should have property People defined like this:
public DbSet<CustomDomain.Person> People {get; set;}
and no property:
public DbSet<Domain.Person> People {get; set;}
even if it comes from StdWebApp DbContext class from which CustomWebApp DbContext class may derive (if that is the case for you). Additionally you may set properly table name:
modelBuilder.Entity<Person>().ToTable("Person");

GenericRepository TEntity change attribute value

I am using EF 5.0 and the model first approach. I have build a GenericRepository that has the basic get, insert, delete etc statements. Like:
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
My EF entities all have the attributes Modified and ModifiedBy. Now I want to change this values everytime I save an entity.
Is it possible to modify this two attributes (set the value) without writing an specific implementation all the time?
Thank you
I see two options for you to do this, but they both entail either introducing a base type or an interface for all of your entities to cover them in a generic function. I would prefer an interface, although each entity would have to implement it again and again.
Let's say you create
interface IAuditable
{
DateTime Modified { get; set; }
string ModifiedBy {get; set; } // User id?
}
Now you can do:
public virtual void Insert(TEntity entity)
where TEntity : IAuditable
{
entity.Modified = DateTime.Now;
entity.ModifiedBy = ???? // Whatever you get the name from
...
}
(Same for edit)
You can also subscribe to the context's SavingChanges event:
// In the constructor:
context.SavingChanges += this.context_SavingChanges;
private void context_SavingChanges(object sender, EventArgs e)
{
foreach (var auditable in context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified)
.Select(entry => entry.Entity)
.OfType<IAuditable>)
{
auditable.Modified = DateTime.Now;
auditable.ModifiedBy = ????;
}
}
If you work with DbContext you can get to the event by
((IObjectContextAdapter)this).ObjectContext.SavingChanges
I'd like to add that more reliable time tracking can (and maybe should) be achieved by database triggers. Now you depend on a client's clock.
You can do this using the following code in your all methods of repository where you want to.
public virtual void Edit(TEntity entity)
{
entity.Modified=DateTime.Now;
entity.ModifiedBy=User.Identity.Name;
//Other saving to repository code
}

On Insert / Update logic in EF code first

I would like to add some logic to the insert and update events of some EF objects.
I have a MVC application with category object which has a property which is a slugified version of the name property.
public class Category
{
public string Name { get; set; }
public string UrlName{ get; set; }
}
I would like to set the UrlName property only on the insert and update events because my slugify logic is quite elaborate.
I am aware that I can add some logic inside the SaveChanges() function on the context itself but I rather would like to put the code closer to the entity itself.
Is there a way to accomplish such thing using EF code first?
You can setup a base class with methods to be called before insert and update
public abstract class Entity
{
public virtual void OnBeforeInsert(){}
public virtual void OnBeforeUpdate(){}
}
public class Category : Entity
{
public string Name { get; set; }
public string UrlName{ get; set; }
public override void OnBeforeInsert()
{
//ur logic
}
}
Then in your DbContext
public override int SaveChanges()
{
var changedEntities = ChangeTracker.Entries();
foreach (var changedEntity in changedEntities)
{
if (changedEntity.Entity is Entity)
{
var entity = (Entity)changedEntity.Entity;
switch (changedEntity.State)
{
case EntityState.Added:
entity.OnBeforeInsert();
break;
case EntityState.Modified:
entity.OnBeforeUpdate();
break;
}
}
}
return base.SaveChanges();
}
No there is no such extension point because your entity is POCO - it is not aware of its persistence. Such logic must be triggered in data access layer which is aware of persistence. DbContext API offers only overriding of SaveChanges.
You can expose custom events or methods on your entities and call them during processing in SaveChanges.

ADO EF Code First Generic Intermediate Class Inheritance mapping

I've got the following requirement that works well in the OO space but I can't seem to get it to map back to the DB using ADO EF code first.
I have numrous products each will have different aspects (attributes but not in the sense of code attributes). For instance ring would have aspects such as mineral type = gold etc whilst a diamond would have an aspec of clarity = VVSI1.
As you can see the products very greatly in thier composition and I want a dynamic way of growing my system.
As such I've created a product class:
public class Product
{
public int id { get; set; }
public string Name { get; set; }
private List<ProductAspect> aspects = new List<ProductAspect>();
public List<ProductAspect> Aspects { get { return aspects; } set { aspects = value; } }
}
It has a list of ProductAspect which is the base class for all aspects moving forward:
public class ProductAspect
{
public int id { get; set; }
public string AspectName { get; set; }
}
I then inherit from the ProductAspect using a generic which alows me to be specific (strongly typed) about my Aspect Value:
public abstract class ProductAspect<T> : ProductAspect
{
public T AspectValue { get; set; }
}
I then create some Aspects that will allow me to decorate my product:
public class StringAspect : ProductAspect<string> { };
public class DecimalAspect : ProductAspect<decimal> { };
public class ImageAspect : ProductAspect<byte[]> { };
I then give the DbContext a try and have tried both TPH and TPC inheritance mappings.
Neither seem to work. The DB model that get's generated doesn't create a foriegn key to the StringAspect or DecimalAspect tables from the Aspect Table.
public class IxamDataContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<ProductAspect> Aspects { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
AspectMapping(modelBuilder);
}
private void AspectMapping(DbModelBuilder mb)
{
//TPH
//mb.Entity<ProductAspect>()
// .Map<StringAspect>(m => m.Requires("type").HasValue("sa"))
// .Map<DecimalAspect>(m => m.Requires("type").HasValue("da"));
//TPC
//mb.Entity<StringAspect>().ToTable("StringAspect");
//mb.Entity<DecimalAspect>().ToTable("DecimalAspect");
}
}
Resulting in the following exception for this Seeding code:
Product p = new Product();
p.Name = "Diamond";
p.Aspects.Add(new StringAspect() { AspectName = "History", AspectValue = "Old and long" });
p.Aspects.Add(new DecimalAspect() { AspectName = "Weight", AspectValue= 96.5M });
context.Products.Add(p);
context.SaveChanges();
Excpetion:
EntityType 'StringAspect' does not
exist in the EntitySet
'IxamDataContext.Aspects'. Parameter
name: entity
Any ideas from the EF code first pros out there?
Entity framework doesn't support intermediate non mapped types in inheritance hierarchy. It means that you can't have this inheritance: A (mapped) -> B (not mapped) -> C (mapped). EF also doesn't support mapping generic types. It means that you must remove your generic intermediate class from the hierarchy and move AspectValue to derived types with correct type.
Maybe it's to late, but I would offer you using ComplexType attribute it will allows you to extend your types as you wish.