Reputation: 1772
I want to modify entity Payment object, containing not-modified, existing Currency object and save it to EF database:
public Payment()
{
int Id {get;set;}
public int Value {get;set;}
public Currency SelectedCurrency{get;set;}
}
public Currency()
{
int Id {get;set;}
string Name;
}
My Edit method looks like this:
public override void Edit(MwbePaymentMethod payment)
{
if (payment.Currency != null && payment.Currency.Id != 0 && Context.Entry(payment.UserData).State != EntityState.Unchanged)
{
Context.Entry(payment.Currency).State = EntityState.Unchanged;
}
Context.Entry(payment).State = EntityState.Modified;
}
A few words about Edit method: It changes contained sub-entity Currency to Unchanged, because it;s not going to be updated. But when line
Context.Entry(payment).State = EntityState.Modified;
is called, error is displayed:
Attaching an entity of type 'Payment' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
I also tried to use DbSet.Attach(payment)
method, but it gives error too.
ADDED1: This is my external method which calls Edit method. It calls Context.Save and reads Payment entity by Id.
public void UpdateMwbePaymentMethod(MwbePaymentFilter filter, MwbePaymentDtoInOut mwbepayment)
{
var currentPayment = paymentMethodRepository.FindBy(x => x.UserData.Id == filter.userId && x.Id == filter.id);
if (currentPayment==null || currentPayment.Count() != 1)
{
throw new DBConcurrencyException();
}
var mwbePayment = Mapper.Map<MwbePayment>(mwbepayment);
mwbePayment.UserData = userRepository.Get(filter.userId).Data;
paymentRepository.Edit(mwbePayment);
paymentRepository.SaveChanges();
}
ADDED2: I added AsNoTracking to both Find queries. I was resported as remedy. But now other entity which is marked as Detached, when method DbSet.Attach(payment) is called, it gives error
Attaching an entity of type 'MobileWallet.Common.Repository.MwbeAddress' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate.
ADDED 3: Added method FindBy:
public IEnumerable<TEntity> FindBy(Expression<Func<TEntity, bool>> predicate, params Expression<Func<TEntity, object>>[] includes)
{
IQueryable<TEntity> query = DbSet.AsNoTracking().Where(predicate);
if (null != includes)
{
foreach (var include in includes)
{
query = query.AsNoTracking().Include(include);
}
}
IEnumerable<TEntity> result = query.AsEnumerable();
return result;
}
Upvotes: 0
Views: 972
Reputation: 4859
EF6 is usually quite good at guessing what needs to be commited to the DB and what not. I am typically using code like this:
Payment payment = context.Payment.Find( 42 ); //get payment with id 42 from db
payment.Value = 199;
context.SaveChanges(); //only changed data is saved
UPDATE: A more elaborate example:
DbContext:
class TestContext : DbContext
{
public TestContext()
: base()
{}
public DbSet<ParentEntity> Parent { get; set; }
public DbSet<ChildEntity> Child { get; set; }
}
Parent Entity:
class ParentEntity
{
[Key]
public int Id { get; protected set; }
public string Property1 { get; set; }
public string Property2 { get; set; }
public virtual ChildEntity child { get; set; } // virtual to enable lazy loading
}
Child Entity:
class ChildEntity
{
[Key]
public int Id { get; set; }
public string Value1 { get; set; }
public string Value2 { get; set; }
}
The test code:
using ( TestContext context = new TestContext( ) )
{
context.Database.Log = Console.WriteLine;
ParentEntity p1 = new ParentEntity( );
p1.Property1 = "Value of P1";
p1.Property2 = "Value of P2";
ChildEntity c1 = new ChildEntity( );
c1.Value1 = "V1";
c1.Value2 = "V2";
p1.child = c1;
context.Parent.Add( p1 );
context.SaveChanges( );
myEntity = p1.Id;
}
using( TestContext context = new TestContext() )
{
context.Database.Log = Console.WriteLine;
ParentEntity p = context.Parent.Where( x => x.Id == myEntity ).FirstOrDefault( );
p.Property1 = "Changed";
p.Property2 = "Value of P2";
context.SaveChanges( );
}
The executed sql:
Started transaction at 28.04.2015 08:49:40 +02:00
INSERT [dbo].[ChildEntities]([Value1], [Value2]) VALUES (@0, @1) SELECT [Id] FROM [dbo].[ChildEntities] WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'V1' (Type = String, Size = -1) -- @1: 'V2' (Type = String, Size = -1) -- Executing at 28.04.2015 08:49:41 +02:00 -- Completed in 3 ms with result: SqlDataReader
INSERT [dbo].[ParentEntities]([Property1], [Property2], [child_Id]) VALUES (@0, @1, @2) SELECT [Id] FROM [dbo].[ParentEntities] WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()
-- @0: 'Value of P1' (Type = String, Size = -1) -- @1: 'Value of P2' (Type = String, Size = -1) -- @2: '2' (Type = Int32) -- Executing at 28.04.2015 08:49:41 +02:00 -- Completed in 3 ms with result: SqlDataReader
Committed transaction at 28.04.2015 08:49:41 +02:00 Closed connection at 28.04.2015 08:49:41 +02:00 Opened connection at 28.04.2015 08:49:41 +02:00
SELECT [Limit1].[Id] AS [Id], [Limit1].[Property1] AS [Property1], [Limit1].[Property2] AS [Property2], [Limit1].[child_Id] AS [child_Id] FROM ( SELECT TOP (1) [Extent1].[Id] AS [Id], [Extent1].[Property1] AS [Property1], [Extent1].[Property2] AS [Property2], [Extent1].[child_Id] AS [child_Id] FROM [dbo].[ParentEntities] AS [Extent1] WHERE [Extent1].[Id] = @p__linq__0 ) AS [Limit1]
-- p__linq__0: '2' (Type = Int32, IsNullable = false) -- Executing at 28.04.2015 08:49:41 +02:00 -- Completed in 0 ms with result: SqlDataReader
Closed connection at 28.04.2015 08:49:41 +02:00 Opened connection at 28.04.2015 08:49:41 +02:00 Started transaction at 28.04.2015 08:49:41 +02:00
UPDATE [dbo].[ParentEntities] SET [Property1] = @0 WHERE ([Id] = @1)
-- @0: 'Changed' (Type = String, Size = -1) -- @1: '2' (Type = Int32) -- Executing at 28.04.2015 08:49:41 +02:00 -- Completed in 5 ms with result: 1
Committed transaction at 28.04.2015 08:49:41 +02:00 Closed connection at 28.04.2015 08:49:41 +02:00
So as can be seen from the SQL-Log only the changed property is updated.
Be careful: 'Changed' means also assigned to the same value. So if you update from a TextBox
check whether the value has changed before assigning it.
UPDATE
EF6 seems to really track the changes - an assignment with the same value does not trigger an update - see updated code
Upvotes: 0