Entity Framework Core 1.0 CurrentValues.SetValues() does not exist - entity-framework-core

I'm attempting to update an entity and its related child entities using Entity Framework Core 1.0 RC 1, where the entities are detached from DbContext. I've done this previously using a solution similar to the one described in this answer.
However, it seems that we are no longer able to do the following using Entity Framework 7:
DbContext.Entry(existingPhoneNumber).CurrentValues.SetValues();
Visual Studio complains that:
EntityEntry does not contain a definition for 'CurrentValues'
etc...
I presume this means that this has not (yet?) been implemented for EF Core 1.0? Apart from manually updating the properties, is there any other solution?

As you have noticed, this API is not implemented yet in EF Core. See this work item: https://github.com/aspnet/EntityFramework/issues/1200

I know this is an old question but I ran into this issue today, and it appears it still isn't implemented in EF Core. So I wrote an extension method to use in the meantime that will update any object's properties with the matching values of any other object.
public static class EFUpdateProperties
{
public static TOrig UpdateProperties<TOrig, TDTO>(this TOrig original, TDTO dto)
{
var origProps = typeof(TOrig).GetProperties();
var dtoProps = typeof(TDTO).GetProperties();
foreach(PropertyInfo dtoProp in dtoProps)
{
origProps
.Where(origProp => origProp.Name == dtoProp.Name)
.Single()
.SetMethod.Invoke(original, new Object[]
{
dtoProp.GetMethod.Invoke(dto, null) });
}
);
return original;
}
}
Usage:
public async Task UpdateEntity(EditViewModel editDto)
{
// Get entry from context
var entry = await _context.Items.Where(p => p.ID == editDto.Id).FirstOrDefaultAsync();
// Update properties
entry.UpdateProperties(editDto);
// Save Changes
await _context.SaveChangesAsync();
}

Related

Upgarde from EF 6 to EF Core

I am trying to upgrade my project to .net Core 2.0,
I am unable to think any solution to change the below lines.
Old Implementation:
public void ResetChangeTracking<T>(T model) where T : class, ICloneable, ICommonModel
{
// We might get called with a null model. If so, just return.
if (model == null) return;
// Calling ObjectContext.GetObjectType helps when DynamicProxies are being used.
// https://msdn.microsoft.com/en-us/data/jj592886.aspx
string key = $"{ObjectContext.GetObjectType(model.GetType())}-{model.Id}";
if (_originalValues.ContainsKey(key))
{
_originalValues.Remove(key);
}
}
How to make this work in .net Core 2.0?
Thanks

EF Core APIs removed, now how to remove automatic pluralization?

(This is not a dupe, please read my comment.)
I've just migrated from EF Core Preview 5 to Preview 6.
This seems to be a breaking change, especially the mapping will break to the existing Databases if this remains in the release version.
In preview 5 I used:
entityType.Relational.TableName = entityType.DisplayName();
Now it seems Relational property was removed. I would not fall back to manually declare the TableName for all dozens of entities, instead just instruct EF Core model builder do not pluralize automatically them.
EF Core 3 introduces, starting preview6, breaking changes on Provider-specific Metadata API. This includes removal of RelationalMetadataExtensions together with its extension methods such as Relational(this IMutableEntityType entityType).
It is replaced by RelationalEntityTypeExtensions where you can do the following:
IMutableEntityType entity = ...;
entity.SetTableName(entity.DisplayName());
With that, removing automatic pluralization can be done as described in this answer on a related question
using Microsoft.EntityFrameworkCore.Metadata;
public static class ModelBuilderExtensions
{
public static void RemovePluralizingTableNameConvention(this ModelBuilder modelBuilder)
{
foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes())
{
entity.SetTableName(entity.DisplayName());
}
}
}
Improved version of Jan Paolo Go's Answer This prevents intermediate table to become something like TeacherStudent Dictionary<string, object>
public static class ModelBuilderExtensions
{
public static void RemovePluralizingTableNameConvention(this ModelBuilder modelBuilder)
{
foreach (IMutableEntityType entity in modelBuilder.Model.GetEntityTypes())
{
if (entity is EntityType { IsImplicitlyCreatedJoinEntityType: true })
{
continue;
}
entity.SetTableName(entity.DisplayName());
}
}
}

Confusing articles and documentation about the differences (if any) between System.Data.EntityState.Add & DbSet.Add

