I am using Julie Lerman's EF Repository techniques.
All my entities implement the following interface
public interface IEntity
{
EntityState State { get; set; }
}
All my repositories call the following GetList function
public virtual IList<T> GetList(Func<T, bool> where, params Expression<Func<T, object>>[] navigationProperties)
{
List<T> list;
IQueryable<T> dbQuery = ((DbContext)context).Set<T>();
//Apply eager loading
foreach (var navigationProperty in navigationProperties)
{
dbQuery = dbQuery.Include(navigationProperty);
}
list = dbQuery.AsNoTracking().Where(where).ToList();
return list;
}
I am finding that the initial state property for my entities is zero, but I want to set it to
I want to set State property to be EntityState.Unchanged
How should I do this?
Julie Lerman described it in her book Programming Entity Framework: DbContext At page 93
(Example 4-15).
You can use following code in your DbContext constractor to set the object states to UnChanged:
public YourContext()
{
((IObjectContextAdapter)this).ObjectContext .ObjectMaterialized +=
(sender, args) =>
{
var entity = args.Entity as IEntity;
if (entity != null)
{
entity.State = State.Unchanged;
}
}
}
Here's another easier approach to this issue. I am using it and it works!!
public abstract class Entity<TId> : BaseEntity, IEntity<TId>, IModelState
{
public virtual TId Id { get; private set; }
public byte[] RowVersion { get; protected set; }
private readonly IDictionary<Type, IEvent> events = new Dictionary<Type, IEvent>();
public IEnumerable<IEvent> Events => events.Values;
public ModelState ModelState { get; protected set; } = ModelState.Unchanged;
protected Entity()
{
ModelState = ModelState.Added;
}
... removed for brevity
make sure you're using C# 7 and Roslyn Compiler with your .NET 4.6.x
I think this is safer because only your own Entity Object will have the power to set it to unchanged when it gets initialized by EF. In my opinion DbContext should not have the right to set the "state" property of any entity.
Related
I want to implement business logic in DbSet derived classes. I like the idea of not having services and DAL abstractions and think this could be a good way. For this to work I need to inject objects into my DbSet but I don't know how. Here some sample code which does not work, because the EF Framework can't create an object of the DbSet. Maybe someone can point me in the right direction?
public class LongTermBookingDbSet : DbSet<LongTermBooking>
{
DbContext _dbContext { get; set; }
public LongTermBookingDbSet(DbContext dbContext )
{
this._dbContext = _bContext ;
}
public override LongTermBooking Add(LongTermBooking entity)
{
return this.Add(entity, false);
}
public LongTermBooking Add(LongTermBooking entity, bool SendMails)
{
var dbSet = base.Add(entity);
//do something with the _dbContext
return dbSet;
}
}
One of the options is to aggregate real DbSet, not derive it:
public class PersonSet : IDbSet<Person>
{
private readonly DbSet<Person> _dbSet;
public PersonSet(DbSet<Person> dbSet)
{
_dbSet = dbSet;
}
}
public class MyDbContext: DbContext
{
public PersonSet PersonSet {...}
}
Inherits from DbSet<T> with the purposes to add property
Is there a way to inherits from DbSet? I want to add some new properties, like this:
public class PersonSet : DbSet<Person>
{
public int MyProperty { get; set; }
}
But I don't know how to instantiate it in my DbContext
public partial MyContext : DbContext
{
private PersonSet _personSet;
public PersonSet PersonSet
{
get
{
_personSet = Set<Person>(); // Cast Error here
_personSet.MyProperty = 10;
return _personSet;
}
}
}
How can I achieve this?
I have found an answer that works for me. I declare my DbSet properties as my derived interface in my context, e.g.:
IDerivedDbSet<Customer> Customers { get; set; }
IDerivedDbSet<CustomerOrder> CustomerOrders { get; set; }
My implementation includes a private IDbSet which which is assigned in the constructor e.g.:
public class DerivedDbSet<T> : IDerivedDbSet<T> where T : class
{
private readonly IDbSet<T> _dbSet;
public DerivedDbSet(IDbSet<T> dbSet)
{
this._dbSet = dbSet;
}
...
}
My implementation of a derived DbContext interface hides the Set<>() method like so:
new public IDerivedSet<TEntity> Set<TEntity>() where TEntity : class
{
//Instantiate _dbSets if required
if (this._dbSets == null)
{
this._dbSets = new Dictionary<Type, object>();
}
//If already resolved, return stored reference
if (this._dbSets.ContainsKey(typeof (TEntity)))
{
return (IDerivedSet<TEntity>) this._dbSets[typeof (TEntity)];
}
//Otherwise resolve, store reference and return
var resolvedSet = new GlqcSet<TEntity>(base.Set<TEntity>());
this._dbSets.Add(typeof(TEntity), resolvedSet);
return resolvedSet;
}
The derived DbContext returns a newly constructed IDerivedSet or picks it's reference cached in a Dictionary. In the derived DbContext I call a method from the constructor which uses type reflection to go through the DbContexts properties and assigns a value/reference using it's own Set method. See here:
private void AssignDerivedSets()
{
var properties = this.GetType().GetProperties();
var iDerivedSets =
properties.Where(p =>
p.PropertyType.IsInterface &&
p.PropertyType.IsGenericType &&
p.PropertyType.Name.StartsWith("IDerivedSet") &&
p.PropertyType.GetGenericArguments().Count() == 1).ToList();
foreach (var iDerivedSet in iDerivedSets)
{
var entityType = iDerivedSet.PropertyType.GetGenericArguments().FirstOrDefault();
if (entityType != null)
{
var genericSet = this.GetType().GetMethods().FirstOrDefault(m =>
m.IsGenericMethod &&
m.Name.StartsWith("Set") &&
m.GetGenericArguments().Count() == 1);
if (genericSet != null)
{
var setMethod = genericSet.MakeGenericMethod(entityType);
iDerivedSet.SetValue(this, setMethod.Invoke(this, null));
}
}
}
}
Works a treat for me. My context class has navigable set properties of my set type that implements a derived interface inheriting IDbSet. This means I can include query methods on my set type, so that queries are unit testable, instead of using the static extensions from the Queryable class. (The Queryable methods are invoked directly by my own methods).
One solution is to create a class that implements IDbSet and delegates all operations to a real DbSet instance, so you can store state.
public class PersonSet : IDbSet<Person>
{
private readonly DbSet<Person> _dbSet;
public PersonSet(DbSet<Person> dbSet)
{
_dbSet = dbSet;
}
public int MyProperty { get; set; }
#region implementation of IDbSet<Person>
public Person Add(Person entity)
{
return _dbSet.Add(entity);
}
public Person Remove(Person entity)
{
return _dbSet.Remove(entity);
}
/* etc */
#endregion
}
Then in your DbContext, put a getter for your Custom DbSet:
public class MyDbContext: DbContext
{
public DbSet<Person> People { get; set; }
private PersonSet _personSet;
public PersonSet PersonSet
{
get
{
if (_personSet == null)
_personSet = new PersonSet( Set<Person>() );
_personSet.MyProperty = 10;
return _personSet;
}
set
{
_personSet = value;
}
}
}
I solved this using another variable to instantiate the "regular" DbSet.
private DbSet<Person> _persons { get; set; }
public PersonDbSet<Person> Persons { get { return new PersonDbSet(_persons); } }
This way entityframework recognizes the Entity but I can still use my own DbSet class.
I know this is really old and the OP has probably moved on but I was just wondering the same thing myself. EF populates the DbSets inside your MyContext at run time.
I just created MyDbSet<T> that inherits from DbSet<T> and the replaced all references to DbSet<T> with my derived class in MyContext. Running my program failed to instantiate any of the properties.
Next I tried setting the properties to IDbSet<T> since DbSet<T> implements this interface. This DOES work.
Investigating further, the constructors for DbSet are protected and internal (the protected one calls the internal one anyway). So MS have made it pretty hard to roll your own version. You may be able to access the internal constructors through reflection but chances are that EF will not construct your derived class anyway.
I would suggest writing an extension method to plug the functionality into the DbSet object, however you're stuck if you want to store state.
How can I detect changes of ICollection<> properties (many-to-many relationships)?
public class Company
{
...
public virtual ICollection<Employee> Employees { get; set; }
}
using (DataContext context = new DataContext(Properties.Settings.Default.ConnectionString))
{
Company company = context.Companies.First();
company.Employees.Add(context.Employees.First());
context.SaveChanges();
}
public class DataContext : DbContext
{
public override int SaveChanges()
{
return base.SaveChanges();
// Company's entity state is "Unchanged" in this.ChangeTracker
}
}
Here is how to find all the changed many-to-many relationships. I've implemented the code as extension methods:
public static class IaExtensions
{
public static IEnumerable<Tuple<object, object>> GetAddedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]);
}
public static IEnumerable<Tuple<object, object>> GetDeletedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]);
}
private static IEnumerable<Tuple<object, object>> GetRelationships(
this DbContext context,
EntityState relationshipState,
Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext
.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => Tuple.Create(
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
}
Some explanation. Many-to-many relationships are represented in EF as Independent Associations, or IAs. This is because the foreign keys for the relationship are not exposed anywhere in the object model. In the database the FKs are in a join table, and this join table is hidden from the object model.
IAs are tracked in EF using "relationship entries". These are similar to the DbEntityEntry objects you get from the DbContext.Entry except that they represent a relationship between two entities rather than an entity itself. Relationship entries are not exposed in the DbContext API, so you need to drop down to ObjectContext to access them.
A new relationship entry is created when a new relationship between two entities is created, for example by adding an Employee to the Company.Employees collection. This relationship is in the Added state.
Likewise, when a relationship between two entities is removed, then the relationship entry is put into the Deleted state.
This means that to find changed many-to-many relationships (or actually any changed IA) we need to find added and deleted relationship entries. This is what the GetAddedRelationships and GetDeletedRelationships do.
Once we have relationship entries, we need to make sense of them. For this you need to know a piece of insider knowledge. The CurrentValues property of an Added (or Unchanged) relationship entry contains two values which are the EntityKey objects of the entities at either end of the relationship. Likewise, but annoyingly slightly different, the OriginalValues property of a Deleted relationship entry contains the EntityKey objects for the entities at either end of the deleted relationship.
(And, yes, this is horrible. Please don’t blame me—it is from well before my time.)
The CurrentValues/OriginalValues difference is why we pass a delegate into the GetRelationships private method.
Once we have the EntityKey objects we can use GetObjectByKey to get the actual entity instances. We return these as tuples and there you have it.
Here’s some entities, a context, and an initializer, I used to test this. (Note—testing was not extensive.)
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
public override string ToString()
{
return "Company " + Name;
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public override string ToString()
{
return "Employee " + Name;
}
}
public class DataContext : DbContext
{
static DataContext()
{
Database.SetInitializer(new DataContextInitializer());
}
public DbSet<Company> Companies { get; set; }
public DbSet<Employee> Employees { get; set; }
public override int SaveChanges()
{
foreach (var relationship in this.GetAddedRelationships())
{
Console.WriteLine(
"Relationship added between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
foreach (var relationship in this.GetDeletedRelationships())
{
Console.WriteLine(
"Relationship removed between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
return base.SaveChanges();
}
}
public class DataContextInitializer : DropCreateDatabaseAlways<DataContext>
{
protected override void Seed(DataContext context)
{
var newMonics = new Company { Name = "NewMonics", Employees = new List<Employee>() };
var microsoft = new Company { Name = "Microsoft", Employees = new List<Employee>() };
var jim = new Employee { Name = "Jim" };
var arthur = new Employee { Name = "Arthur" };
var rowan = new Employee { Name = "Rowan" };
newMonics.Employees.Add(jim);
newMonics.Employees.Add(arthur);
microsoft.Employees.Add(arthur);
microsoft.Employees.Add(rowan);
context.Companies.Add(newMonics);
context.Companies.Add(microsoft);
}
}
Here’s an example of using it:
using (var context = new DataContext())
{
var microsoft = context.Companies.Single(c => c.Name == "Microsoft");
microsoft.Employees.Add(context.Employees.Single(e => e.Name == "Jim"));
var newMonics = context.Companies.Single(c => c.Name == "NewMonics");
newMonics.Employees.Remove(context.Employees.Single(e => e.Name == "Arthur"));
context.SaveChanges();
}
I cant give you the exact code for your situation, but I can tell you your situation will be simplified ten fold by having a joiner table inbetween Employees and Company just to break up the many to many relationship.
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.
Can EntityFramework support an EAV model? Is this a workable scenario, or a nightmare? I want to use an EAV model for a system, and I'd like to embrace EF if possible, but I'm concerned that these two philosophies are in conflict.
It depends how do you expect to use EAV in the application. EF can be used to map this:
public partial class Entity
{
// Key
public virtual int Id { get; set; }
// Other common properties
// Attributes
public virtual ICollection<EavAttriubte> Attributes { get; set; }
}
// The simplest implementation
public class EavAttribute
{
// Key
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string Value { get; set; }
}
This is what can be persisted and what can be queried by Linq-to-entities. Now you can make your entity usable by defining helper properties (can be used only in your application but not by persistance or querying). These helper properties can be used only for well known attributes which will always exists for entity type - optional attributes must be still accessed in collection:
public partial class Entity
{
// Just example without error handling
public decimal Price
{
get
{
return Int32.Parse(Attributes.Single(a => a.Name == "Price"));
}
set
{
Attributes.Single(a => a.Name == "Price").Value = value.ToString();
}
}
}
This is not very nice because of conversions and collection searching. If you access data multiple times they will be executed multiple times.
I didn't tried it but I think this can be avoided by implementing a similar interface by each entity:
public interface IEavEntity
{
// loads attribute values from Attributes collection to local fields
// => conversion will be done only once
void Initialize();
// saves local values back to Attributes collection
void Finalize();
}
Now you will handle ObjectMaterialized and SavingChanges events on ObjectContext. In the first handler you will execute Initialize if materialized object implements IEavEntity in the second handler you will iterate ObjectStateManager to get all updated or inserted entities implementing IEavEntity and you will execute Finalize. Something like:
public void OnMaterialized(object sender, ObjectMaterializedEventArgs e)
{
var entity = e.Entity as IEavEntity;
if (entity != null)
{
entity.Initialize();
}
}
public void SavingChanges(object sender, EventArgs e)
{
var context = sender as ObjectContext;
if (context != null)
{
foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
var entity = entry.Entity as IEavEntity;
if (entity != null)
{
entity.Finalize();
}
}
}
}
}