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
}
Related
I'm struggling with using EF6 with DDD principles, namely value objects attached to aggregates. I can't seem to get migrations to generate that reflect the model and I feel like I'm fighting the tooling instead of actually being productive. Given that a NoSQL implementation is probably more appropriate, this is what I'm stuck with.
The first thing that I ran into was the lack of support for interface properties on an EF entity. The work around for that was to add concrete properties to the entity for each of the implementations, but not to the interface. When I implemented the interface, I added logic to return the right one. I had to do this in order to get any migrations to create the properties for the Policies. See Fund.LargestBalanceFirstAllocationPolicy and Fund.PercentageBasedAllocationPolicy This was annoyance one.
The current annoyance and the genesis of the question is the PercentageBasedAllocationPolicy.AllocationValues property. No matter what I do, when running add-migration, I don't get any tables or fields to represent the AllocationValues. This is basically a collection of DDD value objects hanging off of another value object, which hangs off of an aggregate.
I'm convinced that the model and code are correct to do what I want, but EF keeps getting in the way. In MongoDB, when dealing with an interface property, it actually stores the object type in a string so that it knows how to rehydrate the object. I'm considering serializing the problem areas here to a blob and storing it on the object now, which is just as evil...
public interface IFund
{
Guid Id {get;}
string ProperName {get;}
IAllocationPolicy AllocationPolicy{get;}
void ChangeAllocationPolicy(IAllocationPolicy newAllocationPolicy)
}
public class Fund : IFund
{
public Fund()
{
}
public Fund(Guid id, string nickName, string properName)
{
Id = id;
Nickname = nickName;
ProperName = properName;
// This is stupid too, but you have to instantiate these objects inorder to save or you get some EF errors. Make sure the properties on these objects are all defaulted to null.
LargestBalanceFirstAllocationPolicy = new LargestBalanceFirstAllocationPolicy();
PercentageBasedAllocationPolicy = new PercentageBasedAllocationPolicy();
}
public Guid Id { get; private set; }
public string ProperName { get; private set; }
// Do not add this to the interface. It's here for EF reasons only. Do not use internally either. Use the interface implemention of AllocationPolicy instead
public LargestBalanceFirstAllocationPolicy LargestBalanceFirstAllocationPolicy
{
get; private set;
}
// Do not add this to the interface. It's here for EF reasons only. Do not use internally either. Use the interface implemention of AllocationPolicy instead
public PercentageBasedAllocationPolicy PercentageBasedAllocationPolicy
{
get; private set;
}
public void ChangeAllocationPolicy(IAllocationPolicy newAllocationPolicy)
{
if (newAllocationPolicy == null) throw new DomainException("Allocation policy is required");
var allocationPolicy = newAllocationPolicy as PercentageBasedAllocationPolicy;
if (allocationPolicy != null) PercentageBasedAllocationPolicy = allocationPolicy;
var policy = newAllocationPolicy as LargestBalanceFirstAllocationPolicy;
if (policy != null ) LargestBalanceFirstAllocationPolicy = policy;
}
public IAllocationPolicy AllocationPolicy
{
get {
if (LargestBalanceFirstAllocationPolicy != null)
return LargestBalanceFirstAllocationPolicy;
if (PercentageBasedAllocationPolicy != null)
return PercentageBasedAllocationPolicy;
return null;
}
}
}
public interface IAllocationPolicy
{
T Accept<T>(IAllocationPolicyVisitor<T> allocationPolicyVisitor);
}
public class LargestBalanceFirstAllocationPolicy : IAllocationPolicy
{
public T Accept<T>(IAllocationPolicyVisitor<T> allocationPolicyVisitor)
{
return allocationPolicyVisitor.Visit(this);
}
}
[ComplexType]
public class PercentageBasedAllocationPolicy : IAllocationPolicy
{
public PercentageBasedAllocationPolicy()
{
AllocationValues = new List<PercentageAllocationPolicyInfo>();
}
public List<PercentageAllocationPolicyInfo> AllocationValues { get; private set; }
public T Accept<T>(IAllocationPolicyVisitor<T> allocationPolicyVisitor)
{
return allocationPolicyVisitor.Visit(this);
}
}
[ComplexType]
public class PercentageAllocationPolicyInfo
{
public Guid AssetId { get; private set; }
public decimal Percentage { get; private set; }
}
A value type (in EF marked as ComplexType) will never have any tables. The reason being is that a value types are (by definition) really just values. They don't have any Id( otherwise they would be enities) thus you can't create a table for them.
also if i review the requirements for complex type in entity framework https://msdn.microsoft.com/en-us/library/bb738472(v=vs.100).aspx i notice that you can't use inheritance on complex types. Thus if you want to use complex type in your entity framework as you've shown here then you need to make your property a PercentageBasedAllocationPolicy instead of an IAllocationPolicy.
Alternatively you could turn it into an entity with automatic generated keys.
I would like to implement nlog to each action to add an element.
So when I do myContext.Society.Add(), I would like to log something.
I create a class DbSetExtension and modify the context StockContext to use DbSetExtension<T> instead DbSet.
public class DbSetExtension<T> : DbSet<T> where T : class
{
public override T Add(T entity)
{
LoggerInit.Current().Trace("Add Done");
return base.Add(entity);
}
}
When i launch the programm, I notice when I access to myContext.Society.Add.
Society is null. So I think I miss something with my class DbSetExtension but I don't find.
public class StockContext : DbContext
{
public StockContext()
: base("StockContext")
{
}
public DbSet<HistoricalDatas> HistoricalDatas { get; set; }
public DbSet<Society> Society { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Do you have any idea,
Regards,
Alex
[UPDATE]
Code allows to add.
If I replace DbSetExtension by DbSet, the same code works.
So my assumption is I miss something when I inherit from DbSet.
public bool SetSymbols()
{
CsvTools csvThreat = new CsvTools();
List<Eoddata> currentEnum =
csvThreat.ExtractData<Eoddata>(ConfigurationManager.GetString("FilePathQuotes", ""));
currentEnum.ForEach(
c =>
{
//LoggerInit.Current().Trace("Add Done");
Sc.Society.Add(
new Society()
{
RealName = c.Description,
Symbol = String.Format("{0}.PA", c.Symbol),
IsFind = !String.IsNullOrEmpty(c.Description)
});
});
if (Sc.SaveChanges() > 0)
return true;
return false;
}
In my opinion you took totally wrong direction. DbContext is made to work with DbSet and not DbSetExtension class. It is able to instantiate objects of type DbSet and not your own type. This is basically why you get this exception. Reparing it would require probably hacking EF internals and I fear that this problem will be just a beginning for you. Instead I would recommend you to use general way of logging with EF with use of interceptor classes. Here this is explained in details at the end of article Logging and Intercepting Database Operations. Generally this approach would be much more advantageous for you. Why? Because DbContext is just man-in-the-middle in communication with db. In logs you generally cares about what happens to db and its data. Calling Add method on DbSet may not have any effect at all if SaveChanges won't be called lated on. On contrary query interceptors lets you log strictly only interaction with db. Basing on query sent to db you may distinguish what is going on.
But if you instist on your approach I would recommend you using extension methods instead of deriving from DbSet:
public static class DbSetExtensions
{
public static T LoggingAdd<T>(this DbSet<T> dbSet, T entity)
{
LoggerInit.Current().Trace("Add Done");
return dbSet.Add(entity);
}
}
and call it like this:
context.Stock.LoggingAdd(entity);
I created a generic repository class that all my other repository classes are inheriting from. This is great, because it means almost all the plumbing is done one time for all repositories. I put a full explanation of what I'm talking about here, but here is the code for my GenericRepository (some code is removed for brevity):
public abstract class GenericRepository<T> : IGenericRepository<T> where T : class, new()
{
private IMyDbContext _myDbContext;
public GenericRepository(IMyDbContext myDbContext)
{
_myDbContext = myDbContext;
}
protected IMyDbContext Context
{
get
{
return _myDbContext;
}
}
public IQueryable<T> AsQueryable()
{
IQueryable<T> query = Context.Set<T>();
return query;
}
public virtual void Create(T entity)
{
Context.Set<T>().Add(entity);
}
public virtual void Update(T entity)
{
Context.Entry(entity).State = System.Data.EntityState.Modified;
}
}
As you see, I have a Create method and an Update method. It would be very convenient to have a "CreateOrUpdate" method, so I don't have to manually check for existing objects each time I have to save something to the database.
Each of my objects in Entity Framework have an "Id", but the challenge here is that the GenericRepository works with "T".
Now, with that rather long introduction, to my specific question.
How do I create a generic CreateOrUpdate method for my GenericRepository?
UPDATE
After Marcins response, I implemented the following generic methods in my GenericRepository. It will take some time before I can test that it works as expected, but it looks very promising.
public virtual bool Exists(Guid id)
{
return Context.Set<T>().Any(t => t.Id == id);
}
public virtual void CreateOrUpdate(T entity)
{
if (Exists(entity.Id))
{
var oldEntity = GetSingle(entity.Id);
Context.Entry(oldEntity).CurrentValues.SetValues(entity);
Update(oldEntity);
}
else
{
Create(entity);
}
}
The code above has no less than 3 roundtrips to the database when updating. I'm sure it can be optimized, but it wasn't really the exercise for this question.
This question handles that topic better:
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
Create a interface with Id property, implement it on every of your entities and add another generic constraint to your class:
public interface IEntity
{
int Id { get; set;}
}
And
public abstract class GenericRepository<T> : IGenericRepository<T> where T : class, IEntity, new()
With that, you'll be able to use Id property within your generic repository class.
Of course - Id don't have to be an int, it can be Guid as well.
I have a repository like that:
public class Repository<T> : IRepository<T> where T : class
{
private readonly IRepositoryContext _repositoryContext;
public Repository(IRepositoryContext repositoryContext)
{
_repositoryContext = repositoryContext;
_objectSet = repositoryContext.GetObjectSet<T>();
}
public virtual void Update(T entity)
{
ObjectSet.AddObject(entity);
_repositoryContext.ObjectContext.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
_repositoryContext.SaveChanges();
}
}
Now that actually works for all scalar properties of the entity, but all the other entities that associated with properties of entity typeOf(T), don't care that entity state is modified, and EF simply adds new data.
So, if you do for example Repository<Student>.Update(), and you only changed the name, it will find the right Student and change his name, but it also will change the Campus, although you already have a Campus associated with that student, it will be created again with a different CampusId.
Show me please the correct way to do updates in this situation.
What I did when I wanted to follow generic approach was translated to your code something like:
public class Repository<T> : IRepository<T> where T : class
{
...
public virtual void Update(T entity)
{
if (context.ObjectStateManager.GetObjectStateEntry(entity).State == EntityState.Detached)
{
throw new InvalidOperationException(...);
}
_repositoryContext.SaveChanges();
}
}
All my code then worked like:
var attachedEntity = repository.Find(someId);
// Merge all changes into attached entity here
repository.Update(attachedEntity);
=> Doing this in generic way moves a lot of logic into your upper layer. There is no better way how to save big detached object graphs (especially when many-to-many relations are involved and deleting of relations is involved).
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.