Entity Framework override default property convention to ignore properties - c#

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

Related

Determine if property is a Navigation Property in EF Core

I'm building a simple change tracker to capture all the edits to a Sql Azure database (unfortunately, Sql Azure doesn't support this natively, so far as I can tell).
I'm walking the list of modified entries returned by ChangeTracker():
foreach( EntityEntry entry in _context.ChangeTracker.Entries()
.Where( e => e.State == EntityState.Modified ) )
{
foreach( var prop in entry.Entity
.GetType()
.GetTypeInfo()
.DeclaredProperties )
{
// this line blows up on navigation properties
PropertyEntry propEntry = entry.Property( prop.Name );
if( propEntry.IsModified )
{
var curValue = entry.Property( prop.Name ).CurrentValue;
var origValue = entry.Property( prop.Name ).OriginalValue;
}
}
}
Unfortunately, retrieving the PropertyEntry info for a property blows up -- InvalidOperationException -- when the property is a navigation property, claiming the property can't be found.
I could just wrap the code in an try/catch block...but I'm curious if there's another way to determine, perhaps from metadata, that a property is a navigation or related property.
Rather than using reflection
foreach (var prop in entry.Entity.GetType().GetTypeInfo().DeclaredProperties)
you can use the metadata provided by the EntityEntry.Metadata property:
foreach (var prop in entry.Metadata.GetProperties())
Note that the IEntityType returned by the Metadata property has separate methods for simple properties (GetProperties method) and navigation properties (GetNavigations extension method).

How to configure property in Entity Framework with Type and PropertyInfo

