Entity Framework error - New transaction is not allowed - c#

I am currently getting this error in an EF method that is being called (having got Elmah workking)
New transaction is not allowed because there are other threads running in the session.
I have looked at this :
SqlException from Entity Framework - New transaction is not allowed because there are other threads running in the session
and similar questions, but these all refer to savechanges being called within a foreach loop. My code doesn't have a foreach loop, so i'm struggling to find the issue.
Controller (inherited from ApiController)
static readonly IMyRepository myRepository = new MyRepository();
public HttpResponseMessage PutObject(int id, int id2)
{
if(!myRepository.Update(id,id2))
{
return Request.CreateErrorResponse(HttpStatusCode.NotFound);
}
else
{
return Request.CreateResponse(HttpStatusCode.OK);
}
}
Repository
MyEntities _myEntities;
public MyRepository(MyEntities context)
{
_myEntities = context;
}
public MyRepository()
{
}
public bool Update(int id,int id2, string id3, int id4)
{
_myEntities = _myEntities ?? new MyEntities();
//update by id if id2,id3 and id4 are zero
if (id2 == 0 && id3 == "0" && id4 == 0)
{
var myobject = _myEntities.MyObjects.Where(x => x.id == id);
if (myobject.Count() > 0)
{
MyObject temp = myobject.SingleOrDefault();
temp.Processed = true;
_myEntities.SaveChanges();
return true;
}
else
{
return false;
}
}
else
{
var myobject = _myEntities.MyObjects.Where(x => x.SourceID == id && x.ExternalID == id2 && x.InternalID == id3 && x.Code == id4 && x.Processed == false);
if (myobject.Count() > 0)
{
myobject temp = myobject.SingleOrDefault();
temp.Processed = true;
_myEntities.SaveChanges();
return true;
}
else
{
return false;
}
}
}
is this because the IQueryable returned by the linq, still holds an open connection?

Related

Update Record with Composite Key - Entity Framework Core

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();
}
}

CurrentUser in .Net Core Microservice CQRS

