Loaded Entity not being passed back - c#

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?

Related

How to obtain the value of a specific property in DataRow (unknown) object using LINQ / Reflection?

How can I obtain a specific row in RowData property of my variable? I'm trying to obtain "Id". What I've tried so far is either LINQ or just a regular foreach loop, however I'm only able to acces GetType().GetProperties(). From these methods I'm unable to acces the value of "Id".
What I've tried so far:
I've tried using reflection like this:
public void CommandClickHandler(CommandClickEventArgs<object> args)
{
TabObj.Select(2);
var IdOfModel = args.RowData.GetType().GetProperties().Single(pi => pi.Name == "Id").GetValue(args, null);
}
But this is giving me the error "Object does not match target Type".
Hope someone can help!
Thanks in advance
It's pretty clear what's going on if you break that up. You're passing the wrong argument to PropertyInfo.GetValue(object,arg[]), and it should be:
var obj = args.RowData;
var type = obj.GetType();
var IdOfModel = type.GetProperties().Single(pi => pi.Name == "Id").GetValue(obj, null);
or simply
dynamic obj = args.RowData;
var IdOfModel = obj.Id;

passing System.Type to generic type

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.

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 :.

Linq2SQL - selecting items using reflection

I'm trying to load a collection of entities using Linq2SQL. The problem is, I dont know what the entities are,IList<object>. I have tried to select them using reflection, but I get an out of memory error when I do the select, I presume because the context is unable to parse my expression,and is loading everything from the DB.
If anyone has any advice on this, or an alternative way to do what I want, please let me know.
foreach (object entity in requiredEntities)
{
Type entityType = entity.GetType();
IQueryable<object> entityTable = (IQueryable<object>)dataContext.GetTable(entityType);
// grab the objects primary key field
var pkeyField = entityType.GetProperties().SingleOrDefault(p =>
p.GetCustomAttributes(typeof(System.Data.Linq.Mapping.ColumnAttribute),true)
.Cast<System.Data.Linq.Mapping.ColumnAttribute>()
.Any(attrib => attrib.IsPrimaryKey));
object pkeyValue = pkeyField.GetValue(entity,null);
Func<object,bool> primaryKeySelector = o => pkeyField.GetValue(o,null) == pkeyValue;
// crash here, out of memory exception
object result = entityTable.Where(primaryKeySelector).SingleOrDefault();
}
By using a delegate you are forcing it to use LINQ-to-Objects, which is why it is running out of memory. What you need to do is build an Expression instead. Equally, it is bad practice to use the attributes as that is not the only model that LINQ-to-SQL supports; it is preferable to look at dataContext.Mapping.GetMetaType(entityType) to get the primary key.
If you have 4.0, the following should work:
var entityType = typeof(User);
var metaType = dataContext.Mapping.GetMetaType(entityType);
var member = metaType.DataMembers.Single(m => m.IsPrimaryKey).Member;
var param = Expression.Parameter(entityType);
var body = Expression.Equal(Expression.MakeMemberAccess(param, member),
Expression.MakeMemberAccess(Expression.Constant(entity), member));
dynamic table = dataContext.GetTable(entityType);
object result = Cheeky(table, body, param);
with
static T Cheeky<T>(ITable<T> source, Expression body, ParameterExpression param)
where T : class
{
var predicate = Expression.Lambda<Func<T, bool>>(body, param);
return source.SingleOrDefault(predicate);
}

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

Categories