How to set only selected properties using .NET DbPropertyValues.SetValues? - c#

I am trying to write some code that allows me to update a detached entity using the Entity Framework.
So far, the code looks like this:
public virtual void UpdateUnattached(T entity, string lookupPropertyName, string primaryKeyPropertyName)
{
if (entity == null)
{
throw new ArgumentException("Cannot update a null entity.");
}
// Get the data entry associated with the unattached entity from the context.
var entry = DataContext.Entry<T>(entity);
if (entry.State == EntityState.Detached)
{
// Get the already attached entity by the lookup property (which can be different from the primary key).
var attachedEntity = this.dbSet.Local.SingleOrDefault(
e => (int)ObjectUtil.GetPropertyValue(e, lookupPropertyName) == (int)ObjectUtil.GetPropertyValue(entity, lookupPropertyName)
);
// Get the value of the primary key for the attached entity.
var primaryKeyValue = ObjectUtil.GetPropertyValue(attachedEntity, primaryKeyPropertyName);
// Set the primary key of the unattached entity.
ObjectUtil.SetPropertyValue(entity, primaryKeyPropertyName, primaryKeyValue);
if (attachedEntity != null)
{
// Get the entry associated with the attached entity from the context and set the values of the unattached entity to be updated.
var attachedEntry = DataContext.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{
entry.State = EntityState.Modified;
}
}
}
On the attachedEntry.CurrentValues.SetValues(entity); line I would like to set the values for some properties and skip others. This would allow me to make this method more generic by passing the names of the properties I don't want to be updated.
Does anyone know if this is possible? The SetValues method has one other overload that accepts a DbPropertyValues object but I can't find a way to build this object without the properties I don't want to update.

Current values will set all scalar properties.
If you want to have custom mapping, you can use reflection.
foreach (var name in propertyNames)
{
var value = entity.GetType().GetProperty(name).GetValue(entity, null);
attachedEntity.GetType().GetProperty(name).SetValue(attachedEntity, value);
}

Thanks.
I was already on the way to try and use reflection... I ended up replacing the attachedEntry.CurrentValues.SetValues(entity); call with SetAttachedEntityValues(attachedEntity, entity, new string[] { "Payout", "Client", "Country" }); which calls a method that copies all properties except the ones specified on the array:
private void SetAttachedEntityValues(T attachedEntity, T entity, string[] excludePropertyNames)
{
var properties = typeof(T).GetProperties().Where(x => !excludePropertyNames.Contains(x.Name)).ToList();
foreach(var property in properties)
{
var propertyValue = ObjectUtil.GetPropertyValue(entity, property.Name);
ObjectUtil.SetPropertyValue(attachedEntity, property.Name, propertyValue);
}
}
ObjectUtil is a class that has methods that do pretty much what Yuliam Chandra suggested.

Related

Entity Framework override default property convention to ignore properties

I am rewriting a legacy system to use Entity Framework. The old system had entities where half of the properties were mapped to DB columns and the other half not. To indicate that a property had to be mapped, the property was decorated with a [Field] attribute. All other properties were ignored.
This is the opposite of what EF does. By convention, EF maps all public properties with a getter and setter to a DB field unless the property is decorated with [NotMapped] data annotation or Ignore is called on for that property using the fluent API on model creating.
I want to override the EF convention to work as the old system. ie Ignore properties that do not have the FieldAttribute. I know that this could be done by adding [NotMapped] to all the properties, but I'm looking for a way to do this dynamically so that i don't have to change every single entity (there are hundreds)
There's not system convention to remove or override for this that i can see
https://msdn.microsoft.com/en-us/library/system.data.entity.modelconfiguration.conventions.aspx
I've tried the following code to call ignore using reflection with no luck :
modelBuilder.Properties().Configure((configuration) =>
{
var attributes = configuration.ClrPropertyInfo.GetCustomAttributes(inherit: false);
var fieldAttribute = attributes.FirstOrDefault(x => x.GetType() == typeof(FieldAttribute) || x.GetType() == typeof(KeyAttribute));
if (fieldAttribute == null)
{
var entityMethod = modelBuilder.GetType().GetMethod("Entity");
var entityConfiguration = entityMethod.MakeGenericMethod(configuration.ClrPropertyInfo.ReflectedType).Invoke(modelBuilder, new object[] { });
MethodInfo ignoreMethod = entityConfiguration.GetType()
.GetMethod("Ignore")
.MakeGenericMethod(configuration.ClrPropertyInfo.PropertyType);
var parameter = Expression.Parameter(configuration.ClrPropertyInfo.ReflectedType);
var memberExpression = Expression.Property(parameter, configuration.ClrPropertyInfo.Name);
var lambdaExpression = Expression.Lambda(memberExpression, parameter);
ignoreMethod.Invoke(entityConfiguration, new[] { lambdaExpression });
}
});
This looks like it works, as the property is added to the ignore list of the entity configuration. But EF still tries to map the property to a non existent DB field and throws an Invalid column exception.
Does anyone have any other ideas?
I found a solution to the problem. If i come at this from the TypeConventionConfiguration instead of the PropertyConventionConfiguration it works. I probably had some bug in my code above. This way i need to use less reflection...
modelBuilder.Types().Configure((entityConfiguration) =>
{
const BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly;
foreach (var propertyInfo in entityConfiguration.ClrType.GetProperties(bindingFlags))
{
var attributes = propertyInfo.GetCustomAttributes(inherit: false);
var fieldAttribute = attributes.FirstOrDefault(x => x.GetType() == typeof(FieldAttribute) || x.GetType() == typeof(KeyAttribute));
if (fieldAttribute == null)
{
entityConfiguration.Ignore(propertyInfo);
}
}
});

How to know whether a property stored in DbEntityEntry is a property`?

In Entity Framework 6 within an DbEntityEntry certain information can be retrieved by calling Property.
However, this fails with an ArgumentException when the property is not a property but a collection or reference. Than other functions must be used.
How can I know which function to call? That is, how can I know of what type (simple property, complex property, reference, collection) the property is?
For DbEntityEntry see https://msdn.microsoft.com/en-us/library/system.data.entity.infrastructure.dbentityentry%28v=vs.113%29.aspx
I am using Entity Framework 6.1.3 in Visual Studio 2013.
DbEntityEntry.Member(string) returns a DbMemberEntry, which you can check with (memberEntry is DbPropertyEntry).
I have find how to get if the navigation property is collection type or not.
For this, we need to get BuiltInTypeKind of the property.
I am using this code for getting all navigation properties of the entity:
var entitySetElementType = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<TEntity>().EntitySet.ElementType;
var navProperties = entitySetElementType.NavigationProperties;
Then, we can know if th navigation property is collection or not:
foreach (var navigationProperty in entiySetElementType.NavigationProperties)
{
var builtInType = navigationProperty.TypeUsage.EdmType.BuiltInTypeKind;
var isCollection = builtInType == System.Data.Metadata.Edm.BuiltInTypeKind.CollectionKind
|| builtInType == System.Data.Metadata.Edm.BuiltInTypeKind.CollectionType;
}
UPDATE
Since EF has been moved to a separate assembly & namespace, the System.Data.Metadata.Edm.BuiltInTypeKind.CollectionKind in above code should be changed to System.Data.Entity.Core.Metadata.Edm.BuiltInTypeKind.CollectionKind.
One of the steps is to dig through those dynamic proxies. I do it with:
if ( targetType.BaseType != null
&& targetType.Namespace == "System.Data.Entity.DynamicProxies" )
{
targetType = targetType.BaseType;
}
Not really clean, but does the job.
Update
Based on jjj's answer, I came up with the following method:
private bool IsSimpleProperty( string propertyName, DbEntityEntry entry )
{
DbMemberEntry memberEntry = entry.Member( propertyName );
return memberEntry is DbPropertyEntry;
}
By varying the is expression, you could check for all types.

Entity Framework: Check all relationships of an entity for foreign key use

I have an entity, let's call it CommonEntity that has a primary key used as a foreign key in many other entities. As the application is developed these links will continue to grow.
I'd like a way to see if CommonEntity can be safely deleted (i.e. it's not used by any other entities).
I realise I can do
if(!ce.EntityA.Any() && !ce.EntityB.Any() ... && !ce.EntityN.Any())
{
//Delete
}
but I'm hoping for a way to just check all of the relationships automatically, as I don't love the idea of having to come back and change this code manually every time we add a new relationship. Perhaps there is something in EF4+ that I'm not aware of?
I thought it might be possible to use a transaction scope to just try and delete the object and roll it back if it fails, but I wasn't sure if there were any adverse side effects with this approach.
Is there a better approach?
EDIT: Looks like VS2012 has used EF5 even though the project is .Net 4, so it has created the model with POCOs even though it was generated from a DB.
Just let it fail. If the entity has many relationships, that verification could be really heavy.
public bool TryDelete(int id)
{
try
{
// Delete
return true;
}
catch (SqlException ex)
{
if (ex.Number == 547) return false; // The {...} statement conflicted with the {...} constraint {...}
throw; // other error
}
}
You can use Reflection for this (if you don't want use "Fail Delete On SQL")
I write this because I dont want to DELETE Entity, just want to know if its related to any or not !
public 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);
}
public 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;
}
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;
}
NOTE that : Context must not Disposed and Proxy Must Be Enabled
AND KNOW THAT IT WILL GET ALL RELATED RECORD TO MEMORY (IT'S Too Heavy)
You can try this:
var allrelatedEnds = ((IEntityWithRelationships)ce).RelationshipManager.GetAllRelatedEnds();
bool hasRelation = false;
foreach (var relatedEnd in allrelatedEnds)
{
if (relatedEnd.GetEnumerator().MoveNext())
{
hasRelation = true;
break;
}
}
if (!hasRelation)
{
//Delete
}
First find the entity which you want to delete using Find in EF and pass the entity to below function.. If the function returns true it means cannot be deleted and foreign data exists.. If function returns false it means no parent or child records and can be delete..
public static bool DeleteCheckOnEntity(object entity)
{
var propertiesList = entity.GetType().GetProperties();
return (from prop in propertiesList where prop.PropertyType.IsGenericType select prop.GetValue(entity) into propValue select propValue as IList).All(propList => propList == null || propList.Count <= 0);
}

How to get ObjectSet<T>'s entity key name?

I've created a generic ObjectSet<T> in my generic repository.
What I would like to get is the name of the EntityKey of ObjectSet<T> so that I can use it in the DataContext.GetObjectByKey.
I've searched around and dug deep, but I can't seem to find this value anywhere in the ObjectSet class.
I looked a while ago for a nice way to do this and failed to find one. I generally end up building a GetEntityByKey extension method somewhere and within that, contatenating strings to build Entity Keys for TryGetObjectByKey calls. The general idea for building the entity key goes something like this:
internal class Program
{
private static void Main(string[] args)
{
var dc = new AdventureWorksLT2008Entities();
object c;
dc.TryGetObjectByKey(GetEntityKey(dc.Customers, 23), out c);
var customer = c as Customer;
Console.WriteLine(customer.EmailAddress);
}
private static EntityKey GetEntityKey<T>(ObjectSet<T> objectSet, object keyValue) where T : class
{
var entitySetName = objectSet.Context.DefaultContainerName + "." + objectSet.EntitySet.Name;
var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
var entityKey = new EntityKey(entitySetName, new[] {new EntityKeyMember(keyPropertyName, keyValue)});
return entityKey;
}
}
You may be able to do something similar. This example assumes a single field per EntityKey for simplicity - for multiple value keys you would need to do something slightly more sophisticated with ObjectSet<T>.ElementType.KeyMembers and pass all your keys into the EntityKey constructor.
Generic:
public class GenericoRepositorio<T> : IGenericoRepositorio<T> where T : class
{
protected readonly ObjectSet<T> ObjetoSet;
protected readonly ModeloContainer Contexto;
public GenericoRepositorio(ModeloContainer contexto)
{
Contexto = contexto;
ObjetoSet = Contexto.CreateObjectSet<T>();
}
public T Carregar(int id)
{
object objeto;
Contexto.TryGetObjectByKey(GetEntityKey(ObjetoSet, id), out objeto);
return (T)objeto;
}
private static EntityKey GetEntityKey<T>(ObjectSet<T> objectSet, object keyValue) where T : class
{
var entitySetName = objectSet.Context.DefaultContainerName + "." + objectSet.EntitySet.Name;
var keyPropertyName = objectSet.EntitySet.ElementType.KeyMembers[0].ToString();
var entityKey = new EntityKey(entitySetName, new[] { new EntityKeyMember(keyPropertyName, keyValue) });
return entityKey;
}
}
See this post that I made regarding getting the EntitySetName. For my repository, I create a property that gets the entity set name for the specific class name to do exactly what you are trying to do.
This should give you all the generic arguments (the types) for the ObjectSet:
objectSet.GetType().GetGenericArguments().First()
I had a tough time trying to do almost the same thing, getting the primary key name and value at runtime when the type is unknown. I was just get trying to implement an auditing scheme for deletes, and every solution i find involves superfluous code that I dont really understand. The EntityKey is not available from a DbContext, which is also confusing and annoying. The last 5 lines may save you 5 hours and 1 yr of baldness. I am not attempting this for Inserts, so if you do, you need to inspect those values carefully as they may be 0 or null.
foreach(var entry in ChangeTracker.Entries<IAuditable>())
{
...
case EntityState.Deleted:
var oc = ((IObjectContextAdapter)this).ObjectContext; //this is a DbContext
EntityKey ek = oc.ObjectStateManager.GetObjectStateEntry(entry.Entity).EntityKey;
var tablename = ek.EntitySetName;
var primaryKeyField = ek.EntityKeyValues[0].Key; //assumes only 1 primary key
var primaryKeyValue = ek.EntityKeyValues[0].Value;
var objContext = ((IObjectContextAdapter)this.context).ObjectContext;
var objSet = objContext.CreateObjectSet<T>();
var entityKey = objContext.CreateEntityKey(objSet.EntitySet.Name, entityToUpdate);
Object foundEntity;
var exits = objContext.TryGetObjectByKey(entityKey, out foundEntity);
if (exits && this.dbset.Local != null && this.dbset.Local.Contains(foundEntity) &&this.dbset.Local.Any())
{
if (entityKey.EntityKeyValues != null && entityKey.EntityKeyValues.Any())
{
DbEntityEntry<T> entry = this.context.Entry(this.dbset.Find(entityKey.EntityKeyValues.FirstOrDefault().Value));
entry.CurrentValues.SetValues(entityToUpdate);
}
}
this.context.SaveChanges();
Tested with EF 6.
It will return an array of objects for each primary key value for the given DbEntityEntry.
Their maybe edge cases where this does not work - but for my simple needs works great.
Hope this helps someone else.
object[] GetPrimaryKeyValue(DbEntityEntry entry)
{
List<object> key = new List<object>();
var objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity);
if (objectStateEntry.EntityKey.EntityKeyValues != null && objectStateEntry.EntityKey.EntityKeyValues.Length==1)
{
key.Add(objectStateEntry.EntityKey.EntityKeyValues[0].Value);
}
else
{
if (objectStateEntry.EntitySet.ElementType.KeyMembers.Any())
{
foreach (var keyMember in objectStateEntry.EntitySet.ElementType.KeyMembers)
{
if (entry.CurrentValues.PropertyNames.Contains(keyMember.Name))
{
var memberValue = entry.CurrentValues[keyMember.Name];
if (memberValue != null)
{
key.Add(memberValue);
}
}
}
}
}
return key.ToArray();
}

Can you convince a DataContext to treat a column as always dirty?

Is there a way to force LINQ-to-SQL to treat a column as dirty? Globally would suffice....
Basically, I've got a problem with some audit code on a legacy system that I'm talking to with L2S, imagine:
var ctx = new SomeDataContext(); // disposed etc - keeping it simple for illustration
var cust = ctx.Customers.First(); // just for illustration
cust.SomeRandomProperty = 17; // whatever
cust.LastUpdated = DateTime.UtcNowl;
cust.UpdatedBy = currentUser;
ctx.SubmitChanges(); // uses auto-generated TSQL
This is fine, but if the same user updates it twice in a row, the UpdatedBy is a NOP, and the TSQL will be (roughly):
UPDATE [dbo].[Customers]
SET SomeRandomColumn = #p0 , LastUpdated = #p1 -- note no UpdatedBy
WHERE Id = #p2 AND Version = #p3
In my case, the problem is that there is currently a belt-and-braces audit trigger on all tables, which checks to see if the audit column has been updated, and if not assumes the developer is at fault (substituting SUSER_SNAME(), although it could just as readily throw an error).
What I'd really like to be able to do is say "always update this column, even if it isn't dirty" - is this possible?
Based on KristoferA's answer, I ended up with something like below; this is evil and brittle (reflection often is), but may have to suffice for now. The other side of the battle is to change the triggers to behave:
partial class MyDataContext // or a base-class
{
public override void SubmitChanges(System.Data.Linq.ConflictMode failureMode)
{
this.MakeUpdatesDirty("UpdatedBy", "Updated_By");
base.SubmitChanges(failureMode);
}
}
public static class DataContextExtensions
{
public static void MakeUpdatesDirty(
this DataContext dataContext,
params string[] members)
{
if (dataContext == null) throw new ArgumentNullException("dataContext");
if (members == null) throw new ArgumentNullException("members");
if (members.Length == 0) return; // nothing to do
foreach (object instance in dataContext.GetChangeSet().Updates)
{
MakeDirty(dataContext, instance, members);
}
}
public static void MakeDirty(
this DataContext dataContext, object instance ,
params string[] members)
{
if (dataContext == null) throw new ArgumentNullException("dataContext");
if (instance == null) throw new ArgumentNullException("instance");
if (members == null) throw new ArgumentNullException("members");
if (members.Length == 0) return; // nothing to do
const BindingFlags AllInstance = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
object commonDataServices = typeof(DataContext)
.GetField("services", AllInstance)
.GetValue(dataContext);
object changeTracker = commonDataServices.GetType()
.GetProperty("ChangeTracker", AllInstance)
.GetValue(commonDataServices, null);
object trackedObject = changeTracker.GetType()
.GetMethod("GetTrackedObject", AllInstance)
.Invoke(changeTracker, new object[] { instance });
var memberCache = trackedObject.GetType()
.GetField("dirtyMemberCache", AllInstance)
.GetValue(trackedObject) as BitArray;
var entityType = instance.GetType();
var metaType = dataContext.Mapping.GetMetaType(entityType);
for(int i = 0 ; i < members.Length ; i++) {
var member = entityType.GetMember(members[i], AllInstance);
if(member != null && member.Length == 1) {
var metaMember = metaType.GetDataMember(member[0]);
if (metaMember != null)
{
memberCache.Set(metaMember.Ordinal, true);
}
}
}
}
}
Unfortunately, I think you will have to use a new DataContext
Details at: http://blog.benhall.me.uk/2008/01/custom-insert-logic-with-linq-to-sql.html
You can override the default update behavior. There are 2 ways of doing this
The easiest is to create a stored procedure (if you can't do that on your database, the second method should work) which takes the parameters of your customer object and updates the table:
Create the stored procedure that has a parameter for each property of Customers that needs to be updated.
Import that stored procedure into your Linq To SQL DBML file.
Now you can right click on your customers entity and select "Configure Behavior".
Select your Customers class under the Class dropdown and "Update" on the behavior drop down.
Select the "Customize" radio button and choose the stored procedure you just created.
Now you can map class's properties to the stored procedure.
Now when Linq to SQL tries to update your Customers table, it'll use your stored procedure instead. Just be careful because this will override the update behavior for Customers everywhere.
The second method is to use partial methods. I haven't actually tried this, so hopefully this might just give you some general direction to pursue. In a partial class for your data context, make a partial method for the update (It'll be Update_____ with whatever your class is in the blank. I'd suggest searching in your data context's designer file to make sure you get the right one)
public partial SomeDataContext
{
partial void UpdateCustomer(Customer instance)
{
// this is where you'd do the update, but I'm not sure exactly how it's suppose to work, though. :(
}
}
If you want to go down the [dirty] reflection route, you could try something along the lines of:
1) Override SubmitChanges
2) Go through the change set
3) Use reflection to get hold of the change tracker for each updated object (see What's the cleanest way to make a Linq object "dirty"? )
4) Make the column dirty (there's a dirtyMemberCache field in the StandardTrackedObject class)
The following works for me. Note though that I'm using the linq2sql provider from DevArt, but that may not matter:
MyDataContext dc = new MyDataContext();
Message msg = dc.Messages.Single(m => m.Id == 1);
Message attachingMsg = new Message();
attachingMsg.Id = msg.Id;
dc.Messages.Attach(attachingMsg);
attachingMsg.MessageSubject = msg.MessageSubject + " is now changed"; // changed
attachingMsg.MessageBody = msg.MessageBody; // not changed
dc.SubmitChanges();
This produces the following sql:
UPDATE messages SET messageSubject = :p1, messageBody = :p2 WHERE Id = :key1
So, messageBody is updated even though its value is not changed.
One other change necessary for this, is that for each property (column) of my entity Message, I have set UpdatedCheck = UpdateCheck.Never, except for its ID, which is the primary key.

Categories