in my DbContext subclass I have overridden the SaveChanges() method so I can implement a sort of Trigger-like functionality (before the changes are actually saved).
Now, in some of these triggers it is necessary to detect whether certain relationships have changed, regardless of many-to-many, one-to-one/zero etc.
I have read a number of posts on the internet, including some on this site, that mention that the DbContext API doesn't expose any means of getting relationship info.
However, ObjectContext should be able to.
My SaveChanges method:
public override int SaveChanges()
{
IEntity entity;
ChangeTracker.DetectChanges();
var stateManager = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager;
var added = stateManager.GetObjectStateEntries(EntityState.Added).ToList();
var updated = stateManager.GetObjectStateEntries(EntityState.Modified).ToList();
var deleted = stateManager.GetObjectStateEntries(EntityState.Deleted).ToList();
var unchanged = stateManager.GetObjectStateEntries(EntityState.Unchanged).ToList();
while ((entity = _entitiesRequiringTriggering.FirstOrDefault(x => x.Value).Key) != null)
{
_entitiesRequiringTriggering[entity] = false;
var entry = ChangeTracker.Entries<IEntity>().SingleOrDefault(x => x.State != EntityState.Unchanged && x.Entity == entity);
if (entry == null) continue;
var trigger = Triggers.Triggers.GetTriggerForEntity(entry.Entity, this);
if (trigger == null) continue;
trigger.BeforeSave(entry.Entity);
switch (entry.State)
{
case EntityState.Added:
trigger.BeforeAdd(entry.Entity);
break;
case EntityState.Modified:
trigger.BeforeUpdate(entry.Entity);
break;
case EntityState.Deleted:
trigger.BeforeDelete(entry.Entity);
break;
}
}
return base.SaveChanges();
}
Note the four variables added, updated, deleted and unchanged.
According to what I've found so far, the GetObjectStateEntries is supposed to return a collection of ObjectStateEntry, which has a property IsRelationship.
I run the following code in a test application:
using (var db = container.Resolve<IDatabaseContext>())
{
var cus = db.Query<Customer>().Single(x => x.Id == 1);
var newAddress = db.Query<Address>().Single(x => x.Id == 5);
cus.Address = newAddress; //also sets the foreign key property Customer.AddressId to its new corresponding value
db.SaveChanges();
}
When I inspect the code in SaveChanges after that call, I get what was expected:
one result in the updated list, the Customer object.
But at no point do I ever get an ObjectStateEntry for the relationship (one-to-one) Customer_Address.
I need to be able to detect as previously described when the relationship has changed.
For normal scalar properties you would do this:
var changed = DbEntry.Property(x => x.Name).OriginalValue == DbEntry.Property(x => x.Name).CurrentValue;
But for reference properties that doesn't work obviously.
Any ideas?
You can use this ExtensionMethod to get a list of relationships that changed
public static class DbContextExtensions
{
public static IEnumerable<Tuple<object, object>> GetRelationships(
this DbContext context)
{
return GetAddedRelationships(context)
.Union(GetDeletedRelationships(context));
}
public static IEnumerable<Tuple<object, object>> GetAddedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]);
}
public static IEnumerable<Tuple<object, object>> GetDeletedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]);
}
private static IEnumerable<Tuple<object, object>> GetRelationships(
this DbContext context,
EntityState relationshipState,
Func<ObjectStateEntry, int, object> getValue)
{
context.ChangeTracker.DetectChanges();
var objectContext = ((IObjectContextAdapter)context).ObjectContext;
return objectContext.ObjectStateManager
.GetObjectStateEntries(relationshipState)
.Where(e => e.IsRelationship)
.Select(
e => Tuple.Create(
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
}
Related
I am creating a uni-directional sync which is I have 2 context (WebContext and StagingContext). Everytime I call SaveChangesAsync() it should sync the data updated on WebContext to StagingContext. Here's the following code I have:
Attributes
[AttributeUsage(AttributeTargets.Class)]
public class SyncEntityAttribute : Attribute
{
public Type Target { get; set; }
}
DbContext
public override async Task<int> SaveChangesAsync(CancellationToken cancellation = default)
{
await SyncEntityAsync(stagingContext, cancellation);
return await base.SaveChangesAsync(cancellation);
}
private async Task SyncEntityAsync<T>(T dbContext, CancellationToken cancellation = default) where T : DbContext
{
ChangeTracker.DetectChanges();
var entries = ChangeTracker.Entries()
.Where(x => Attribute.GetCustomAttribute(x.Entity.GetType(), typeof(SyncEntityAttribute)) != null)
.ToList();
try
{
foreach (var entry in entries)
{
if (entry.State == EntityState.Detached || entry.State == EntityState.Unchanged)
continue;
var attribute = Attribute.GetCustomAttribute(entry.Entity.GetType(), typeof(SyncEntityAttribute)) as SyncEntityAttribute;
if (attribute == null)
continue;
var uniqueKeys = entry.Properties.Where(x => x.Metadata.IsUniqueIndex()).ToList();
var targetEntity = await GetQueryable(dbContext, attribute.TargetEntity); // Not yet implemented, I want this to return a single entity based on the generic type filtered by its unique key
mapper.Map(entry.Entity, targetEntity);
switch (entry.State)
{
case EntityState.Added:
dbContext.Add(targetEntity);
break;
case EntityState.Deleted:
if (targetEntity.HasProperty("IsActive"))
{
targetEntity.TrySetProperty("IsActive", false);
}
break;
}
}
}
catch (Exception ex)
{
Log.Error(ex.Message);
}
}
I need to sync any entity model that has the SyncEntity attribute in it. I want to query the data on the StagingContext to see if it already exists or not. In order to do this, I need to query it by its Target attribute which is a Generic Type. If I am only querying using Primary Key then this would be easy since the DbContext has FindAsync method which allows to pass Generic Types. I want to do the same with FindAsync but this time I will be filtering it using the unique key which unique are get from entry.Properties.Where(x => x.Metadata.IsUniqueIndex()).ToList();
How can I achieve this? Looking for a solution like an extension method but I can't find any in the internet.
Update based on the suggested solution:
private static async Task<object> _GetQueryable<T>(DbContext dbContext, List<PropertyEntry> uniqueKeys)
where T : class
{
if (uniqueKeys is null) throw new ArgumentNullException();
if (uniqueKeys.Count <= 0) throw new ArgumentNullException();
var p = Expression.Parameter(typeof(T));
var filters = new List<Expression>(uniqueKeys.Count);
foreach (var key in uniqueKeys)
{
var wrapper = Expression.Constant(Activator.CreateInstance(typeof(Wrapper<>).MakeGenericType(key.CurrentValue.GetType()), key.CurrentValue));
var value = Expression.Property(wrapper, "Value");
filters.Add(Expression.Equal(p, value));
}
var body = filters.Aggregate((c, n) => Expression.AndAlso(c, n));
var predicate = Expression.Lambda<Func<T, bool>>(body, p);
return await dbContext.Set<T>().FirstOrDefaultAsync(predicate);
}
This is my current solution for now, but executing the code filters.Add(Expression.Equal(p, value)); throws an exception The binary operator Equal is not defined for the types 'Web.Gateway.Core.StagingModels.User' and 'System.String'.. It seems like it compared the unique key to the model and not on the model property.
Update: Final code that works
private static Task<object> GetQueryable(DbContext dbContext, Type entityType, List<PropertyEntry> uniqueKeys)
{
return (Task<object>)typeof(SharedContext).GetMethod(nameof(_GetQueryable), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(entityType)
.Invoke(null, new object[] { dbContext, uniqueKeys });
}
private static async Task<object> _GetQueryable<T>(DbContext dbContext, List<PropertyEntry> uniqueKeys)
where T : class
{
if (uniqueKeys is null) throw new ArgumentNullException();
if (uniqueKeys.Count <= 0) throw new ArgumentNullException();
var entityType = typeof(T);
var p = Expression.Parameter(entityType);
var filters = new List<Expression>(uniqueKeys.Count);
foreach (var key in uniqueKeys)
{
var wrapper = Expression.Constant(Activator.CreateInstance(typeof(Wrapper<>).MakeGenericType(key.CurrentValue.GetType()), key.CurrentValue));
var value = Expression.Property(wrapper, "Value");
filters.Add(Expression.Equal(Expression.Property(p, entityType.GetProperty(key.Metadata.Name)), value));
}
var body = filters.Aggregate((c, n) => Expression.AndAlso(c, n));
var predicate = Expression.Lambda<Func<T, bool>>(body, p);
return await dbContext.Set<T>().FirstOrDefaultAsync(predicate);
}
You can create a generic version of your method GetQueryable and call it through reflection:
private static Task<object> GetQueryable(DbContext dbContext, Type entityType, List<PropertyEntry> uniqueKeys)
{
return (Task<object>)typeof(ApplicationContext).GetMethod(nameof(_GetQueryable), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(entityType)
.Invoke(null, new object[] { dbContext, uniqueKeys });
}
Then the generic version should create a predicate dynamically. Linq expression could be created with Expression class:
private static async Task<object> _GetQueryable<T>(DbContext dbContext, List<PropertyEntry> uniqueKeys)
where T : class
{
if (uniqueKeys is null) throw new ArgumentNullException();
if (uniqueKeys.Count <= 0) throw new ArgumentNullException();
var p = Expression.Parameter(typeof(T));
var filters = new List<Expression>(uniqueKeys.Count);
foreach (var key in uniqueKeys)
{
var property = Expression.Property(p, key.Metadata.PropertyInfo);
var value = Expression.Constant(key.CurrentValue);
filters.Add(Expression.Equal(property, value));
}
var body = filters.Aggregate((c, n) => Expression.AndAlso(c, n));
var predicate = Expression.Lambda<Func<T, bool>>(body, p);
return await dbContext.Set<T>().FirstOrDefaultAsync(predicate);
}
Depending of your expectations, you should rework your code to be more efficient.
Your entities are fetched one by one, maybe you should consider batching.
The call of _GetQueryable<T>() is done by reflection, you could consider it as a slow API (but the previous point is much important). A delegate could be created and cached for each entity type.
Generated expressions with usage of Expression.Constant will lead to non parameterized sql queries: internal cache of EF can grow (with many CompiledQuery instances) and causes some memory leaks. If your database is sqlserver, query execution plan will be different for each entity. You can create a wrapper arround the value, expose the value with a public property and replace the constant expression by a property expression:
public class Wrapper<T>
{
public T Value { get; }
public Wrapper(T value)
{
Value = value;
}
}
//...
var wrapper = Expression.Constant(Activator.CreateInstance(typeof(Wrapper<>).MakeGenericType(key.CurrentValue.GetType()), key.CurrentValue));
var value = Expression.Property(wrapper, "Value");
I am having an issue updating a record with a composite key using EF Core 5 and Net Core 5. I am able to get the record using both parts of the composite key (two ids in my case) but when I save the changes to the database I get the following error:
Database operation expected to affect 1 row(s) but actually affected 24 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
After looking in the database, it looks like entity framework is trying to update all the records in the table with same id (first part of the composite key) instead of updating the record that I got.
Has anyone else seen this behavior? Any ideas of how to get around it or what I might be doing wrong?
Here is a basic version of the table set up
Table1:
Id (PK)
Table2:
Id (PK),
Table1Id (PK, FK)
Scenario where it fails:
Table1:
Id
--
1
2
Table2:
Id, Table1Id
------------
1, 1
2, 1
1, 2
2, 2
If I try to update the first record in Table2 (1, 1), EF would try to update it and the third record (1, 2) and throw the above error.
Code:
// Getting record
var record = await context.Table2s
.FirstOrDefaultAsync(e => e.Id == 1 && e.Table1Id == 1, cancellationToken);
// Saving
await context.SaveChangesAsync(cancellationToken);
// I have also tried specifying which record to update before saving
context.Table2s.Update(record);
Configuration for Table2:
builder.ToTable("Table2");
builder.HasKey(e => new { e.Id, e.Table1Id });
builder.HasOne(e => e.Table1)
.WithMany(m => m.Table2s)
.HasForeignKey(e => e.Table1Id)
.OnDelete(DeleteBehavior.Cascade);
It works in my own projects, I hope it will be useful.
protected override void OnModelCreating(DbModelBuilder mb) {
mb.Entity<WebServisUser>().HasKey(x => new
{
x.Proje, /* Composite Keys */
x.KulAdi /* Composite Keys */
});
mb.Entity<WebServisUser>().Property(e => e.Proje).IsUnicode(false);
mb.Entity<WebServisUser>().Property(e => e.KulAdi).IsUnicode(false);
}
I have added the link other functions for better understanding.
public static class Helper
{
public static bool IsNull(this object value) => (value == null || Convert.IsDBNull(value) || Convert.GetTypeCode(value) == TypeCode.Empty);
public static bool IsNotNull(this object value) => !value.IsNull();
public static string ToPropertyName(this Expression value)
{
if (typeof(LambdaExpression).IsAssignableFrom(value.GetType())) { value = ((LambdaExpression)value).Body; }
if (value is MemberExpression e_mem) { return e_mem.Member.Name; }
else if (value is UnaryExpression e_una) { return ((MemberExpression)e_una.Operand).Member.Name; }
else { throw new InvalidCastException(String.Format("value type can be \"{0}\" or \"{1}\"", nameof(MemberExpression), nameof(UnaryExpression))); }
}
public static object ChangeType(object value, Type type)
{
var c = TryTypeIsNullable(type, out Type _outtype);
return (c && value.IsNull()) ? null : (_outtype.IsEnum ? Enum.ToObject(_outtype, value) : Convert.ChangeType(value, c ? Nullable.GetUnderlyingType(type) : type));
}
public static void SetPropertyValue<T>(this T value, string propertyname, object data) where T : class
{
var p = typeof(T).GetProperty(propertyname);
p.SetValue(value, data.IsNull() ? null : ChangeType(data, p.PropertyType));
}
public static bool TryTypeIsNullable(Type value, out Type outvalue)
{
var t = (value.IsGenericType && value.GetGenericTypeDefinition().Equals(typeof(Nullable<>)));
outvalue = (t ? value.GenericTypeArguments[0] : value); return t;
}
public static bool TryTypeIsAttribute<T, Y>(T value, out Y outvalue) where T : ICustomAttributeProvider where Y : Attribute
{
bool ret;
try
{
var v = value.GetType();
var y = typeof(Y);
if (typeof(Assembly).IsAssignableFrom(v) && Attribute.IsDefined((Assembly)((object)value), y)) { ret = true; }
else if (typeof(MemberInfo).IsAssignableFrom(v) && Attribute.IsDefined((MemberInfo)((object)value), y)) { ret = true; }
else if (typeof(Module).IsAssignableFrom(v) && Attribute.IsDefined((Module)((object)value), y)) { ret = true; }
else if (typeof(ParameterInfo).IsAssignableFrom(v) && Attribute.IsDefined((ParameterInfo)((object)value), y)) { ret = true; }
else { ret = false; }
outvalue = ret ? value.GetCustomAttributes(y, false).Cast<Y>().Take(1).FirstOrDefault() : default;
}
catch
{
outvalue = default;
ret = false;
}
return ret;
}
public static void SetCompositeKey<T, TKey>(this DbContext context, T entity, Expression<Func<T, TKey>> compositekey, TKey compositekeyvalue, Action<T> actionislem = null) where T : class, new()
{
var t = typeof(T);
var c = compositekey.ToPropertyName();
var ie_prop = typeof(T).GetProperties().Where(x => x.GetMethod.IsNotNull() && x.SetMethod.IsNotNull() && !x.GetMethod.IsVirtual && !x.SetMethod.IsVirtual && !Helper.TryTypeIsAttribute(x, out NotMappedAttribute _)).Select(x => new
{
name = x.Name,
iskey = x.Name == c,
iscompositekey = Helper.TryTypeIsAttribute(x, out KeyAttribute _outkeyvalue) && _outkeyvalue.IsNotNull() && Helper.TryTypeIsAttribute(x, out DatabaseGeneratedAttribute _outdatagenvalue) && _outdatagenvalue.DatabaseGeneratedOption == DatabaseGeneratedOption.None
});
if (ie_prop.Where(x => x.iscompositekey).Count() < 2) { throw new KeyNotFoundException("minimum of 2 composite keys exception message..."); }
else if (ie_prop.Any(x => x.iscompositekey && x.iskey))
{
var addentity = new T();
var e = context.Entry<T>(entity);
var dbset = context.Set<T>();
dbset.Attach(entity);
foreach (var item in ie_prop.Select(x => new
{
x.name,
x.iskey
})) { addentity.SetPropertyValue(item.name, item.iskey ? compositekeyvalue : e.Property(item.name).OriginalValue); }
dbset.Add(addentity); /* Insert record about new key value */
if (actionislem.IsNotNull()) { actionislem(addentity); } /* If there are other areas of connection, do the operations there with the action method. */
dbset.Remove(entity); /* delete old value */
}
else { throw new Exception("compositekey must be composite key exception message..."); }
}
}
and example
using (var mdl = new mdlBayUniOrtak())
{
using (var c = new TransactionScope())
{
var beforeentity = mdl.WebServisUsers.Where(x => x.Proje == "loremipsum").Take(1).FirstOrDefault();
var e = new Action<WebServisUser>((WebServisUser afterentity) =>
{
/* Other actions if necessary. Other foreign key operations can be done here! */
});
mdl.SetCompositeKey(beforeentity, x => x.KulAdi, "galatasaray", e);
mdl.SaveChanges();
c.Complete();
}
}
I have a parent table (from oracle dataBase) and some child tables, and I can successfully update the parent entity using the Update method below, but not the child ones.
By the way, can I use with generic entities here?
public void UpdateEnt<T>(T entity) where T : parent_table
{
if (entity == null)
{
throw new ArgumentException("Cannot add a null entity.");
}
using (var _context = (DataModelDetach)d.GetContextForRead(Module))
{
var entry = _context.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
var set = _context.Set<T>();
T attachedEntity = set.Include(x => x.children_1).Include(x => x.children_2).SingleOrDefault(e => e.idSeq == entity.idSeq);
if (attachedEntity != null)
{
var attachedEntry = _context.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified; // This should attach entity
}
}
}
}
I'm trying to do a generic method to update Entity Framework collections, one to many. I did this method, but i'm having a problem, when i try to verify, if the element in the new collection already exists in the old one. If exists, i have to update it, rather then remove and add it again.
The code is this:
public TEntity UpdateCollection<TEntity, TChildren>(myappContext dbContext, TEntity parentObject, Expression<Func<TEntity,
ICollection<TChildren>>> propertyExpression, ICollection<TChildren> objectChilren) where TEntity : class where TChildren : class
{
var parentEntityObject = dbContext.Entry<TEntity>(parentObject);
List<TChildren> originalChildrenData = parentEntityObject.Collection(propertyExpression).CurrentValue.ToList();
// Updating or removing existing items
foreach (var originalItem in originalChildrenData)
{
// Where the problem is: If entry was just modified, i have to update.
var newItem = objectChilren.FirstOrDefault(x => x == originalItem);
if (newItem != null)
{
dbContext.Entry<TChildren>(originalItem).CurrentValues.SetValues(newItem);
dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Modified;
}
else
{
dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Deleted;
}
}
// Adding new items
foreach(var newItem in objectChilren.Except(originalChildrenData)){
parentEntityObject.Collection(propertyExpression).CurrentValue.Add(newItem);
}
parentEntityObject.State = System.Data.EntityState.Modified;
return parentEntityObject.Entity;
}
Instead of try to check with:
var newItem = objectChilren.FirstOrDefault(x => x == originalItem);
if (newItem != null)
I also tryed with:
var newItem = this.Set<TChildren>().Local.FirstOrDefault(x => x == originalItem);
But also doesn't work, always returns null. I have to get the corresponding entry and only update it.
If it's not possible, there is another generic way to update collections "one to many"?
I did it, comparing the key property from my collection, as Magnus suggested, like this:
public TEntity UpdateCollection<TEntity, TChildren>(myappContext dbContext, TEntity parentObject, Expression<Func<TEntity,
ICollection<TChildren>>> propertyExpression, ICollection<TChildren> objectChilren) where TEntity : class where TChildren : class
{
var parentEntityObject = dbContext.Entry<TEntity>(parentObject);
List<TChildren> originalChildrenData = parentEntityObject.Collection(propertyExpression).CurrentValue.ToList();
// Get key name
var entityKeyName = GetKeyName(dbContext, originalChildrenData.Union(objectChilren).First());
// Updating or removing existing items
foreach (var originalItem in originalChildrenData)
{
var originalValueKey = originalItem.GetType().GetProperty(entityKeyName).GetValue(originalItem, null);
var itemCompareExpression = GetCompareExpression<TChildren>(entityKeyName, originalValueKey);
// If entry was just modified, i have to update.
var newItem = objectChilren.FirstOrDefault(itemCompareExpression.Compile());
if (newItem != null)
{
dbContext.Entry<TChildren>(originalItem).CurrentValues.SetValues(newItem);
dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Modified;
// Remove item, because only 'new items' will be added after this loop
objectChilren.Remove(newItem);
}
else
{
dbContext.Entry<TChildren>(originalItem).State = System.Data.EntityState.Deleted;
}
}
// Adding new items
foreach(var newItem in objectChilren)
{
parentEntityObject.Collection(propertyExpression).CurrentValue.Add(newItem);
}
parentEntityObject.State = System.Data.EntityState.Modified;
return parentEntityObject.Entity;
}
Methods called:
public string GetKeyName<TEntity>(myappContext dbContext, TEntity entity) where TEntity : class
{
ObjectContext objectContext = ((IObjectContextAdapter)dbContext).ObjectContext;
ObjectSet<TEntity> set = objectContext.CreateObjectSet<TEntity>();
return set.EntitySet.ElementType.KeyMembers.FirstOrDefault().Name;
}
public Expression<Func<TEntity, bool>> GetCompareExpression<TEntity>(string keyName, object value) where TEntity : class
{
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, keyName);
var method = property.Type.GetMethod("Equals", new[] { property.Type });
var convertedValue = Convert.ChangeType(value, property.Type);
var expression = Expression.Call(property, method, Expression.Constant(convertedValue));
return Expression.Lambda<Func<TEntity, bool>>(expression, parameter);
}
As all my entities only have one key, i just used "First" on "GetKeyName", but it returns all keys, if necessary.
I'm trying to add a LINQ or DbContext extension method to get an element (FirstOrDefault) but if one does not already exist then create a new instance with data (FirstOrCreate) instead of returning null.
is this possible?
i.e.:
public static class LINQExtension
{
public static TSource FirstOrCreate<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
if (source.First(predicate) != null)
{
return source.First(predicate);
}
else
{
return // ???
}
}
}
and a usage could be:
using (var db = new MsBoxContext())
{
var status = db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled");
//Here we should get the object if we find one
//and if it doesn't exist create and return a new instance
db.Entities.Add(new Entity()
{
Name = "New Entity",
Status = status
});
}
I hope that you understand my approach.
public static class LINQExtension
{
public static TSource FirstOrCreate<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate,
Func<T> defaultValue)
{
return source.FirstOrDefault(predicate) ?? defaultValue();
}
}
usage
var status = db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled",
() => new EntityStatus {Name = "Enabled"});
However you must note that this will not work quite like FirstOrDefault().
If you did the following
var listOfStuff = new List<string>() { "Enabled" };
var statuses = from s in listOfStuff
select db.EntitiesStatus.FirstOrCreate(s => s.Name == "Enabled",
() => new EntityStatus {Name = "Enabled"});
You would get O(n) hits to the database.
However I suspect if you did...
var listOfStuff = new List<string>() { "Enabled" };
var statuses = from s in listOfStuff
select db.EntitiesStatus.FirstOrDefault(s => s.Name == "Enabled")
?? new EntityStatus {Name = "Enabled"};
It is plausible it could work...
conclussion:
instead of implement an extension method the best solution is using ?? operator in that way:
var status = db.EntitiesStatus.FirstOrDefault(s => s.Name == "Enabled") ?? new EntityStatus(){Name = "Enabled"};
I am a self taught programmer and I am really bad at typing so i was looking for the exact same thing. I ended up writing my own. It took a few steps and revisions before it would work with more than 1 property. Of course there are some limitations and I haven't fully tested it but so far it seems to work for my purposes of keeping the records distinct in the DB and shortening the code (typing time).
public static class DataExtensions
{
public static TEntity InsertIfNotExists<TEntity>(this ObjectSet<TEntity> objectSet, Expression<Func<TEntity, bool>> predicate) where TEntity : class, new()
{
TEntity entity;
#region Check DB
entity = objectSet.FirstOrDefault(predicate);
if (entity != null)
return entity;
#endregion
//NOT in the Database... Check Local cotext so we do not enter duplicates
#region Check Local Context
entity = objectSet.Local().AsQueryable().FirstOrDefault(predicate);
if (entity != null)
return entity;
#endregion
///********* Does NOT exist create entity *********\\\
entity = new TEntity();
// Parse Expression Tree and set properties
//Hit a recurrsive function to get all the properties and values
var body = (BinaryExpression)((LambdaExpression)predicate).Body;
var dict = body.GetDictionary();
//Set Values on the new entity
foreach (var item in dict)
{
entity.GetType().GetProperty(item.Key).SetValue(entity, item.Value);
}
return entity;
}
public static Dictionary<string, object> GetDictionary(this BinaryExpression exp)
{
//Recurssive function that creates a dictionary of the properties and values from the lambda expression
var result = new Dictionary<string, object>();
if (exp.NodeType == ExpressionType.AndAlso)
{
result.Merge(GetDictionary((BinaryExpression)exp.Left));
result.Merge(GetDictionary((BinaryExpression)exp.Right));
}
else
{
result[((MemberExpression)exp.Left).Member.Name] = exp.Right.GetExpressionVaule();
}
return result;
}
public static object GetExpressionVaule(this Expression exp)
{
if (exp.NodeType == ExpressionType.Constant)
return ((ConstantExpression)exp).Value;
if (exp.Type.IsValueType)
exp = Expression.Convert(exp, typeof(object));
//Taken From http://stackoverflow.com/questions/238413/lambda-expression-tree-parsing
var accessorExpression = Expression.Lambda<Func<object>>(exp);
Func<object> accessor = accessorExpression.Compile();
return accessor();
}
public static IEnumerable<T> Local<T>(this ObjectSet<T> objectSet) where T : class
{
//Taken From http://blogs.msdn.com/b/dsimmons/archive/2009/02/21/local-queries.aspx?Redirected=true
return from stateEntry in objectSet.Context.ObjectStateManager.GetObjectStateEntries(
EntityState.Added |
EntityState.Modified |
EntityState.Unchanged)
where stateEntry.Entity != null && stateEntry.EntitySet == objectSet.EntitySet
select stateEntry.Entity as T;
}
public static void Merge<TKey, TValue>(this Dictionary<TKey, TValue> me, Dictionary<TKey, TValue> merge)
{
//Taken From http://stackoverflow.com/questions/4015204/c-sharp-merging-2-dictionaries
foreach (var item in merge)
{
me[item.Key] = item.Value;
}
}
}
Usage is as simple as:
var status = db.EntitiesStatus.InsertIfNotExists(s => s.Name == "Enabled");
The extension will check the database first, if is not found it will check the local context (so you do not add it twice), if it is still not found it creates the entity, parses the expression tree to get the properties and values from the lambda expression, sets those values on a new entity, adds the entity to the context and returns the new entity.
A few things to be aware of...
This does not handle all possible uses (assumes all the expressions in the lambda are ==)
The project I did this in is using an ObjectContext as apposed to a DBContext (I have not switched yet so I don't know if this would work with DBContext. I assume it would not be difficult to change)
I am self-taught so there maybe many ways to optimize this. If you have any input please let me know.
What about this extension that also adds the new created entity to the DbSet.
public static class DbSetExtensions
{
public static TEntity FirstOrCreate<TEntity>(
this DbSet<TEntity> dbSet,
Expression<Func<TEntity, bool>> predicate,
Func<TEntity> defaultValue)
where TEntity : class
{
var result = predicate != null
? dbSet.FirstOrDefault(predicate)
: dbSet.FirstOrDefault();
if (result == null)
{
result = defaultValue?.Invoke();
if (result != null)
dbSet.Add(result);
}
return result;
}
public static TEntity FirstOrCreate<TEntity>(
this DbSet<TEntity> dbSet,
Func<TEntity> defaultValue)
where TEntity : class
{
return dbSet.FirstOrCreate(null, defaultValue);
}
}
The usage with predicate:
var adminUser = DbContext.Users.FirstOrCreate(u => u.Name == "Admin", () => new User { Name = "Admin" });
or without predicate:
var adminUser = DbContext.Users.FirstOrCreate(() => new User { Name = "Admin" });