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 :.
Related
My Problem is this A have a API controler And creating a Single method delete to data from X DbSet properties but the don't have the same Generic paramether. My result is to somehow pass a System.Type to Generic paramether. And my Question is some way to do it?
var table = TableInfo.GetValue(_context) as DbSet<[here i need pass it]>;
I need to do something like (I know this can't work)
var table = TableInfo.GetValue(_context) as DbSet<TableInfo.GetType>;
My full code
[HttpDelete("{name}/{id}")]
[Route("api/Delete")]
public IActionResult Delete(string name = "Items", int id = 2)
{
PropertyInfo TableInfo = GetValueByName(name);
if (TableInfo == null)
return NotFound("Haaaah");
var table = TableInfo.GetValue(_context) as DbSet<[here i need pass it]>;
if (table == null)
return BadRequest();
var prop = table.SingleOrDefault(p => p.Id == id);
if (prop == null)
return NotFound(prop);
table.Remove(prop);
_context.SaveChanges();
return Ok();
}
public PropertyInfo GetValueByName(string name)
{
Type t = _context.GetType();
List<PropertyInfo> list = new List<PropertyInfo>(t.GetProperties());
foreach(PropertyInfo m in list)
{
if (m.Name == name)
return m;
}
return null;
}
For end sorry about my English.
And thanks for all answers :)
var table = TableInfo.GetValue(_context) as DbSet<[here i need pass it]>;
You can't do that, you have no compile time information on what type you need, how do you expect to leverage it before the code is even running?
If you really want compile time type information of table you either know the generic type at compile time or you cover all possible execution paths considering all potential generic types your method must handle (horrendous, don't do that).
Using an interface won't work either. A hypothetical IIdEntity and a cast along the lines table as DbSet<IIdEntity> will never work because:
Type variance is only allowed in interfaces and delegates, DbSet is not an interface.
Even if you use IDbSet<TEntity>, this interface is invariant in TEntity so the following will always fail:
class User: IIdEntity { ... }
object o = someDbEntityOfUser;
var db = o as IDbSet<IIdEntity> //will always be null.
The best options you have with your current setup are:
Keep using reflection; use it to inspect the Id property of the entities.
Use dynamic and simply let the runtime resolve the Id call.
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);
}
}
});
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.
I have an entity by getting it from DbEntityEntry.Entity. This returns the Entity Framework proxy for the entity.
How do I access the underlying object as its original type instead of the proxy?
Alternatively I need to dynamically try to cast the proxy to the entity type. Here's a start.
var theEntityType = entityEntry.Entity;
if (
theEntityType.BaseType != null
&& entityType.Namespace == "System.Data.Entity.DynamicProxies"
)
theEntityType = entityType.BaseType;
// Now I need to cast to the correct type
// THIS WON'T WORK BECAUSE `theEntityType` is dynamic.
var entityObject = (theEntityType)entityEntry.Entity;
// My entites also don't implement IConvertible
While working with EF 6 i used the following code to get the underlying POCO entity type from proxy type,
var entityType = ObjectContext.GetObjectType(dbEntitymodifiedEntry.Entity.GetType());
ObjectContext.GetObjectType : Return the POCO from proxy object
reference : https://learn.microsoft.com/en-us/ef/ef6/fundamentals/proxies
First I should say there is no underlying object. A proxy doesn't wrap an entity object (decorator pattern), it derives from it (inheritance). So we can't unwrap the entity, we can only convert a proxy to a base object. Conversion (contrary to casting) always creates a new object.
For this conversion, we can exploit the fact that most of the time, by the way proxies are returned by EF, the compile time type of a proxy is the base type. That is, if a proxy is entered as an argument to a generic method, the generic parameter will be inferred as the base type. This feature allows us to create a method that does what you want:
T UnProxy<T>(DbContext context, T proxyObject) where T : class
{
var proxyCreationEnabled = context.Configuration.ProxyCreationEnabled;
try
{
context.Configuration.ProxyCreationEnabled = false;
T poco = context.Entry(proxyObject).CurrentValues.ToObject() as T;
return poco;
}
finally
{
context.Configuration.ProxyCreationEnabled = proxyCreationEnabled;
}
}
Explanation
The proxy object enters the method. Its type is inferred as the base POCO type. Now we can temporarily turn off ProxyCreationEnabled on the context and copy the proxy object to an object of its base POCO type. This copy action gratefully uses a few EF features.
If you end up needing to do this from a project that does not have access to EF or the DBContext, and you don't know if the type you are referencing is a proxy you can do something like this:
public Type GetType
{
get
{
var thisType = _baseObject.GetType();
if (thisType.Namespace == "System.Data.Entity.DynamicProxies")
return thisType.BaseType;
return thisType;
}
}
Proposed answer has number of problems - for example it doesn't preserve properties defined in partial classes for generated POCO classes and it reloads entity from DB (which also hits the performance).
You can try to turn proxies off before you ask for changes, but it may not help if entities have already been loaded before - they'll be proxy types already (probably it depends on EF version, but it worked out once and didn't work another time in my experience).
You also need to materialize it before you turn proxies back - it isn't obvious but it's just deferred query which has to be materialized:
context.Configuration.ProxyCreationEnabled = false;
var changes = context.ChangeTracker.Entries().ToArray();
To get a JSON friendly object in EF Core I used this method:
T UnProxy<T>(T efObject) where T : new()
{
var type = efObject.GetType();
if (type.Namespace == "Castle.Proxies")
{
var baseType = type.BaseType;
var returnObject = new T();
foreach (var property in baseType.GetProperties())
{
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
if (propertyType.Namespace == "System")
{
var value = property.GetValue(efObject);
property.SetValue(returnObject, value);
}
}
return returnObject;
}
return efObject;
}
Use AutoMapper 4.2.1 It is having DynamicMap which can remove the Proxy from the object.
var parents = parentsRepo.GetAll().ToList();
Mapper.CreateMap<Parent,ParentDto>();
var parentsDto = Mapper.DynamicMap<List<ParentDto>>(parents);
This is probably a complete noobie error.
My deepload is loading my related entitied fine and T Entity is correctly populated, but when I go back to the original call it hasnt kept the updates?
Now I'm sure EntityObjects are reference types (stupid question, but im doubting myself here)
So I shouldnt need to pass it back.
here is my deep load:
public void DeepLoad(T entity, Type[] childTypes)
{
Type baseType;
HasBaseType(typeof (T), out baseType);
var entitySetName = ProviderHelper.GetEntitySetName(Context, baseType.Name);
var query = Context.CreateQuery<T>(entitySetName);
foreach (var childType in ProviderHelper.GetChildTypeNames(childTypes).Split(','))
{
query = query.Include(childType);
}
entity = query.SingleOrDefault();
}
any help including finger pointing and laughing is excepted :)
Looks like EntityObjects are Value types not Reference types, because when I change my method to the below, it all works as it should.
public void DeepLoad(ref T entity, Type[] childTypes)
{
Type baseType;
HasBaseType(typeof (T), out baseType);
var entitySetName = ProviderHelper.GetEntitySetName(Context, baseType.Name);
var query = Context.CreateQuery<T>(entitySetName);
foreach (var childType in ProviderHelper.GetChildTypeNames(childTypes).Split(','))
{
query = query.Include(childType);
}
entity = query.SingleOrDefault();
}
I still think Im missing something here though... any views on this?