namespace LED.OM.Core.Base
{
public abstract class BaseContext : DbContext
{
public BaseContext(DbContextOptions options) : base(options)
{ }
public override int SaveChanges()
{
UpdateAuditEntities();
return base.SaveChanges();
}
public override int SaveChanges(bool acceptAllChangesOnSuccess)
{
UpdateAuditEntities();
return base.SaveChanges(acceptAllChangesOnSuccess);
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
UpdateAuditEntities();
return base.SaveChangesAsync(cancellationToken);
}
private void UpdateAuditEntities()
{
var CurrentUser = Thread.CurrentPrincipal?.Identity?.Name ?? "";
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is Entity &&
(x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted));
foreach (var entry in modifiedEntries)
{
var entity = (Entity)entry.Entity;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedDate = now;
entity.CreatedBy = CurrentUser;
}
else if (entry.State == EntityState.Deleted && entry.Entity is not IHardDelete)
{
// Varlığı değiştirilmedi olarak ayarlıyoruz.
// (tüm varlığı Değiştirildi olarak işaretlersek, her alan güncelleme olarak Db'ye gönderilir)
entry.State = EntityState.Unchanged;
// Yalnızca IsDeleted alanını güncelleyin - yalnızca bu Db'ye gönderilir
entity.IsDelete = true;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
}
}
}
user.cs
entity.cs
I am working with microservice. The entity is the common field in my entire table. I want to fill in the fields created and updated by those who want to log in. But I couldn't find how to fill this user info in BaseContext.
The problem is not about microservices or CQRS. The problem is about Entity Framework Core. So you need to detect the modified entities in UpdateAuditEntities() method then you can fill the UpdatedDate and
UpdatedBy properties.
Also, this method already fills the CreatedDate and CreatedBy properties in creating (EntityState.Added) operations.
You can get an exception when casting entry.Entity to LED.OM.Core.Base.Entities.Entity. You should use this code for retrieving modified entries: var modifiedEntries = ChangeTracker.Entries<LED.OM.Core.Base.Entities.Entity>()...
You should handle the updated entities (EntityState.Modified) like below:
private void UpdateAuditEntities()
{
var CurrentUser = Thread.CurrentPrincipal?.Identity?.Name ?? "";
var modifiedEntries = ChangeTracker.Entries<LED.OM.Core.Base.Entities.Entity>()
.Where(x => x.Entity is Entity &&
(x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted));
foreach (var entry in modifiedEntries)
{
var entity = (Entity)entry.Entity;
var now = DateTime.UtcNow;
if (entry.State == EntityState.Added)
{
entity.CreatedDate = now;
entity.CreatedBy = CurrentUser;
}
else if (entry.State == EntityState.Modified)
{
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
else if (entry.State == EntityState.Deleted && entry.Entity is not IHardDelete)
{
// Varlığı değiştirilmedi olarak ayarlıyoruz.
// (tüm varlığı Değiştirildi olarak işaretlersek, her alan güncelleme olarak Db'ye gönderilir)
entry.State = EntityState.Unchanged;
// Yalnızca IsDeleted alanını güncelleyin - yalnızca bu Db'ye gönderilir
entity.IsDelete = true;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedDate = now;
entity.UpdatedBy = CurrentUser;
}
}

EF ChangeTracker SoftDelete

I'm looking for a better way for implementing the following logic (HasAnyRelation and SetDeleteMarks) into ChangeTracker in order to avoid rewriting this logic for all my entities.
The problem is that once we call Remove() it will drop all relations with child/parent entities and I can't check the relations into ChangeTracker anymore.
In this post I got a suggestion to implement a copy of my previous entity to keep my collections intact after Remove(). However it works, I also would have to implement the logic many times.
Does anybody would have any other approach to achieve it?
Regards
CONTROLLER DELETE ACTION
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Delete(Sector sector)
{
if (ModelState.IsValid)
{
var currentEntity = db.Sector.Find(sector.SectorId);
var hasAnyRelation = EntityHelper.HasAnyRelation(currentEntity);
if (hasAnyRelation)
{
currentEntity = (Sector)EntityHelper.SetDeleteMarks(currentEntity);
db.Sector.Attach(currentEntity);
db.Entry(currentEntity).State = EntityState.Modified;
}
else
{
db.Sector.Attach(currentEntity);
db.Sector.Remove(currentEntity);
}
db.SaveChanges();
}
}
ENTITY HELPER
public static class EntityHelper
{
public static object SetDeleteMarks(object entityObj)
{
var deletedProperty = entityObj.GetType().GetProperties().Where(p => p.Name == "Deleted").FirstOrDefault();
var nameProperties = entityObj.GetType().GetProperties().Where(p => p.Name.Contains("Name"));
if (deletedProperty != null)
{
deletedProperty.SetValue(entityObj, true);
foreach (var nameProperty in nameProperties)
{
var deletedMark = "(*)";
var currentValue = nameProperty.GetValue(entityObj).ToStringNullSafe();
if (!currentValue.Contains(deletedMark) && !String.IsNullOrEmpty(currentValue))
{
nameProperty.SetValue(entityObj, String.Format("{0} {1}", currentValue, deletedMark).Trim());
}
}
}
return entityObj;
}
public static bool HasAnyRelation(object entityObj)
{
var collectionProps = GetManyRelatedEntityNavigatorProperties(entityObj);
foreach (var item in collectionProps)
{
var collectionValue = GetEntityFieldValue(entityObj, item.Name);
if (collectionValue != null && collectionValue is IEnumerable)
{
var col = collectionValue as IEnumerable;
if (col.GetEnumerator().MoveNext())
{
return true;
}
}
}
return false;
}
private static object GetEntityFieldValue(this object entityObj, string propertyName)
{
var pro = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).First(x => x.Name == propertyName);
return pro.GetValue(entityObj, null);
}
private static IEnumerable<PropertyInfo> GetManyRelatedEntityNavigatorProperties(object entityObj)
{
var props = entityObj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.CanWrite && x.GetGetMethod().IsVirtual && x.PropertyType.IsGenericType == true);
return props;
}
}

Anonymous method with return type IEnumerable<T>

