I'm using EF code first for developing my 3 layer WinForm Application, I used disconnected POCO
s as my model entities. All my entities inherited from BaseEntity
class.
I used disconnected POCO
s, so I handle entity's State
on client side, and in ApplyChanges()
method, I attach my entity graph(e.g An Order
with it's OrderLines
and Products
) to my DbContext
and then synch each entity's State
with it's client side State
.
public class BaseEntity
{
int _dataBaseId = -1;
public virtual int DataBaseId // DataBaseId override in each entity to return it's key
{
get { return _dataBaseId; }
}
public States State { get; set; }
public enum States
{
Unchanged,
Added,
Modified,
Deleted
}
}
So, when I want to save a graph of related entities, I used following methods:
public static EntityState ConvertState(BaseEntity.States state)
{
switch (state)
{
case BaseEntity.States.Added:
return EntityState.Added;
case BaseEntity.States.Modified:
return EntityState.Modified;
case BaseEntity.States.Deleted:
return EntityState.Deleted;
default:
return EntityState.Unchanged;
}
}
public void ApplyChanges<TEntity>(TEntity root) where TEntity : BaseEntity
{
_dbContext.Set<TEntity>().Add(root);
foreach (var entry in _dbContext.ChangeTracker
.Entries<BaseEntity>())
{
BaseEntity stateInfo = entry.Entity;
entry.State = ConvertState(stateInfo.State);
}
}
But if my graph contains 2 or more entities with the same key i give this error :
An object with the same key already exists in the ObjectStateManager...
How can i detect entities with the same keys in my graph(root
) and make them unique in my ApplyChanges()
method?
There is a way to search the database and check if a record with that same primary key already exists, I don't know if that's what you are looking for, but the code is below:
public static class ObjectSetExtensions
{
#region Constants
private const BindingFlags KeyPropertyBindingFlags =
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
#endregion
#region Public Methods and Operators
public static bool RecordExists<TEntity>(
this ObjectSet<TEntity> set,
TEntity entity) where TEntity : class
{
Contract.Requires(set != null);
Contract.Requires(entity != null);
var expressionParameter = Expression.Parameter(typeof(TEntity));
var keyProperties = set.GetKeyProperties();
var matchExpression =
keyProperties.Select(
pi =>
Expression.Equal(
Expression.Property(expressionParameter, pi.Last()),
Expression.Constant(pi.Last().GetValue(entity, null))))
.Aggregate<BinaryExpression, Expression>(
null,
(current, predicate) => (current == null) ? predicate :
Expression.AndAlso(current, predicate));
var existing =
set.SingleOrDefault(Expression.Lambda<Func<TEntity, bool>>(
matchExpression,
new[] { expressionParameter }));
return existing != null;
}
#endregion
#region Methods
private static IEnumerable<PropertyPathCollection> GetKeyProperties<TEntity>(this ObjectSet<TEntity> objectSet)
where TEntity : class
{
Contract.Requires(objectSet != null);
var entityType = typeof(TEntity);
return
objectSet.EntitySet.ElementType.KeyMembers.Select(
c => new PropertyPathCollection(entityType.GetProperty(c.Name, KeyPropertyBindingFlags)));
}
#endregion
}
public sealed class PropertyPathCollection : IEnumerable<PropertyInfo>
{
// Fields
#region Static Fields
public static readonly PropertyPathCollection Empty = new PropertyPathCollection();
#endregion
#region Fields
private readonly List<PropertyInfo> components;
#endregion
// Methods
#region Constructors and Destructors
public PropertyPathCollection(IEnumerable<PropertyInfo> components)
{
this.components = new List<PropertyInfo>();
this.components.AddRange(components);
}
public PropertyPathCollection(PropertyInfo component)
{
this.components = new List<PropertyInfo> { component };
}
private PropertyPathCollection()
{
this.components = new List<PropertyInfo>();
}
#endregion
#region Public Properties
public int Count
{
get
{
return this.components.Count;
}
}
#endregion
#region Public Indexers
public PropertyInfo this[int index]
{
get
{
return this.components[index];
}
}
#endregion
#region Public Methods and Operators
public static bool Equals(PropertyPathCollection other)
{
if (ReferenceEquals(null, other))
{
return false;
}
return true;
}
public static bool operator ==(PropertyPathCollection left, PropertyPathCollection right)
{
return Equals(left, right);
}
public static bool operator !=(PropertyPathCollection left, PropertyPathCollection right)
{
return !Equals(left, right);
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj))
{
return false;
}
if (ReferenceEquals(this, obj))
{
return true;
}
if (obj.GetType() != typeof(PropertyPathCollection))
{
return false;
}
return Equals((PropertyPathCollection)obj);
}
public override int GetHashCode()
{
return this.components.Aggregate(0, (t, n) => (t + n.GetHashCode()));
}
#endregion
#region Explicit Interface Methods
IEnumerator<PropertyInfo> IEnumerable<PropertyInfo>.GetEnumerator()
{
return this.components.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.components.GetEnumerator();
}
#endregion
}
And the usage is like this:
var context = this.DbContext;
var adapter = context as IObjectContextAdapter;
var objectContext = adapter.ObjectContext;
objectContext.CreateObjectSet<TEntity>().RecordExists(instance);
When you call _dbContext.Set<TEntity>().Add(root);
it tells the context that all the entities in the graph have a state of EntityState.Added
. But 2 entities with the same ID and EntityState.Added
are not allowed, and an exception is thrown.
Try changing the line to _dbContext.Set<TEntity>().Attach(root);
. That will put the graph into the context with EntityState.Unchanged
. Entities that are already in the context in some other state will have their state set to Unchanged.
Now you should be able to go fix the states.
Struck this answer out because Attach
causes the same error - as per comment
References:
When to use DbSet<T>.Add() vs DbSet<T>.Attach()
Why does Entity Framework Reinsert Existing Objects into My Database?
Making Do with Absent Foreign Keys
Provide better support for working with disconnected entities
DbSet.Attach method
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With