Load related data with IgnoreQueryFilter - c#

What is the best way to Update all related Entities when working with Queryfilter?
All my Entities inherit from a BaseClass with "IsDeleted" Property.
When i build my Context i use
if (table.GetProperties().Any(column => column.ClrType == typeof(bool) && column.Name == "IsDeleted"))
{
var parameter = Expression.Parameter(table.ClrType);
var propertyMethodInfo = typeof(EF).GetMethod("Property").MakeGenericMethod(typeof(bool));
var isDeletedProperty = Expression.Call(propertyMethodInfo, parameter, Expression.Constant("IsDeleted"));
BinaryExpression compareExpression = Expression.MakeBinary(ExpressionType.Equal, isDeletedProperty, Expression.Constant(false));
var lambda = Expression.Lambda(compareExpression, parameter);
builder.Entity(table.ClrType).HasQueryFilter(lambda);
}
the HasQueryfilter to Ignore all deleted Entity. SoftDelete.
When i try to Update my all Collections, i only get the filterd Collections. So its never possible to Update my IsDelete Property with a true value.
if (entry.State == EntityState.Modified)
{
foreach (var collectionEntry in _context.Entry(dbMember).Collections)
{
await collectionEntry.LoadAsync();
foreach (var o in collectionEntry.CurrentValue)
{
var entity = o as BaseEntity;
entity.IsDeleted = dbMember.IsDeleted;
}
}
}
What i need is something like
await collectionEntry.LoadAsync().IgnoreQueryFilters().

Related

How do I update the specific properties in EF Core?

I have been trying to update specific properties in EF Core. But I'm still getting an exception.
I have tried to set the state to detach but I'm getting the same exception.
public async Task UpdateSpecificAsync(TEntity entity, params Expression<Func<TEntity, object>>[] updatedProperties)
{
_context.Entry(entity).State = EntityState.Modified;
if (updatedProperties.Any())
{
foreach (var property in updatedProperties)
{
_context.Entry(entity).Property(property).IsModified = true;
}
}
await _context.SaveChangesAsync();
}
Usage:
await _efUserRepository.UpdateSpecificAsync(userEntity,
x => x.Address, x => x.FirstName, x => x.LanguageId, x => x.LastName, x => x.MiddleName);
Exception:
The instance of entity type 'User' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
Solved With
public async Task UpdateSpecificAsync(TEntity entity, params string[] updatedProperties)
{
var dbentity = Entities.First(x => x.Id == entity.Id);
foreach (var property in updatedProperties)
{
var newValue = entity.GetType().GetProperty(property).GetValue(entity, null);
PropertyInfo propertyInfo = dbentity.GetType().GetProperty(property);
Type t = Nullable.GetUnderlyingType(propertyInfo.PropertyType) ?? propertyInfo.PropertyType;
object safeValue = (newValue == null) ? null : Convert.ChangeType(newValue, t);
propertyInfo.SetValue(dbentity, safeValue, null);
}
await _context.SaveChangesAsync();
}
await _efUserRepository.UpdateSpecificAsync(userModel,
nameof(User.Address), nameof(User.FirstName), nameof(User.LanguageId), nameof(User.LanguageId), nameof(User.MiddleName));
I'm not really happy with it but it does solve the issue.
DbContext should know about your entity and if it is in a Detached state, you must attach the entity to DbContext.
public async Task UpdateSpecificAsync(TEntity entity, params Expression<Func<TEntity, object>>[] updatedProperties)
{
if (_context.Entry(entity).State == EntityState.Detached &&
_context.Set<TEntity>().Local.All(e => e.Id != entity.Id))
{
_context.Attach(entity);
}
if (updatedProperties.Any())
{
foreach (var property in updatedProperties)
{
_context.Entry(entity).Property(property).IsModified = true;
}
}
await _context.SaveChangesAsync();
}
in EF7 don't need to attach the entity !
just set changed properties to to IsModified = true;
public void UpdateSpecificField(TEntity entity, params Expression<Func<TEntity, object>>[] updatedProperties)
{
foreach (var property in updatedProperties)
_context.Entry(entity).Property(property).IsModified = true;
}
for use this method can do :
var oldUser = await _appUow.UserRepo
.Select(x => new GetUserDto { UserId = x.UserId, Name = x.Name })
.FirstOrDefaultAsync(x => x.UserId > 1);
var newUser = new User
{
UserId = oldUser.UserId,
Name = "newName3",
Code = "code3"
};
_appUow.UserRepo.UpdateSpecificField(newUser, x => x.Name, x => x.Code);
await _appUow.SaveChangesAsync();
then this command generate this tsql :
SELECT TOP(1) [u].[UserId], [u].[Name]
FROM [Base].[User] AS [u]
WHERE [u].[UserId] > 1
and
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
UPDATE [Base].[User] SET [Code] = #p0, [Name] = #p1
OUTPUT 1
WHERE [UserId] = #p2;

