I am trying to write a GenericEFRepository which will be used by other Repositories. I have a Save method as below.
public virtual void Save(T entity) // where T : class, IEntity, new() And IEntity enforces long Id { get; set; }
{
var entry = _dbContext.Entry(entity);
if (entry.State != EntityState.Detached)
return; // context already knows about entity, don't do anything
if (entity.Id < 1)
{
_dbSet.Add(entity);
return;
}
var attachedEntity = _dbSet.Local.SingleOrDefault(e => e.Id == entity.Id);
if (attachedEntity != null)
_dbContext.Entry(attachedEntity).State = EntityState.Detached;
entry.State = EntityState.Modified;
}
You can find the problem in comments of below code
using (var uow = ObjectFactory.GetInstance<IUnitOfWork>()) // uow is implemented like EFUnitOfWork which gives the DbContext instance to repositories in GetRepository
{
var userRepo = uow.GetRepository<IUserRepository>();
var user = userRepo.Get(1);
user.Name += " Updated";
userRepo.Save(user);
uow.Save(); // OK only the Name of User is Updated
}
using (var uow = ObjectFactory.GetInstance<IUnitOfWork>())
{
var userRepo = uow.GetRepository<IUserRepository>();
var user = new User
{
Id = 1,
Name = "Brand New Name"
};
userRepo.Save(user);
uow.Save();
// NOT OK
// All fields (Name, Surname, BirthDate etc.) in User are updated
// which causes unassigned fields to be cleared on db
}
The only solution I can think of is creating Entities via repository like userRepo.CreateEntity(id: 1) and repository will return an Entity which is attached to DbContext. But this seems error prone, still any developer may create an entity using new keyword.
What are your solution suggestions about this particular problem?
Note: I already know about cons and pros of using a GenericRepository and an IEntity interface. So, "Don't use a GenericRepository, don't use an IEntity, don't put a long Id in every Entity, don't do what you are trying to do" comments will not help.
Yes it is error prone but simply that is the problem with EF and repositories. You must either create entity and attach it before you set any data you want to update (Name in your case) or you must set modified state for each property you want to persist instead of whole entity (as you can imagine again developer can forget to do that).
The first solution leads to special method on your repository doing just this:
public T Create(long id) {
T entity = _dbContext.Set<T>().Create();
entity.Id = id;
_dbContext.Set<T>().Attach(entity);
return entity;
}
The second solution needs something like
public void Save(T entity, params Expression<Func<T, TProperty>>[] properties) {
...
_dbContext.Set<T>().Attach(entity);
if (properties.Length > 0) {
foreach (var propertyAccessor in properties) {
_dbContext.Entry(entity).Property(propertyAccessor).IsModified = true;
}
} else {
_dbContext.Entry(entity).State = EntityState.Modified;
}
}
and you will call it like:
userRepository(user, u => u.Name);
This is kind of a fundamental problem of this approach because you expect the repository to magically know which fields you changed and which ones you didn't. Using null as a signal for "unchanged" does not work in case null is a valid value.
You'd need to tell the repository which fields you want to have written, for example sending a string[] with the field names. Or one bool for each field. I do not think this is a good solution.
Maybe you can invert the control flow like this:
var entity = repo.Get(1);
entity.Name += "x";
repo.SaveChanges();
That would allow change tracking to work. It is closer to how EF wants to be used.
Alternative:
var entity = repo.Get(1);
entity.Name += "x";
repo.Save(entity);
While the other two answers provide good insight into how perhaps you can avoid this issue I think its worth pointing out a couple of things.
What you are trying to do (ie a proxy entity update) is extremely EF-centeric and IMO actually doesn't make sense outside of the EF context and hence it doesnt make sense that a generic repository would be expected to behave in this way.
You actually haven't even gotten the flow quite right for EF, if you attach an object with a few fields already set EF will conciser what you told it to be the current DB state unless you modify a value or set a modified flag. To do what you are attempting without a select you would normally attach an object without the name and then set the name after attaching the ID object
Your approach is normally used for performance reasons, I would suggest that by abstracting over the top of an existing framework you are almost always going to suffer some logical performance degradation. If this is a big deal maybe you shouldn't be using a repository? The more you add to your repository to cater to performance concerns the more complex and restrictive it becomes and the harder it gets to provide more than one implementation.
All that being said I do think you can handle this particular case in a generic situation.
This is one possible way you could do it
public void UpdateProperty(Expression<Func<T,bool>> selector, FunctionToSetAProperty setter/*not quite sure of the correct syntax off the top of my head*/)
{
// look in local graph for T and see if you have an already attached version
// if not attach it with your selector value set
// set the property of the setter
}
Hope this makes some sense, I'm not by my dev box atm so I cant really do a working sample.
I think this is a better approach for a generic repository as it allows you to implement this same behavior in multiple different ways, the abovc may work for EF but there will be different methods if you have an in memory repository (for example). This approach allows you to implement different implementations that fulfill the intent rather than restrict your repository to only act like EF.
Related
I have a large model which has been partially updated via deserialization. Since it has only been partially updated I would like to ignore any null values when I pass this to my entity framework update. Ultimately the EntityState.Modified is set but the issue I am having is that all fields are updated. This means anything that was null is now blanked in the database.
Is it possible to change this default behavior through a setting or override a method to check for null? It seems that since the context is expecting the full model I cannot simply set only a few values.
I've verified this by mapping only what I need to modify and the same behavior occurs.
You could implement something like this.
In this case I'm using a generic repository with reflection, to iterate through the properties and exclude null values in the update method.
public virtual TEntity Update(TEntity entity)
{
dbSet.Attach(entity);
dbContext.Entry(entity).State = EntityState.Modified;
var entry = dbContext.Entry(entity);
Type type = typeof(TEntity);
PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.GetValue(entity, null) == null)
{
entry.Property(property.Name).IsModified = false;
}
}
dbContext.SaveChanges();
return entity;
}
public IHttpActionResult PutProduct(int id, Product product)
{
NorthwindEntities db = new NorthwindEntities();
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
db.Products.Attach(product);
// Only the fields you want to update, will change.
db.Entry(product).Property(p => p.ProductName).IsModified = true;
db.Entry(product).Property(p => p.UnitPrice).IsModified = true;
db.Entry(product).Property(p => p.UnitsInStock).IsModified = true;
// only if if the value is not null, the field will change.
db.Entry(product).Property(p => p.UnitsOnOrder).IsModified =
product.UnitsOnOrder != null;
db.SaveChanges();
return Ok(product);
}
This is a somewhat tedious problem I've had to solve.
For lack of a more straightforward solution, eventually I decided I'd rather not want to try solving it as much downstream as when EF's SaveChanges-and-the-likes' get called (i.e., no need to "hook into" EF so late), but instead as much higher upstream / earlier on as possible --
that is, to do so right after I obtain a satisfying deserialization, which is meaningful to mutate the model, on a per-entity instance basis (in my use case, none of the updatable properties would represent relationships, but only independent attributes, in E/R parlance -- YMMV)
So, I opted for a "Populate" helper, along the lines of:
static void Populate(object from, object to)
{
var sourceType = from.GetType();
foreach (PropertyInfo target in to.GetType().GetProperties())
{
// Is the property at the target object writable and *not* marked
// as `[NotMapped]'?
var isUpdatable =
target.CanWrite &&
(target.GetCustomAttribute<NotMappedAttribute>(true) == null);
if (isUpdatable)
{
// If so, just find the corresp. property with the same name at the source object
// (caller is assumed responsible to guarantee that there is one, and of the same type, here)
var source = sourceType.GetProperty(target.Name);
var #default = sourceType.IsValueType ? Activator.CreateInstance(sourceType) : null;
var equality = (IEqualityComparer)typeof(EqualityComparer<>).MakeGenericType(sourceType).GetProperty("Default", BindingFlags.Public | BindingFlags.Static).GetValue(null);
var value = source.GetValue(from);
// Test for <property value> != default(<property type>)
// (as we don't want to lose information on the target because of a "null" (or "default(...)") coming from the source)
if (!equality.Equals(value, #default))
{
target.SetValue(to, value, null);
}
}
}
}
where "from" is the fresh entity instance that just got partially populated by whatever deserialization code, and where "to" is the actual target entity that lives in the DbContext (be it an EF proxy or not);
and where NotMappedAttribute is the EF's usual.
You'd typically have Populate to be called some time after the deserialization (&/or DTO-mapping) onto your "from" instance is done, but anyway before SaveChanges() gets call on your DbContext for all the "to" entities -- obviously, we assume there is a feasible 1-to-1 mapping "from" ... "to", that the caller of Populate knows about / could figure out.
Note I still don't know if there is a more elegant (more straightforward) way to do that, without recourse to reflection -- so, there, FWIW.
Remarks
1) above code can (or should) be made more defensive in various ways, depending on the caller assumptions;
2) one may want to cache those IEqualityComparer's (&/or the PropertyInfo's) for whatever (good) reason may arise -- in my case, I didn't have to;
3) finally, my understanding is that third-party librairies such as AutoMapper are also specially designed for that sort of task, if you can afford the additional dependency
'HTH,
I'm using Entity Framework 6, Code First approach. I'll try to present my problem with a simple piece of code:
public void ViewEntity(MyEntity Entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
Db.MyEntities.Attach(Entity); // Exception
}
}
The exception message is: Attaching an entity of type 'MyProgram.MyEntity' failed because another entity of the same type already has the same primary key value.
From what I've read on MSDN it's an expected behaviour. But what I want on that last line is to first check if there is an entity with the same key already attached to a context; if it is, use it instead, and only otherwise attach my entity to context.
But I've failed to find a way to do so. There are many utility methods on ObjectContext instance (for example GetObjectByKey). I can't test them all 'cause they all ultimately need a qualifiedEntitySetName, and I don't have any in my real imlpementation, because this method should be on an abstract class and it should work for all entity types. Calling Db.Entity(this) is no use, there is no EntityKey which would have EntitySetName.
So all of this became complex really fast. And in my terms I just want to check if the object is already in "cache" (context), use it, otherwise use my object and attach it to this context.
To be clear, I have a detached object from a TreeNode.Tag in the first place, and I just want to use it again, or if it's impossible; if there already is one in the context), use that one instead. Maybe I'm missing some crucial concepts of EF6, I'm just starting out with EF.
I've found a solution for me. As I guessed correctly ObjectContext.GetObjectByKey method does what I need, but first I needed to construct qualifiedEntitySetName, and I found a way to do so. A tad bit cumbersome (using reflection, iterating properties of MyDbContext), but does not compare to a headache of a problem I made out of all this. Just in case, here's the patch of code that is a solution for me:
public SdsAbstractObject GetAttachedToContext()
{
var ObjContext = (SdsDbContext.Current as IObjectContextAdapter).ObjectContext;
var ExistingItem = ObjContext.GetObjectByKey(GetEntityKey()) as SdsAbstractObject;
if (ExistingItem != null)
return ExistingItem;
else
{
DbSet.Attach(this);
return this;
}
}
public EntityKey GetEntityKey()
{
string DbSetName = "";
foreach (var Prop in typeof(SdsDbContext).GetProperties())
{
if (Prop.PropertyType.IsGenericType
&& Prop.PropertyType.GenericTypeArguments[0] == ObjectContext.GetObjectType(GetType()))
DbSetName = Prop.Name;
}
if (String.IsNullOrWhiteSpace(DbSetName))
return null;
else
return new EntityKey("SdsDbContext." + DbSetName, "Id", Id);
}
An Entity can be in one of five stages : Added, Unchanged, Modified, Deleted, Detached.
public void ViewEntity(MyEntity entity) // Want to read properties of my entity
{
using (var Db = new MyDbContext())
{
var DummyList = Db.MyEntities.ToList(); // Iteration on this DbSet
// Set the Modified state of entity or you can write defensive code
// to check it before set the state.
if (Db.Entry(entity).State == EntityState.Modified) {
Db.Entry(entity).State = EntityState.Modified
}
// Attached it
Db.MyEntities.Attach(Entity);
Db.SaveChanges();
}
}
Since EF doesn't know which properties are different from those in the database, it will update them all.
I had a question more detailed earlier which I had no answer, I will have the same question with a simpler way:
I have an EF database with foreign key to another table.
I would like to UPDATE an ENTITY. But I need to this like this and I'll write the codes below:
Go to database and retrieve the Member by id, return EF Member object
Do some changes on the object OUTSIDE the EF Context
Send the MODIFED EF Member into a Save method
In BL layer save method uses the context and save changes.
1)
MemberManager currentMemberManager = new MemberManager();
Member NewMember = currentMemberManager.GetById(2);
2)
NewMember.FirstName = "NewFirstName";
NewMember.LanguageId = 1;
3)
currentMemberManager.Save(NewMember);
4)
public void Save2(Member newMember)
{
using (var Context = new NoxonEntities())
{
Member existingMember = Context.Member.First(c => c.Id == newMember.Id);
existingMember.FirstName = newMember.FirstName;
existingMember.Language = Context.Language.First(c => c.Id == newMember.LanguageId);
Context.SaveChanges();//In here I get the error below
}
}
The changes to the database were committed successfully, but an error
occurred while updating the object context. The ObjectContext might be
in an inconsistent state. Inner exception message: A referential
integrity constraint violation occurred: The property values that
define the referential constraints are not consistent between
principal and dependent objects in the relationship.
Note: You may suggest to SEND a different class (Ex: public class
MyMember) that has all the necessary properties and totally separated
from EF. But this requires much work to get all EF object converting
into my separate classes. Am I right?
I am hoping there is a way to Detach the entity just long enough for me to modify it and save the values into database. (Also, I tried the Detach method which updates no rows at all)
I've been trying to solve this for hours now.
Please, help me to understand it better, I really need a solution. Thank you so much to anyone how has some ideas.
Could you do something simple like detaching the entity, then attaching it to the context when you're ready to save?
MemberManager currentMemberManager = new MemberManager();
Member NewMember = currentMemberManager.GetById(2);
The get:
public Member GetById(int id)
{
var member = YourContext.Members.FirstOrDefault(m => m.id == id);
YourContext.Detach(member);
return member;
}
The save:
public void Save2(Member newMember)
{
using (var Context = new NoxonEntities())
{
Context.Attach(newMember);
Context.ObjectStateManager.ChangeObjectState(newMember, EntityState.Modified);
Context.SaveChanges();
}
}
I want to maintain all modified records and deleted records in my project and track their status using a Status column. Below is the code from my first attempt but it seems I cannot cast OriginalValues.ToObject() to DbEntityEntry for obvious reasons. How shoudl I go about accomplishing this?
public override int SaveChanges()
{
var now = DateTime.Now;
var currentUser = HttpContext.Current.User.Identity.Name;
var trackedEntities = ChangeTracker.Entries<ITrackable>();
foreach (var trackedEntity in trackedEntities)
{
switch(trackedEntity.State)
{
case System.Data.EntityState.Added:
trackedEntity.Entity.CreatedDate = now;
trackedEntity.Entity.CreatedBy = currentUser;
trackedEntity.Entity.Status = Status.Active;
break;
case System.Data.EntityState.Modified:
var originalEntity = trackedEntity.OriginalValues.ToObject() as DbEntityEntry<ITrackable>;
originalEntity.Entity.ModifiedDate = now;
originalEntity.Entity.ModifiedBy = currentUser;
originalEntity.Entity.Status = Status.Modified;
var newEntity = trackedEntity.CurrentValues.ToObject() as DbEntityEntry<ITrackable>;
newEntity.State = System.Data.EntityState.Added;
newEntity.Entity.Status = Status.Active;
break;
case System.Data.EntityState.Deleted:
trackedEntity.State = System.Data.EntityState.Modified;
trackedEntity.Entity.Status = Status.Deleted;
break;
}
}
return base.SaveChanges();
}
I am not sure if I understand correctly what you are trying to achieve in the Modified case. But my understanding is that ToObject() creates a completely new object and fills it with the property values from the OriginalValues dictionary. So, originalEntity is not attached to the context and will just be garbage collected without any effect when the variable goes out of scope.
Therefore, don't you need to save the originalEntity as a new added entity to the database to save the last version before the modification is saved?
I would try this:
case System.Data.EntityState.Modified:
var originalEntity = trackedEntity.OriginalValues.ToObject() as ITrackable;
originalEntity.ModifiedDate = now;
originalEntity.ModifiedBy = currentUser;
originalEntity.Status = Status.Modified;
Entry(originalEntity).Status = System.Data.EntityState.Added;
trackedEntity.Entity.Status = Status.Active;
break;
trackedEntity will be saved anyway because it is in state Modified and originalEntity would be created and saved as a new object which represents the state before modification.
I expect that the cast to ITrackable should work because ToObject() creates an object of the type which is represented by the OriginalValues dictionary, and that must be an ITrackable because you filter the ChangeTracker.Entries enumeration by this interface.
The above is untested, I am unsure if it will work.
Edit
The naming above is a bit confusing because originalEntity is an entity in your model while trackedEntity is a DbEntityEntry<T>. Maybe rename that either to originalEntityObject (ugly) or better trackedEntityEntry.
Just to mention: It's absolutely necessary (unless you are using change tracking proxies consequently) that you call ChangeTracker.DetectChanges() before you enter the foreach loop because otherwise the entries are possibly not in the correct final state and you enter wrong case sections or no section at all (when the state is Unchanged but still has changed properties). POCO changes are detected by comparison with the snapshot in OriginalValues and this last comparison is done in base.SaveChanges which is after your loop and too late. (See also "Edit 2" in the answer here: https://stackoverflow.com/a/6157071/270591.)
I was looking for how you should override SaveChanges() to implement a similar goal
it would be easier to cast like this;
((ITrackable)trackedEntity.Entity).CreatedDate = now;
Though not identical to the methodology you're using, I use the repository pattern for this. The rest of the app just instantiates the repository class, and calls Save(objToSave) or Delete(objToDelete) or similar. Now the execution process is disconnected from the actual data store, and I can take appropriate action: update the ModifyDate, change the Status, etc. If you're exposing the DataContext to the rest of the application, it's very hard to control the details under the hood.
Model #1 - This model sits in a database on our Dev Server.
Model #1 http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/bdb2b000-6e60-4af0-a7a1-2bb6b05d8bc1/Model1.png
Model #2 - This model sits in a database on our Prod Server and is updated each day by automatic feeds. alt text http://content.screencast.com/users/Keith.Barrows/folders/Jing/media/4260259f-bce6-43d5-9d2a-017bd9a980d4/Model2.png
I have written what should be some simple code to sync my feed (Model #2) into my working DB (Model #1). Please note this is prototype code and the models may not be as pretty as they should. Also, the entry into Model #1 for the feed link data (mainly ClientID) is a manual process at this point which is why I am writing this simple sync method.
private void SyncFeeds()
{
var sourceList = from a in _dbFeed.Auto where a.Active == true select a;
foreach (RivWorks.Model.NegotiationAutos.Auto source in sourceList)
{
var targetList = from a in _dbRiv.Product where a.alternateProductID == source.AutoID select a;
if (targetList.Count() > 0)
{
// UPDATE...
try
{
var product = targetList.First();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
else
{
// INSERT...
try
{
long clientID = source.Client.ClientID;
var companyDetail = (from a in _dbRiv.AutoNegotiationDetails where a.ClientID == clientID select a).First();
var company = companyDetail.Company;
switch (companyDetail.FeedSourceTable.ToUpper())
{
case "AUTO":
var product = new RivWorks.Model.Negotiation.Product();
product.alternateProductID = source.AutoID;
product.isFromFeed = true;
product.isDeleted = false;
product.SKU = source.StockNumber;
company.Product.Add(product);
break;
}
_dbRiv.SaveChanges();
}
catch (Exception ex)
{
string m = ex.Message;
}
}
}
}
Now for the questions:
In Model #2, the class structure for Auto is missing ClientID (see red circled area). Now, everything I have learned, EF creates a child class of Client and I should be able to find the ClientID in the child class. Yet, when I run my code, source.Client is a NULL object. Am I expecting something that EF does not do? Is there a way to populate the child class correctly?
Why does EF hide the child entity ID (ClientID in this case) in the parent table? Is there any way to expose it?
What else sticks out like the proverbial sore thumb?
TIA
1) The reason you are seeing a null for source.Client is because related objects are not loaded until you request them, or they are otherwise loaded into the object context. The following will load them explicitly:
if (!source.ClientReference.IsLoaded)
{
source.ClientReference.Load();
}
However, this is sub-optimal when you have a list of more than one record, as it sends one database query per Load() call. A better alternative is to the Include() method in your initial query, to instruct the ORM to load the related entities you are interested in, so:
var sourceList = from a in _dbFeed.Auto .Include("Client") where a.Active == true select a;
An alternative third method is to use something call relationship fix-up, where if, in your example for instance, the related clients had been queried previously, they would still be in your object context. For example:
var clients = (from a in _dbFeed.Client select a).ToList();
The EF will then 'fix-up' the relationships so source.Client would not be null. Obviously this is only something you would do if you required a list of all clients for synching, so is not relevant for your specific example.
Always remember that objects are never loaded into the EF unless you request them!
2) The first version of the EF deliberately does not map foreign key fields to observable fields or properties. This is a good rundown on the matter. In EF4.0, I understand foreign keys will be exposed due to popular demand.
3) One issue you may run into is the number of database queries requesting Products or AutoNegotiationContacts may generate. As an alternative, consider loading them in bulk or with a join on your initial query.
It's also seen as good practice to use an object context for one 'operation', then dispose of it, rather than persisting them across requests. There is very little overhead in initialising one, so one object context per SychFeeds() is more appropriate. ObjectContext implements IDisposable, so you can instantiate it in a using block and wrap the method's contents in that, to ensure everything is cleaned up correctly once your changes are submitted.