If I had a simple class like this
class Person
{
public int Id { get; set; }
// ...
}
and I wanted "do something" in EF OnModelCreating to the Id property, such as:
modelBuilder.Entity<Person>().Property( _p => _p.Id ).HasDatabaseGeneratedOption( DatabaseGeneratedOption.None );
I have no problem. However, when I have the Type and Property:
var entityType = typeof(Person);
var propInfo = entityType.GetProperty("Id");
I want a function such as
ModelEntityProperty( modelBuilder, propertyType, entityType).HasDatabaseGeneratedOption( DatabaseGeneratedOption.None );
My question: Does EntityFramework allow one to configure entities/properties using reflection information? Or is it exclusively using these LambdaExpressions?
I ended up writing this function, but it is long and in my opinion much uglier than what may be available:
private PrimitivePropertyConfiguration ModelEntityProperty(
DbModelBuilder p_model,
PropertyInfo p_propInfo,
Type p_entityType = null )
{
// If the entityType was not set, then use the property's declaring type
var entityType = (p_entityType == null) ? p_propInfo.DeclaringType : p_entityType;
// Get the Entity <> method- a generic method
var genericEntityMethod = typeof( DbModelBuilder ).GetMethod( "Entity", new Type[0] );
// Get the actual method for the Type we're interested in
var entityMethod = genericEntityMethod.MakeGenericMethod( new Type[] { entityType } );
// get the return value of .Entity{p_type}()
var theEntityConfigurator = entityMethod.Invoke( p_model, new object[0] );
// I really don't like this, but it works (for now, until they change something)
var propMethod = theEntityConfigurator
.GetType()
.GetMethods( BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public )
.Where( _mi => _mi.Name == "Property" && _mi.IsGenericMethod )
.First()
.MakeGenericMethod( p_propInfo.PropertyType );
// That whole ugly mess should have been a GetMethod call, but I don't know how
// to set the parameter type to make sure the correct version of the method is
// returned unambiguously
// Build the expression that will be used to identify the property
var paramExpr = Expression.Parameter( entityType );
var memberExpr = Expression.MakeMemberAccess( paramExpr, p_propInfo );
var lambdaExpr = Expression.Lambda( memberExpr, paramExpr );
// Invoke the correct version of the Property method with the correct parameter
var thePropertyConfiguration = propMethod.Invoke(
theEntityConfigurator,
new object[] { lambdaExpr } );
// and return that thing
return thePropertyConfiguration as PrimitivePropertyConfiguration;
}
This function works perfectly for this example, but it needs help to be more general (e.g. DateTimes, etc. don't work). Is there a better or more elegant way of doing this "natively" within EF? Or is this an appropriate method, assuming its fixed for the various ValueTypes that the "Property" method can handle?

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.

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

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.

Mapping from IEdmEntity to CLR

I'm trying to find a way to go from an IEdmEntity to the CLR Type in entity framework. From the casting to ObjectContext to get Metadata. I'm using the DataSpace.OCSpace to get access to the mapping. I believe that is correct but I might have the wrong DataSpace, the DataSpaces are not clear in my head of which does what, even after this blog http://blogs.msdn.com/b/alexj/archive/2009/04/03/tip-10-understanding-entity-framework-jargon.aspx.
In the end I get back System.Data.Entity.Core.Mapping.MappingBase objects which doesn't do much for me. From the debugger it seems I could get access to what I want but those classes are marked internal and I can't cast to them.
Am I making this too hard or is there no way to go from an IEdmModel from Entity Framework back to the CLR Types it maps to?
Adding code to try and make it more clear what I'm working with and trying to get out
public Type GetIEdmEntityTypeToClrType(IEdmEntityTypeReference edmEntityType, DbContext context)
{
var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
var fullname = edmEntityType.EntityDefinition().FullName();
EntityType entityType;
if (metadata.TryGetItem(fullname, DataSpace.CSSpace, out entityType))
{
//doesn't hit
}
if (metadata.TryGetItem(fullname, DataSpace.CSpace, out entityType))
{
//hits but can't get access to CLR Type that it's mapped too.
}
if (metadata.TryGetItem(fullname, DataSpace.OCSpace, out entityType))
{
//doesn't hit
}
if (metadata.TryGetItem(fullname, DataSpace.OSpace, out entityType))
{
//doesn't hit
}
if (metadata.TryGetItem(fullname, DataSpace.SSpace, out entityType))
{
//doesn't hit
}
return null;
}
The *IEdm** interfaces you mentioned in both your question and answer are not used by Entity Framework per se (the EF6 NuGet package has no Microsoft.Data.Edm dependency), but are primarily used with OData service metadata (CSDL). Since entities declared in OData CSDL don't necessarily map to any particular CLR classes, you can only find their CLR types indirectly. (I think that confusion is why Andrew's EF-only answer assumed you had access to an EntityObject.)
Fortunately, when presenting EF entities via OData, there's normally a 1:1 correspondence between the full names of the entities in the CSDL of both the OData service and EF model. Assuming that's the case, your can search using edmEntityType.FullName as you did above, but you have to get the corresponding EF EntityType from the ObjectContext metadata first.
DataSpace.OCSpace in MetadataWorkspace was a reasonable place to look for the mapping, since that's where the Object Space <-> Conceptual Space mappings are stored. But as you discovered, while EF6's mapping API is supposedly public, ObjectTypeMapping and its related classes are still marked internal :(
However, it turns out that you don't need to do any ugly reflection hacks with the internal OCSpace classes! You can get the mapped CLR type directly from your 'hit' in CSpace like this:
var clrTypeMetadataPropName = #"http://schemas.microsoft.com/ado/2013/11/edm/customannotation:ClrType";
var clrType = (Type)
((IObjectContextAdapter)context).ObjectContext
.MetadataWorkspace
.GetItems<EntityType>(DataSpace.CSpace)
.Single(s => s.FullName == edmEntityType.FullName())
.MetadataProperties
.Single(p => p.Name == clrTypeMetadataPropName )
.Value;
Sure, it uses the 'internal' ClrType custom annotation key magic string, but everything is done through the current public API. I think that's as close as you can get to an 'official' solution until/unless the rest of the mapping API is made public.
This should work for entity and property types.
public static Type GetClrTypeFromCSpaceType(
this MetadataWorkspace workspace, EdmType cType)
{
var itemCollection = (ObjectItemCollection)workspace.GetItemCollection(DataSpace.OSpace);
if (cType is StructuralType) {
var osType = workspace.GetObjectSpaceType((StructuralType)cType);
return itemCollection.GetClrType(osType);
} else if (cType is EnumType) {
var osType = workspace.GetObjectSpaceType((EnumType)cType);
return itemCollection.GetClrType(osType);
} else if (cType is PrimitiveType) {
return ((PrimitiveType)cType).ClrEquivalentType;
} else if (cType is CollectionType) {
return workspace.GetClrTypeFromCSpaceType(((CollectionType)cType).TypeUsage.EdmType);
} else if (cType is RefType) {
return workspace.GetClrTypeFromCSpaceType(((RefType)cType).ElementType);
} else if (cType is EdmFunction) {
return workspace.GetClrTypeFromCSpaceType(((EdmFunction)cType).ReturnParameter.TypeUsage.EdmType);
}
return null;
}
usage
var entity = workspace.GetItems<EntityType>(DataSpace.CSpace).First();
var entityType = workspace.GetClrTypeFromCSpaceType(entity);
var propertyType = workspace.GetClrTypeFromCSpaceType(entity.Properties[0].TypeUsage.EdmType);
I assume you are using Entity Framework 6, where Mapping API is not public.
Please have a look at new release of Entity Framework 6.1 RTM:
http://blogs.msdn.com/b/adonet/archive/2014/03/17/ef6-1-0-rtm-available.aspx
More specifically at the Public Mapping API feature:
https://entityframework.codeplex.com/wikipage?title=Public%20Mapping%20API
You should play with metadataWorkspace to get information about entity framework types and their mapping, for example all simple properties of your entity and their CLR types can be retrieved like this:
EntityObject entity = null; //your entity
MetadataWorkspace metadataWorkspace = dataContext.MetadataWorkspace;
Type currentEntityType = entity.GetType();
EntityType entityType = metadataWorkspace.GetItem<EntityType>(currentEntityType.FullName, DataSpace.OSpace);
var simpleProperties = entityType.Properties.Where(p => p.DeclaringType == entityType && p.TypeUsage.EdmType is SimpleType);
foreach (EdmProperty simpleProperty in simpleProperties)
{
Console.WriteLine(string.Format("Name: {0} Type: {1}", simpleProperty.Name,simpleProperty.TypeUsage));
}
Here's what I have that works from my limited testing but really seems like a hack. Hoping someone else finds something better.
public Type ConvertIEdmEntityTypeToClr(IEdmEntityType edmEntityType, DbContext context)
{
var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;
var oSpace = metadata.GetItemCollection(DataSpace.OSpace);
var typeName = oSpace.GetItems<EntityType>().Select(e => e.FullName).FirstOrDefault(name =>
{
var fullname = name + ":" + edmEntityType.FullName();
MappingBase map;
return metadata.TryGetItem(fullname, DataSpace.OCSpace, out map);
});
return Type.GetType(typeName, false);
}
Assumes that the OSpace Identity is the same as the CLR name. Also assumes that ID for the OCSpace is the two put together separated by a :.

Categories