How get types of entities on ends of association sets in Entity Framework?

I develop method, which resets cache, when Entity Framework is saving changes. I handle this event in objectContext_SavingChanges
private void objectContext_SavingChanges(object sender, EventArgs e)
{
if (_cacheProvider != null)
{
var objectContext = (this as IObjectContextAdapter).ObjectContext;
var entries =
objectContext.ObjectStateManager.GetObjectStateEntries(EntityState.Modified | EntityState.Added);
var result = new List<string>();
if (entries != null && entries.Any())
{
foreach (var entry in entries.Where(entry => !entry.IsRelationship))
{
var entity = entries.Select(r => r.Entity).FirstOrDefault();
if (entity != null)
{
var genericTypeName =
typeof(List<>).MakeGenericType(ObjectContext.GetObjectType(entity.GetType()))?.ToString();
_cacheProvider.ResetCache(genericTypeName);
}
}
foreach (var entry in entries.Where(entry => entry.IsRelationship))
{
var set = entry.EntitySet as AssociationSet;
if(set !=null)
{
var firstEntitySet = set.AssociationSetEnds[0].EntitySet;
var secondEntitySet = set.AssociationSetEnds[1].EntitySet;
var firstEntitySetName = firstEntitySet.ElementType.FullName + "," + Assembly.GetExecutingAssembly().FullName;
var secondEntitySetName = secondEntitySet.ElementType.FullName + "," + Assembly.GetExecutingAssembly().FullName;
var firstGenericTypeName =
typeof(List<>).MakeGenericType((Type.GetType(firstEntitySetName)))?.ToString();
var secondGenericTypeName =
typeof(List<>).MakeGenericType((Type.GetType(secondEntitySetName)))?.ToString();
_cacheProvider.ResetCache(firstGenericTypeName);
_cacheProvider.ResetCache(secondGenericTypeName);
}
}
}
}
}
It works great for entity's entries of object state entries, but does not work for association sets. I need to reset cache for both entities on other sides of relation. AssociationSet has "FullName" property but this property does not contain real name of type. It contains short name of type and assembly name, but not contains full namespace in solution.
For example, type has real full name in solution DataAccess.Entities.CachedEntity, namespace: DataAccess.Entities, short name: CachedEntity. But full name in association set will be DataAccess.CachedEntity.
Can I get full name of type for each end of relation?
I decided this task so:
var clrTypeNamespace = #"http://schemas.microsoft.com/ado/2013/11/edm/customannotation:ClrType";
var firstEdmType = set.AssociationSetEnds[0]?.EntitySet?.ElementType;
var secondEdmType = set.AssociationSetEnds[1]?.EntitySet?.ElementType;
var firstType = firstEdmType?.MetadataProperties.SingleOrDefault(p => p.Name == clrTypeNamespace)?.Value as Type;
var secondType = secondEdmType?.MetadataProperties.SingleOrDefault(p => p.Name == clrTypeNamespace)?.Value as Type;
We can use namespace clrTypeNamespace of CLR type in EDM and find real type of entity in MetadataProperties
Also, we can use navigation properies for each relationship's set. But this solution is sutable for situations, when navigation properties exist on both sides of relationship.
var asem = set.AssociationSetEnds[0].CorrespondingAssociationEndMember;
var propInfo = asem.MetadataProperties.SingleOrDefault(p => p.Name == "ClrPropertyInfo")?.Value as PropertyInfo;

C# mongodb driver 2.0 - How to upsert in a bulk operation?

