I need to dynamically cast a SQL return value to a model like this
var data = _db.Query<myModel>(myStoredProcedure, p, commandType: System.Data.CommandType.StoredProcedure);
The above works as it should as the model is explicit in the casting.
However, myModel could be any type of model class that I have in my project. I am able to get the actual model through reflection and the use of a string variable called "TableName"
I thought I could go this routine to set it but I get the error of 'entityObject is a variable but used like a type'
//First we need to find the project that holds all of our entity models in the assembly
var assembly = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName.Contains("MyProject.Models")).FirstOrDefault();
//Now we need to search through the assembly to match the Entity to the supplied TableName
var type = assembly.GetTypes()
.FirstOrDefault(t => t.Name == localTableName);
//Once found then we create a dynamic instance of the entity using reflection
if (type != null)
{
var ctx = new MyProject.Models.Entities();
//Create the DBSet here
System.Data.Entity.DbSet myDbSet = ctx.Set(type);
//Now create the actual entity reference which is just an object at this point
var entityObject = myDbSet.Create();
--> Errors here var data = _db.Query<entityObject>(myStoredProcedure, p, commandType: System.Data.CommandType.StoredProcedure);
}
Should I use ExpandoObject? If so how can convert the expandoobject into the entityClass?
Related
I'm attempting to add "History" tables to EFCore, and have it mostly working. For every POCO in my model that has a [Historyable] attribute on the class, I use reflection to generate a new class that has three additional properties in addition to the properties that are in the original class - ChangeDate, ChangeType, and ChangeUserID.
My problem comes when I need to set the primary key for the new generated History type. In hand written code, adding the primary keys would look something like:
modelBuilder.Entity<EquipmentPortProfile>().HasKey(k => new { k.Name, k.PortType, k.CardType })
modelBuilder.Entity<EquipmentPortProfileHistory>().HasKey(k => new { k.Name, k.PortType, k.CardType, k.ChangeDate }) // add ChangeDate to keep it unique
where EquipmentPortProfile is the original [Historable] POCO that I can manually call HasKey() for and EquipmentPortProfileHistory is the new generated type that I need to call HasKey() for via reflection.
I've got the following code which adds the new History type of the model, but I can't quite figure out how to generically invoke the call to HasKey() with the List of MemberExpressions I've built.
var type = tbuilder.CreateType(); // create the new History type
var entityMethod = typeof(ModelBuilder).GetMethods().First(f => f.Name == "Entity");
var ent = entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { }); // add the new History type to the model
var haskey = ent.GetType().GetMethods().FirstOrDefault(m => m.Name == "HasKey");
var param = Expression.Parameter(type, "k"); List<MemberExpression> args = new List<MemberExpression>();
var pk = historable.FindPrimaryKey();
foreach (var prop in pk.Properties) // we add in the same key properties as the table that we are tracking
args.Add(Expression.PropertyOrField(param, prop.Name));
args.Add(Expression.PropertyOrField(param, "ChangeDate")); // and then also add in ChangeDate as part of the PK to keep it unique
The simplest is to use the HasKey overload accepting params string[] propertyNames argument.
Also there is no need to call the generic Entity<TEntity> method via reflection, you can simply use the non generic Entity method accepting Type type argument, e.g. something like this
var type = tbuilder.CreateType(); // create the new History type
var pk = historable.FindPrimaryKey();
var pkPropertyNames = pk.Properties.Select(p => p.Name).Append("ChangeDate").ToArray();
modelBuilder.Entity(type).HasKey(pkPropertyNames);
I am passing a table name to a function and I need to get the name of the entity dynamically and use that entity as normal.
Normal hard coded way
MyEntity myEntity = new MyEntity();
i tried to get it but this doesnt work
// Get the Assembly here as the entites exist in another project within the solution
//Now that we have the assembly, search through it to get the correct entity based on the tablename string
var assembly = AppDomain.CurrentDomain.GetAssemblies().Where(x => x.FullName.Contains("NAMESPACE")).FirstOrDefault();
var type = assembly.GetTypes()
.FirstOrDefault(t => t.Name == tableName);
if (type != null)
{
System.Data.Entity.DbSet myDbSet = context.Set(type);
myDbSet myEntity = new ??? <-- I need to create an instance of the entity here
}
You can either use Activator.CreateInstance or DbSet.Create() to create entity. Then use DbSet.Add() to add it to dbset.
See sample below
object entityObj = dbSet.Create();
//Populate values using reflection / dynamic
//....
dbSet.Add(entityObj);
dbSet.SaveChanges();
Hope this helps.
I am using the VS Lightswitch ServerApplicationContext to access and modify lightswitch data entities within an ApiController.
Let's say I have a Customer entity, and i can query the collection of customers in the lightswitch db using linq:
IEnumerable<Customer> customers = from custs in serverContext.DataWorkspace
.ApplicationData
.Customers
.GetQuery()
.Execute()
where c.AProperty == aProperty
select custs;
or
IEnumerable<Customer> customers =
serverContext.DataWorkspace
.ApplicationData
.Customers
.Where(c => c.AProperty == aProperty)
.Execute();
This works perfectly.
However, I have many more entities and several projects with different entities in each project and I am trying to create a library to allow me to query ServerApplicationContext using reflection.
I have used reflection to get the properties of the ServerApplicationContext object, which gives me access to the EntitySet<T>, but I can not execute any queries against it.
This is the code as it stands:
Type t = serverContext.DataWorkspace.ApplicationData.GetType();
PropertyInfo[] pInfo = t.GetProperties();
foreach (var p in pInfo)
{
// p is equal to {Microsoft.LightSwitch.Framework.EntitySet`1[LightSwitchApplication.Customer] Customers}
MethodInfo mInfo = p.PropertyType.GetMethod("GetQuery");
var result = mInfo.Invoke(p.PropertyType, null) ; //<-- Error Here
}
The error returned is:
An exception of type 'System.Reflection.TargetException' occurred in mscorlib.dll but was not handled in user code
Additional information: Object does not match target type.
Has anyone had any joy with querying EntitySets (including where clauses) using reflection?
The part where you're trying to invoke the GetQuery MethodInfo has the wrong target - the way it's currently written it's trying to call a GetQuery method on an instance of System.Type (obtained from p.PropertyType), which isn't going to work. What you need to do is get the instance of your EntitySet<T> from serverContext.DataWorkspace.ApplicationData first, then invoke the GetQuery method on that instance.
Type t = serverContext.DataWorkspace.ApplicationData.GetType();
PropertyInfo[] pInfo = t.GetProperties();
foreach (var p in pInfo)
{
// p is equal to {Microsoft.LightSwitch.Framework.EntitySet`1[LightSwitchApplication.Customer] Customers}
MethodInfo mInfo = p.PropertyType.GetMethod("GetQuery");
var entitySet = p.GetValue(serverContext.DataWorkspace.ApplicationData); // new line
var result = mInfo.Invoke(entitySet, null); // updated line
}
For details on how to put together a dynamic Where clause against your EntitySet<T>, check out the links in this answer: https://stackoverflow.com/a/4799798/2611587.
Consider the following example LINQ to entity query
from history in entities.foreignuserhistory
select new { history.displayname, login=history.username, history.foreignuserid }
ToTraceString() return string looks like:
SELECT "Extent1"."foreignuserid" AS "foreignuserid",
"Extent1"."displayname" AS "displayname",
"Extent1"."username" AS "username"
FROM "integration"."foreignuserhistory" AS "Extent1"
The problem for me is that columns come in different order from query and do not take aliases like login in the example. Where does Entity Framework store mapping information for anonymous types?
Background: I'm going to develop insert with select operation using LINQ to entity for mass operations.
Update:
Insert with select is not that hard except for an unknown column to property mapping algorithm. One can get table and column names for destination ObjectSet using metadata, build INSERT INTO tableName (column_name1, …) sql statement string and then append some ObjectQuery.ToTraceString SELECT statement. Then create a DbCommand with resulting text using ((EntityConnection)ObjectContext.Connection).StoreConnection and fill command’s parameters from ObjectQuery. So the problem is to find matching column order in inserted and selected records.
Here’s my solution all the way down of privates and internals. It travels with reflection into cached query plan which will exist after ToTraceString call or query execution to get what is called _columnMap. Column map contains ScalarColumnMap objects going in the order of anonymous object’s properties and pointing to the corresponding column position with ColumnPos property.
using System;
using System.Data.Objects;
using System.Reflection;
static class EFQueryUtils
{
public static int[] GetPropertyPositions(ObjectQuery query)
{
// get private ObjectQueryState ObjectQuery._state;
// of actual type internal class
// System.Data.Objects.ELinq.ELinqQueryState
object queryState = GetProperty(query, "QueryState");
AssertNonNullAndOfType(queryState, "System.Data.Objects.ELinq.ELinqQueryState");
// get protected ObjectQueryExecutionPlan ObjectQueryState._cachedPlan;
// of actual type internal sealed class
// System.Data.Objects.Internal.ObjectQueryExecutionPlan
object plan = GetField(queryState, "_cachedPlan");
AssertNonNullAndOfType(plan, "System.Data.Objects.Internal.ObjectQueryExecutionPlan");
// get internal readonly DbCommandDefinition ObjectQueryExecutionPlan.CommandDefinition;
// of actual type internal sealed class
// System.Data.EntityClient.EntityCommandDefinition
object commandDefinition = GetField(plan, "CommandDefinition");
AssertNonNullAndOfType(commandDefinition, "System.Data.EntityClient.EntityCommandDefinition");
// get private readonly IColumnMapGenerator EntityCommandDefinition._columnMapGenerator;
// of actual type private sealed class
// System.Data.EntityClient.EntityCommandDefinition.ConstantColumnMapGenerator
object columnMapGenerator = GetField(commandDefinition, "_columnMapGenerator");
AssertNonNullAndOfType(columnMapGenerator, "System.Data.EntityClient.EntityCommandDefinition+ConstantColumnMapGenerator");
// get private readonly ColumnMap ConstantColumnMapGenerator._columnMap;
// of actual type internal class
// System.Data.Query.InternalTrees.SimpleCollectionColumnMap
object columnMap = GetField(columnMapGenerator, "_columnMap");
AssertNonNullAndOfType(columnMap, "System.Data.Query.InternalTrees.SimpleCollectionColumnMap");
// get internal ColumnMap CollectionColumnMap.Element;
// of actual type internal class
// System.Data.Query.InternalTrees.RecordColumnMap
object columnMapElement = GetProperty(columnMap, "Element");
AssertNonNullAndOfType(columnMapElement, "System.Data.Query.InternalTrees.RecordColumnMap");
// get internal ColumnMap[] StructuredColumnMap.Properties;
// array of internal abstract class
// System.Data.Query.InternalTrees.ColumnMap
Array columnMapProperties = GetProperty(columnMapElement, "Properties") as Array;
AssertNonNullAndOfType(columnMapProperties, "System.Data.Query.InternalTrees.ColumnMap[]");
int n = columnMapProperties.Length;
int[] propertyPositions = new int[n];
for (int i = 0; i < n; ++i)
{
// get value at index i in array
// of actual type internal class
// System.Data.Query.InternalTrees.ScalarColumnMap
object column = columnMapProperties.GetValue(i);
AssertNonNullAndOfType(column, "System.Data.Query.InternalTrees.ScalarColumnMap");
//string colName = (string)GetProp(column, "Name");
// can be used for more advanced bingings
// get internal int ScalarColumnMap.ColumnPos;
object columnPositionOfAProperty = GetProperty(column, "ColumnPos");
AssertNonNullAndOfType(columnPositionOfAProperty, "System.Int32");
propertyPositions[i] = (int)columnPositionOfAProperty;
}
return propertyPositions;
}
static object GetProperty(object obj, string propName)
{
PropertyInfo prop = obj.GetType().GetProperty(propName, BindingFlags.NonPublic | BindingFlags.Instance);
if (prop == null) throw EFChangedException();
return prop.GetValue(obj, new object[0]);
}
static object GetField(object obj, string fieldName)
{
FieldInfo field = obj.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null) throw EFChangedException();
return field.GetValue(obj);
}
static void AssertNonNullAndOfType(object obj, string fullName)
{
if (obj == null) throw EFChangedException();
string typeFullName = obj.GetType().FullName;
if (typeFullName != fullName) throw EFChangedException();
}
static InvalidOperationException EFChangedException()
{
return new InvalidOperationException("Entity Framework internals has changed, please review and fix reflection code");
}
}
I think some assertions can be relaxed to check not the exact type but base type containing necessary property.
Is there a solution without reflection?
How the columns are aliased in the query shouldn't matter, and neither should their order. Entity Framework handles populating a new instance of your anonymous type with each result, and that's where you get the alias like login.
As a side note, I think Entity Framework may not work quite how you think. You can't do a select/insert in a single operation like you can using a normal SQL query. Entity Framework will execute your select, return back the results, use those results to create new instances of your entities (or in your case, an anonymous type), and you would then have to use each result to create a new instance of your target type, adding each one to your entity/object context, and finally call save changes on your entity/object context. This will cause an individual insert statement to be executed for each new entity that you've added.
If you want to do it all in a single operation without instantiating a new entity for every record, you'll need to either use a stored procedure that you map in your context, or else execute an in-line SQL query using ObjectContext.ExecuteStoreCommand
UPDATE: Based on your responses, what you're really getting into is closer to meta-programming that relies on your entity model more so than actually using entity framework. I don't know what version of EF you're using (EF 4.0? 4.1 w/ code first and DbContext?), but I've had a lot of success using the C# POCO template with EF 4.0 (the POCO template is a download from the online visual studio gallery). It uses a T4 template to generate POCO classes from the .edmx data model. In your T4 template, you could add methods to your context that would essentially call ExecuteStoreCommand, but the difference would be you can generate the query that gets executed based on your data model. That way any time your data model changes, your query would stay in sync with the changes.
Updated the reflection on this for EF 4.4 (5-RC)
full post at http://imaginarydevelopment.blogspot.com/2012/06/compose-complex-inserts-from-select.html
using this functionality/logic for doing a bulk insert from a select with some parameters provided
int Insert<T>(IQueryable query,IQueryable<T> targetSet)
{
var oQuery=(ObjectQuery)this.QueryProvider.CreateQuery(query.Expression);
var sql=oQuery.ToTraceString();
var propertyPositions = GetPropertyPositions(oQuery);
var targetSql=((ObjectQuery)targetSet).ToTraceString();
var queryParams=oQuery.Parameters.ToArray();
System.Diagnostics.Debug.Assert(targetSql.StartsWith("SELECT"));
var queryProperties=query.ElementType.GetProperties();
var selectParams=sql.Substring(0,sql.IndexOf("FROM "));
var selectAliases=Regex.Matches(selectParams,#"\sAS \[([a-zA-Z0-9_]+)\]").Cast<Match>().Select(m=>m.Groups[1].Value).ToArray();
var from=targetSql.Substring(targetSql.LastIndexOf("FROM [")+("FROM [".Length-1));
var fromAlias=from.Substring(from.LastIndexOf("AS ")+"AS ".Length);
var target=targetSql.Substring(0,targetSql.LastIndexOf("FROM ["));
target=target.Replace("SELECT","INSERT INTO "+from+" (")+")";
target=target.Replace(fromAlias+".",string.Empty);
target=Regex.Replace(target,#"\sAS \[[a-zA-z0-9]+\]",string.Empty);
var insertParams=target.Substring(target.IndexOf('('));
target = target.Substring(0, target.IndexOf('('));
var names=Regex.Matches(insertParams,#"\[([a-zA-Z0-9]+)\]");
var remaining=names.Cast<Match>().Select(m=>m.Groups[1].Value).Where(m=>queryProperties.Select(qp=>qp.Name).Contains(m)).ToArray(); //scrape out items that the anonymous select doesn't include a name/value for
//selectAliases[propertyPositions[10]]
//remaining[10]
var insertParamsOrdered = remaining.Select((s, i) => new { Position = propertyPositions[i], s })
.OrderBy(o => o.Position).Select(x => x.s).ToArray();
var insertParamsDelimited = insertParamsOrdered.Aggregate((s1, s2) => s1 + "," + s2);
var commandText = target + "(" + insertParamsDelimited + ")" + sql;
var result=this.ExecuteStoreCommand(commandText,queryParams.Select(qp=>new System.Data.SqlClient.SqlParameter{ ParameterName=qp.Name, Value=qp.Value}).ToArray());
return result;
}
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);
}