I am working on a C# ASP.NET MVC 5 web application with EF 5. Mapping of my database tables using EF generates a DbContext class and an .edmx file. Today, I was reading a great article about creating generic DAL classes, but I stopped on the following sentence:
Note that using the Entry method to change the state of an entity will
only affect the actual entity that you pass in to the method. It won’t
cascade through a graph and set the state of all related objects,
unlike the DbSet.Add method.
That contradicts what is mentioned in these questions:
http://forums.asp.net/p/2015170/5803192.aspx
http://forums.asp.net/p/2060606/5943259.aspx
Difference between DbSet.Add(entity) and entity.State = EntityState.Added
What is the difference between IDbSet.Add and DbEntityEntry.State = EntityState.Added?
In all the above questions’ answers, all users mentioned that using System.Data.EntityState.Added is exactly the same as using DbSet.Add. But the article I mentioned first states that using System.Data.EntityState.Added will not cascade through the graph.
Based on my test, I conclude that using System.Data.EntityState.Added will cascade through the graph same as in the DBset.Add case. Is the article wrong, or is it my test and the Q&A?
Those methods are the same which you can verify by regular testing, or, if you want to be completely sure - by some exploration of EF 6 code.
DbSet.Add method (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/DbSet.cs)
public virtual TEntity Add(TEntity entity)
{
Check.NotNull<TEntity>(entity, "entity");
this.GetInternalSetWithCheck("Add").Add((object) entity);
return entity;
}
This calls InternalSet<T>.Add(object) method.
DbEntityEntry<T>.State property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Infrastructure/DbEntityEntry.cs)
public EntityState State
{
get { return _internalEntityEntry.State; }
set { _internalEntityEntry.State = value; }
}
Where _internalEntityEntry is of InternalEntityEntry type.
InternalEntityEntry.State property (http://entityframework.codeplex.com/SourceControl/latest#src/EntityFramework/Internal/EntityEntries/InternalEntityEntry.cs)
public virtual EntityState State
{
get { return IsDetached ? EntityState.Detached : _stateEntry.State; }
set
{
if (!IsDetached)
{
if (_stateEntry.State == EntityState.Modified
&& value == EntityState.Unchanged)
{
// Special case modified to unchanged to be "reject changes" even
// ChangeState will do "accept changes". This keeps the behavior consistent with
// setting modified to false at the property level (once that is supported).
CurrentValues.SetValues(OriginalValues);
}
_stateEntry.ChangeState(value);
}
else
{
switch (value)
{
case EntityState.Added:
_internalContext.Set(_entityType).InternalSet.Add(_entity);
break;
case EntityState.Unchanged:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
break;
case EntityState.Modified:
case EntityState.Deleted:
_internalContext.Set(_entityType).InternalSet.Attach(_entity);
_stateEntry = _internalContext.GetStateEntry(_entity);
Debug.Assert(_stateEntry != null, "_stateEntry should not be null after Attach.");
_stateEntry.ChangeState(value);
break;
}
}
}
}
You see that if entity is detached (your case) and state is Added - the same InternalSet<T>.Add(object) is called.
As for verification by testing:
using (var ctx = new TestDBEntities()) {
// just some entity, details does not matter
var code = new Code();
// another entity
var error = new Error();
// Code has a collection of Errors
code.Errors.Add(error);
var codeEntry = ctx.Entry(code);
// modify code entry and mark as added
codeEntry.State = EntityState.Added;
// note we did not do anything with Error
var errorEntry = ctx.Entry(error);
// but it is marked as Added too, because when marking Code as Added -
// navigation properties were also explored and attached, just like when
// you do DbSet.Add
Debug.Assert(errorEntry.State == EntityState.Added);
}
I don't know the writer of that blog. I do know the writers of the book DbContext though (albeit not in person). They know EF inside-out. So when on page 80 they write
Calling DbSet.Add and setting the State to Added both achieve exactly the same thing.
I know what I'm up to. They do exactly the same thing, which is:
If the entity is not tracked by the context, it will start being tracked by the context in
the Added state. Both DbSet.Add and setting the State to Added are graph operations—
meaning that any other entities that are not being tracked by the context and are reachable
from the root entity will also be marked as Added.
I also know by experience that it works that way. But to remove any doubt, in EF's source code, both DbSet.Add and DbEntityEntry.State (when set to Added) arrive at the same point in ObjectContext that does the actual work:
public virtual void AddObject(string entitySetName, object entity)
It's a feature that continues to delude developers that start working with EF, as is evident from the large number of questions at StackOverflow asking something along the lines of "how come my entities are duplicated?". Julie Lerman wrote an entire blog explaining why this may happen.
This continued delusion made the EF team decide to change this behavior in EF7.
Maybe the writer of the blog you refer to was one of those deluded developers.

Update object using only DbSet

I'm trying to apply the unit of work pattern as described in this blog, but have bumped into the following problem: If I inject the associated DbSet into the repo only, e.g.
public ArticleRepository(DbSet<Article> articles)
{
this.articles = articles;
}
then how do I update records or set their status to modified?
Before I used
public void Update(Article article)
{
this.context.Entry(article).State = EntityState.Modified;
}
but with the new approach I don't have access to DbContext anymore. Neither DbSet.Add nor DbSet.Attach will work here, so how can I update the object in the context?
System.Data.Entity.Migrations.IDbSetExtensions contains the IDbSet extension AddOrUpdate<TEntity>. This will update the entity.
Some people like the advantage of not knowing whether they are adding a new entity or changing an existing one.
However, if you really want an error if you are updating an item that is not added yet, take a look at the Source Code of IDbSetExtensions.AddOrUpdate
Here you can see that the function first checks if the item exists and depending on the result adds or updates it as follows:
var existing = set.SingleOrDefault
(Expression.Lambda<Func <TEntity, bool>> (matchExpression, new[]
{parameter}));
if (existing != null)
{ // entity exists: update it
foreach (var keyProperty in keyProperties)
{
keyProperty.Single().SetValue
(entity, keyProperty.Single().GetValue (existing, null), null);
}
internalSet.InternalContext.Owner.Entry(existing)
.CurrentValues.SetValues (entity);
}
else
{ // not existing entity: Add it
internalSet.Add(entity);
}
If you don't want the AddOrUpdate, but really only an update, consider Creating your own Extension method for IDbSet. See Extension Methods (C# Programming Guide)

Dynamic Data with multiple Entity Framework models

I am using Dynamic Data with Entity framework models.
If I use this with 1 EF model, then this works like a charm.
But now I need to use multiple EF models in my Dynamic Data Project and I'm receiving errors during the registration process.
Code:
public static void RegisterRoutes(RouteCollection routes)
{
var model1 = new MetaModel();
model1.RegisterContext(() =>
{
return ((IObjectContextAdapter)new Model1Entities()).ObjectContext;
}, new ContextConfiguration() { ScaffoldAllTables = true });
routes.Add(new DynamicDataRoute("model1/{table}/{action}.aspx")
{
Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
Model = model1
});
var model2 = new MetaModel();
model2.RegisterContext(() =>
{
return ((IObjectContextAdapter)new Model2Entities()).ObjectContext;
}, new ContextConfiguration() { ScaffoldAllTables = true });
routes.Add(new DynamicDataRoute("model2/{table}/{action}.aspx")
{
Constraints = new RouteValueDictionary(new { action = "List|Details|Edit|Insert" }),
Model = model2
});
}
On runtime I'm receiving an error when he's executing model2.RegisterContext .
Error:
Item has already been added. Key in dictionary:
'System.Data.Objects.ObjectContext' Key being added:
'System.Data.Objects.ObjectContext'
So for model1 he can register the context but for model2 he's blocked on this error.
If you know how to solve this, please advise!
I got it to work in 2 steps:
by removing the '.tt' files in my entity model.
in your edmx model, putting the property 'Code Generation Strategy' to 'Default' of your model. (and rebuild your solution)
After this he accepted the registration of multiple entities.
Hy Tom,
I have the same issue since more than one month. I was investigating many time into this, since it is blocking my project. I started a forum entry here:
http://forums.asp.net/t/1946475.aspx?Duplicated+key+when+try+to+register+multiple+ObjectContexts+in+Dynamic+Data
It worked in previous versions.
There was a workaround (see http://blog.davidebbo.com/2011/01/using-dynamic-data-with-ef-code-first.html) but this does not help anymore for current versions.
In short: it is an ASP.Net bug. Since we cannot call DbContext directly (which would be distinguishable) and we have to call ObjectContext, and the internal dictionary for the MetaModel uses the type without namespace as key (i.e. always "ObjectType"), there is no way around it.
Luckily, the ASP.Net team is now investigating into it now. Keep informed on the mentioned forum page.
UPDATE MARCH 2014: The current version http://blogs.msdn.com/b/webdev/archive/2014/02/28/announcing-the-release-of-dynamic-data-provider-and-entitydatasource-control-for-entity-framework-6.aspx fixes the problem.