I migrated from 1.9 to 2.2 and reading the documentation I was surprised to discover that is not possible to upsert during a bulk operation anymore, since operations don't allow options.
bulkOps.Add(new UpdateOneModel<BsonDocument>(filter, update));
collection.BulkWrite(bulkOps);
Should be
options.isUpsert = true;
bulkOps.Add(new UpdateOneModel<BsonDocument>(filter, update, options));
collection.BulkWrite(bulkOps);
Is this work in progress, intended, or I'm missing something? Thank you.
Set the IsUpsert property of the UpdateOneModel to true to turn the update into an upsert.
var bulkOps = new List<WriteModel<BsonDocument>>();
// Create and add one or more write models to list
var upsertOne = new UpdateOneModel<BsonDocument>(filter, update) { IsUpsert = true };
bulkOps.Add(upsertOne);
// Write all changes as a batch
collection.BulkWrite(bulkOps);
given mongo collection
IMongoCollection<T> collection
and enumerable of records to insert where T has Id field.
IEnumerable<T> records
this snippet will do a bulk upsert (the filter condition may be changed according to the situation):
var bulkOps = new List<WriteModel<T>>();
foreach (var record in records)
{
var upsertOne = new ReplaceOneModel<T>(
Builders<T>.Filter.Where(x => x.Id == record.Id),
record)
{ IsUpsert = true };
bulkOps.Add(upsertOne);
}
collection.BulkWrite(bulkOps);
Here is an extension method based on #Aviko response
public static BulkWriteResult<T> BulkUpsert<T>(this IMongoCollection<T> collection, IEnumerable<T> records)
{
string keyname = "_id";
#region Get Primary Key Name
PropertyInfo[] props = typeof(T).GetProperties();
foreach (PropertyInfo prop in props)
{
object[] attrs = prop.GetCustomAttributes(true);
foreach (object attr in attrs)
{
BsonIdAttribute authAttr = attr as BsonIdAttribute;
if (authAttr != null)
{
keyname = prop.Name;
}
}
}
#endregion
var bulkOps = new List<WriteModel<T>>();
foreach (var entry in records)
{
var filter = Builders<T>.Filter.Eq(keyname, entry.GetType().GetProperty(keyname).GetValue(entry, null));
var upsertOne = new ReplaceOneModel<T>(filter, entry){ IsUpsert = true };
bulkOps.Add(upsertOne);
}
return collection.BulkWrite(bulkOps);
}

EF7 - How to get values from DbSet before change

I try to get the value of entity that stored in DbSet before it was changed by code and before it was saved. However, when I try to get it with LINQ Single statement I get the changed value. I'm using EF7.
Here's the code:
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
ent.FirstName = "New name";
Entity entityBeforeChange = dbSet.Single(x => x.Id == id); //here I want to get entity with old values, if that's important I just need to read it without modifying this instance
Context.SaveChanges();
Hope I was clear enough and can get some help
You can grab the original values of any entity from the ChangeTracker.
var original = Context.ChangeTracker.Entries<Entity>().Single(x => x.Id == id);
var firstName = original.Property<string>("FirstName").OriginalValue;
Here is a code I use from my audit library.
EF7
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
context.ChangeTracker.DetectChanges();
// Find your entry or get all changed entries
var changes = context.ChangeTracker.Entries().Where(x => x.State == EntityState.Modified);
foreach (var objectStateEntry in changes)
{
AuditEntityModified(audit, objectStateEntry, auditState);
}
}
public static void AuditEntityModified(Audit audit, AuditEntry entry, EntityEntry objectStateEntry)
{
foreach (var propertyEntry in objectStateEntry.Metadata.GetProperties())
{
var property = objectStateEntry.Property(propertyEntry.Name);
if (entry.Parent.CurrentOrDefaultConfiguration.IsAudited(entry.ObjectStateEntry, propertyEntry.Name))
{
entry.Properties.Add(new AuditEntryProperty(entry, propertyEntry.Name, property.OriginalValue, property.CurrentValue));
}
}
}
EF6
using (var ctx = new TestContext())
{
Entity ent = entity.Single(x => x.Id == id);
entity.FirstName = "New name";
var entry = ((IObjectContextAdapter)ctx).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity);
var currentValues = entry.CurrentValues;
var originalValues = entry.OriginalValues;
AuditEntityModified(originalValues, currentValues);
}
public static void AuditEntityModified(DbDataRecord orginalRecord, DbUpdatableDataRecord currentRecord, string prefix = "")
{
for (var i = 0; i < orginalRecord.FieldCount; i++)
{
var name = orginalRecord.GetName(i);
var originalValue = orginalRecord.GetValue(i);
var currentValue = currentRecord.GetValue(i);
var valueRecord = originalValue as DbDataRecord;
if (valueRecord != null)
{
// Complex Type
AuditEntityModified(valueRecord, currentValue as DbUpdatableDataRecord, string.Concat(prefix, name, "."));
}
else
{
if (!Equals(currentValue, originalValue))
{
// Add modified values
}
}
}
}
Edit:
The complete source code can be found in my GitHub repository: https://github.com/zzzprojects/EntityFramework-Plus
Library Website: http://entityframework-plus.net/
You can Detach an entity from the context. Keep in mind that you'll have to pull it from the context before you update the other, attached entity.
DbSet<Entity> dbSet = Context.dbSet;
Entity ent = dbSet.Single(x => x.Id == id);
Entity entityBeforeChange = dbSet.Single(x => x.Id == id);
Context.Entry(entityBeforeChange).State = EntityState.Detached; // severs the connection to the Context
ent.FirstName = "New name";
Context.SaveChanges();
You could use a new DbContext since the loaded entity is cached in the one you already have.
Entity oldUnchanged;
using (var ctx = new YourDbContext())
{
oldUnchanged = ctx.Set<Entity>().Single(x => x.Id == id);
}