I'm designing a search engine for my website. Read search key and return data.
My test code:
public string ErrorMessage { get; set; }
private IEnumerable<TopicViewModels> GetTopics(List<TopicViewModels> topics)
{
foreach (var item in topics)
{
yield return item;
}
}
public async Task<IEnumerable<TopicViewModels>> Search(string key)
{
try
{
using (var db = new MyDbContext()) //EF
{
var topics = await db.Topics.Where(x => x.Title.Contains(key)).ToListAsync();
if (topics != null && topics.Count > 0)
{
return await Task.Run(() => GetTopics(topics));
}
ErrorMessage = "No topic was found.";
}
}
catch (Exception e)
{
ErrorMessage = e.Message;
}
return null;
}
I'm looking for a solution that I can use GetTopics method as an anonymous method. No need to create new method to get all topics because no more another classes/methods reuse GetTopics method.
But my problem is: yield return cannot be accepted in anonymous method. Just like:
var topics = await db.Topics.Where(x => x.Title.Contains(key)).ToListAsync();
topics.ForEach(x =>
{
yield return x;
});
So, my question is: Is there another way to do it better?
UPDATE: (Based on #EricLippert comment)
public async Task<IEnumerable<TopicViewModels>> Search(string key)
{
using (var db = new MyDbContext())
{
var topics = await db.Topics.Where(x => x.Title.Contains(key)).ToListAsync();
if (topics != null && topics.Count > 0)
{
foreach (var topic in topics)
{
yield return topic;
}
}
ErrorMessage = "No topic was found.";
yield return null;
}
}
Error syntax message:
The body of 'TopicMaster.Search(string)' cannot be an iterator block
because Task<IEnumerable<TopicViewModels>> is not an iterator
interface type
UPDATE 2:
public async Task<IEnumerable<TopicViewModels>> Search(string key)
{
var topics = await new MyDbContext().Topics.Where(x => x.Title.Contains(key)).ToListAsync();
return topics != null && topics.Count > 0 ? topics : null;
}
This is what Eric's saying:
if (topics != null && topics.Count > 0)
{
return topics;
}
Specifically, List<T> implements IEnumerable<T>, so you can just return the list. There's no need for an iterator block, or an anonymous delegate, or Task.Run, or foreach/ForEach.

Use ContinueWith to make async method

I want to make async method in redis by StackExchange.Redis follow code :
public bool Insert<T>(T entity) where T : IBaseEntity
{
long entityCounter = _redisClient.StringIncrement(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
if (entity.Id == 0)
{
entity.Id = ((int)GetLastId<T>()) + 1;
}
_redisClient.StringSet(CacheProcessPatterns.MakeLastId(entity.GetType().Name), entity.Id);
string itemRedisKey = CacheProcessPatterns.MakeItemById(entity.GetType().Name, entity.Id);
bool setStatus = _redisClient.StringSet(itemRedisKey, JsonSerializer.SerializeToString<T>(entity));
if (setStatus)
{
_redisClient.StringSet(CacheProcessPatterns.MakeIdByKey(entity.GetType().Name, entity.Id), entity.Key.ToString());
_redisClient.StringSet(CacheProcessPatterns.MakeKeyById(entity.GetType().Name, entity.Key.ToString()), entity.Id);
_redisClient.SetAdd(CacheProcessPatterns.MakeItemKeysByType(entity.GetType().Name), entity.Id);
}
else
{
entityCounter = _redisClient.StringDecrement(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
}
return setStatus;
}
and the other hands i trying make async but I have a problem on second ContinueWith() Method .
Error : Cannot implicity convert type 'Task' to 'Task'.An
explicit conversation exists(are you missing a cast?).
Follow code :
public Task<bool> Insert<T>(T entity) where T : IBaseEntity
{
return _redisClient.StringIncrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name))
.ContinueWith(entityCounter =>
{
if (entity.Id == 0)
{
entity.Id = ((int)GetLastId<T>().Result);
}
}).ContinueWith(_ =>
{
_redisClient.StringSetAsync(CacheProcessPatterns.MakeLastId(entity.GetType().Name), entity.Id).ContinueWith(status =>
{
string itemRedisKey = CacheProcessPatterns.MakeItemById(entity.GetType().Name, entity.Id);
_redisClient.StringSetAsync(itemRedisKey, JsonSerializer.SerializeToString<T>(entity)).ContinueWith( setStatus =>
{
if (setStatus.Result)
{
ITransaction tran = _redisClient.CreateTransaction();
tran.StringSetAsync(CacheProcessPatterns.MakeIdByKey(entity.GetType().Name, entity.Id), entity.Key.ToString());
tran.StringSetAsync(CacheProcessPatterns.MakeKeyById(entity.GetType().Name, entity.Key.ToString()), entity.Id);
tran.SetAddAsync(CacheProcessPatterns.MakeItemKeysByType(entity.GetType().Name), entity.Id);
return tran.ExecuteAsync();
}
else
{
_redisClient.StringDecrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
}
return setStatus;
});
});
});
}
What is my problem ? and how to fix that ?Thanks ...
I think the problem is that your second ContinueWith returns a Task and not a Task<bool>. Try changing the code as follows:
public Task<bool> Insert<T>(T entity) where T : IBaseEntity
{
return _redisClient.StringIncrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name))
.ContinueWith(entityCounter =>
{
if (entity.Id == 0)
{
entity.Id = ((int)GetLastId<T>().Result);
}
})
// Explicitly specify task type to be bool
.ContinueWith<bool>(_ =>
{
_redisClient.StringSetAsync(CacheProcessPatterns.MakeLastId(entity.GetType().Name), entity.Id).ContinueWith(status =>
{
string itemRedisKey = CacheProcessPatterns.MakeItemById(entity.GetType().Name, entity.Id);
_redisClient.StringSetAsync(itemRedisKey, JsonSerializer.SerializeToString<T>(entity)).ContinueWith( setStatus =>
{
if (setStatus.Result)
{
ITransaction tran = _redisClient.CreateTransaction();
tran.StringSetAsync(CacheProcessPatterns.MakeIdByKey(entity.GetType().Name, entity.Id), entity.Key.ToString());
tran.StringSetAsync(CacheProcessPatterns.MakeKeyById(entity.GetType().Name, entity.Key.ToString()), entity.Id);
tran.SetAddAsync(CacheProcessPatterns.MakeItemKeysByType(entity.GetType().Name), entity.Id);
return tran.ExecuteAsync();
}
else
{
_redisClient.StringDecrementAsync(CacheProcessPatterns.MakeItemCounter(entity.GetType().Name));
}
return setStatus;
});
});
return true; // since this is a Task<bool> we need a bool return value

Categories

Resources