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);
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.
As far as I knew, Object.GetType() should never return null. (related discussion)
Dapper .Query() return private class DapperRow instances to be treated as dynamic objects. I found a strange thing: DapperRow's .GetType() return null.
Here's the sample code to reproduce the problem. Create a C# project, reference Dapper and open a connection to SQL Server (or other database), use .Query() to execute simple select query and retrieve the first row of result. Use GetType() to get the type of result object, the return value is null.
using (SqlConnection cn = new SqlConnection(csSql))
{
var rec = cn.Query("select getdate() as D").Single();
var t = rec.GetType(); // t == null
Console.WriteLine(t.Name); // null reference exception
}
I suspect that dynamic or private type is the cause of null, so I write my class library for test:
namespace Lib
{
public class Blah
{
public static dynamic SecretObject;
static Blah()
{
SecretObject = new PrivateType();
}
}
class PrivateType
{
}
}
In another project, get the dynamic type static field and call GetType():
dynamic obj = Lib.Blah.SecretObject;
Console.WriteLine(obj.GetType().Name); // "Lib.PrivateType"
According to the test result, even cast private type as dynamic, I still can get the private type information from GetType(), why DapperRow.GetType() return null?
DapperRow is specifically built and utilized within Dapper to provide highly optimized row returns without reiterating header information. This is to help condense the size of the object and reduce redundant data, making it more efficient.
However, it would appear that the StackExchange team took the meta programming even further than a first glance would indicate.
DapperRow implements the System.Dynamic.IDynamicMetaObjectProvide interface, which requires that the GetMetaObject method be implemented:
System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
System.Linq.Expressions.Expression parameter)
{
return new DapperRowMetaObject(parameter,
System.Dynamic.BindingRestrictions.Empty, this);
}
DapperRowMetaObject is a custom implementation of DynamicMetaObject that essentially hijacks and overrides what methods can be invoked against the dynamic type and what those calls should translate to. In this case, calls to anything other than the DapperRow's IDictionary.Item getter or the DapperRow.SetValue will fail since they are always routed to those two calls, but the value will be defaulted to null for any "get" calls where the target property does not exist in the table.
public bool TryGetValue(string name, out object value)
{
var index = table.IndexOfName(name);
if (index < 0)
{ // doesn't exist
value = null;
return false;
}
...
}
At that point, any methods invoked on a null dynamic value will throw a RuntimeBinderException:
RuntimeBinderException: Cannot perform runtime binding on a null
reference
You can easily test this hypothesis by replacing GetType() with another call that will throw the exact same exception:
var rec = cn.Query("select getdate() as D").Single();
var t = rec.AsEnumerable();
Console.WriteLine(t.ToList());
Keep in mind, the underlying type information of any properties on the dynamic object itself can still be accessed directly:
var rec = cn.Query("select getdate() as D").Single();
var t = rec.D.GetType();
Console.WriteLine(t.Name);
I have a stored procedure call interface that I'm using to handle results from stored procedures with entity (using Translate method to translate the results of our stored procedure into entities that can be tracked and used in EF as normal)
Here's the basic code...
List<object> current = new List<object>();
object item = ((Type)currenttype.Current).GetConstructor(System.Type.EmptyTypes).Invoke(new object[0]);
ObjectContext actualContext = ((IObjectContextAdapter)context).ObjectContext;
string className = "";
EntityContainer container = null;
string setName = "";
className = ((Type)currenttype.Current).ToString();
container = actualContext.MetadataWorkspace.GetEntityContainer(((IObjectContextAdapter)context).ObjectContext.DefaultContainerName, DataSpace.CSpace);
setName = (from meta in container.BaseEntitySets
where meta.ElementType.FullName == className
select meta.Name).FirstOrDefault();
var t = typeof(ObjectContext).GetMethod("Translate", new Type[] { typeof(DbDataReader), typeof(string), typeof(MergeOption) }).MakeGenericMethod(item.GetType()).Invoke(actualContext, new object[] { reader, setName, MergeOption.AppendOnly });
The issue is that I can't do anything with 't' that I want, it's type is listed as
object {System.Data.Entity.Core.Objects.ObjectResult<POCOClass>}. I can't call any of the normal methods that I can normally on the ObjectResult type such as ToArray or ToList.
I need a way to convert it into System.Data.Entity.Core.Objects.ObjectResult<POCOClass>. The difference being that 't' is listed as type object first.
I cannot use any strongly typed casts because the types will change depending on the stored procedure. I've tried using the dynamic keyword instead of var for t and I've also tried using Convert.ChangeType. It never changes from the object base type. dynamic t returns this the following error:
'System.Data.Entity.Core.Objects.ObjectResult<POCOClass>' does not contain a definition for 'ToList'
Thought I know for a fact it does...
To clear up confusion, here's a watch screenshot. The first line is what's being returned, I want it to be like the second (see Type column).
Edit: might be getting closer... I added this:
var listedT = typeof(Enumerable).GetMethod("ToList").MakeGenericMethod(item.GetType()).Invoke(null, new object[] { t });
current.AddRange(listedT); // Error here...
listedT becomes a object {System.Collections.Generic.List<ReportCatalog.Models.Catalog_Reports>} and I get the error, cannot convert from object to System.Collections.Generic.IEnumerable<object>.
Since ObjectResult<T> implements also the non-generic IEnumerable interface, cast it to this type and enumerate it.
var e = (IEnumerable)t;
foreach (object o in e) {
//TODO: use o
}
I get the error
"Error 4 Cannot convert type 'TExternalEntity' to 'OTIS.Domain.InventoryMgmt.OrderHeader'"
Why not? I'm sure it has something to do with the where statement defining the generic type, but not exactly sure how to get around this.
We can see that we are testing to see if the type is of type OrderHeader, so can't we cast to OrderHeader?
public ActionConfirmation<string> CreateUpdateEntity<TExternalEntity>
(TExternalEntity entity, CompanyPreferencesFinancialsSystemCommon preferences)
where TExternalEntity : class, OTIS.Domain.IEntity, IFinancials, new()
{
//Determine TExternalEntity type (invoice, vendor, customer) to determine which
//mapper class to create. Then convert TExternalEntity to TQuickbooksEntity.
if (entity is InvoiceHeader)
{
var qbInvoice = new InvoiceMapper().ToQuickbooksEntity(entity as InvoiceHeader, preferences);
return CreateUpdateQuickBooksEntity(
qbInvoice,
x => x.Id == entity.FinancialsId,
entity.FinancialsId);
}
if (entity is OrderHeader)
{
var orderHdr = (OrderHeader)entity; <------ ERROR HERE
var qbSalesReceipt = orderHdr.ToQuickBooksEntity(preferences);
return CreateUpdateQuickBooksEntity(
qbInvoice,
x => x.Id == entity.FinancialsId,
entity.FinancialsId);
}
You should probably mention that this is a compile-time error, not a run-time error.
The compiler doesn't know that you are testing the type, so it sees that this cast will potentially fail.
Either of the suggestions in the comments should work for you.
If you use (OrderHeader)(object)entity and the entity is not an OrderHeader (you know it is since you are testing for it first, but humor me), then you would get an error on that line.
If you use entity as OrderHeader and the entity is not an OrderHeader, the orderHdr variable would be set to null and execution would continue.
I also trimmed your example down to the minimum code required to duplicate the issue.
public ActionConfirmation<string> CreateUpdateEntity<TExternalEntity>(TExternalEntity entity)
{
if (entity is OrderHeader)
{
var orderHdr = (OrderHeader)entity; //<------ ERROR HERE
}
return null;
}
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 :.