AWS API: how to cast dynamo result as class

So I have a model repository that utilizes the C# AWS SDK for Dynamo. Right now it's a bit ugly. What I'd like is to cast-out result items to my model. Going into Dynamo it's great. I just do some type reflection on my Poco classes and shove them in like so:
var doc = new Document();
foreach (PropertyInfo prop in model.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
var propName = (string)prop.Name;
// dont add if value is null
if (prop.GetValue(model, null) != null)
{
if (prop.PropertyType == typeof(string))
doc[propName] = (string)prop.GetValue(model, null);
if (prop.PropertyType == typeof(List<string>))
doc[propName] = (List<string>)prop.GetValue(model, null);
if (prop.PropertyType == typeof(float))
doc[propName] = (float)prop.GetValue(model, null);
}
}
But here in the repo, I'd like to not have to write this ugly manual cast when retrieving items. Is there a AWS helper to make this less manual? I guess I could write the inverse of the above loop and get the attribute property names then test for null on each N, S, SS type etc.
var request = new ScanRequest
{
TableName = TableName.User,
};
var response = client.Scan(request);
var collection = (from item in response.ScanResult.Items
from att in item
select new User(att.Value.S, att.Value.N, att.Value.S, att.Value.N, att.Value.S, att.Value.S, att.Value.S, att.Value.S, att.Value.S,
att.Value.S, att.Value.S, att.Value.S, att.Value.S, att.Value.SS, att.Value.SS)).ToList();
return collection.AsQueryable();
You can use the built-in FromDocument method to convert the Dictionary<string, AttributeValue> to your class of type T
List<MyClass> result = new List<MyClass>();
var response = await client.QueryAsync(request);
foreach (Dictionary<string, AttributeValue> item in response.Items)
{
var doc = Document.FromAttributeMap(item);
var typedDoc = context.FromDocument<MyClass>(doc);
result.Add(typedDoc);
}
You can use the Object Persistence Model feature of the .NET SDK. This allows you to annotate your .NET objects with attributes that then direct the SDK how that data should be stored in DynamoDB.
I ended up doing it the LOOOOOONG way. It was kind of fun to use type reflection to create my own casting function. Would have been more fun if it weren't at 2 AM.
The Pavel's answer is more official, but this still works like a charm.
public static T ResultItemToClass<T>(Dictionary<string, AttributeValue> resultItem) where T : new()
{
var resultDictionary = new Dictionary<string, object>();
Type type = typeof(T);
T ret = new T();
foreach (KeyValuePair<string, AttributeValue> p in resultItem)
if (p.Value != null)
foreach (PropertyInfo prop in p.Value.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
if (prop.GetValue(p.Value, null) != null)
{
if (prop.Name == "S")
type.GetProperty(p.Key).SetValue(ret, prop.GetValue(p.Value, null), null);
if (prop.Name == "SS")
type.GetProperty(p.Key).SetValue(ret, (List<string>)prop.GetValue(p.Value, null), null);
if (prop.Name == "N")
type.GetProperty(p.Key).SetValue(ret, Convert.ToInt32(prop.GetValue(p.Value, null)), null);
// TODO: add some other types. Too tired tonight
}
return ret;
}
From the aws Document result class, you can use the instance method: .ToJson(), then deserialise into your class.
Generic method convert Dynamo table to c# class as an extension function.
public static List<T> ToMap<T>(this List<Document> item)
{
List<T> model = (List<T>)Activator.CreateInstance(typeof(List<T>));
foreach (Document doc in item)
{
T m = (T)Activator.CreateInstance(typeof(T));
var propTypes = m.GetType();
foreach (var attribute in doc.GetAttributeNames())
{
var property = doc[attribute];
if (property is Primitive)
{
var properties = propTypes.GetProperty(attribute);
if (properties != null)
{
var value = (Primitive)property;
if (value.Type == DynamoDBEntryType.String)
{
properties.SetValue(m, Convert.ToString(value.AsPrimitive().Value));
}
else if (value.Type == DynamoDBEntryType.Numeric)
{
properties.SetValue(m, Convert.ToInt32(value.AsPrimitive().Value));
}
}
}
else if (property is DynamoDBBool)
{
var booleanProperty = propTypes.GetProperty(attribute);
if (booleanProperty != null)
booleanProperty.SetValue(m, property.AsBoolean());
}
}
model.Add(m);
}
return model;
}

Categories