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.
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 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?
I have a method which gets the name of a sub class from an error message, as below
string jobDesc = getDtlName(serviceResponse.ErrorMessages[0].Error);
I have the Class and want to access the sub class properties but how can I do that when I only have the sub class name as a string.
Java have a method which works like this
Class myClass = Class.forName(jobDesc);
How can I do the same in C#?
You need to do something like this,
string jobDesc = getDtlName(serviceResponse.ErrorMessages[0].Error);
SomeClass myClass = new SomeClass();
// set some class values,
var classType = typeof(SomeClass );
PropertyInfo info = classType.GetProperty(jobDesc);
var propertyValue = info.GetValue(myClass, null);
You might need to do a minor changes as I haven't tested this solution,
You can get all loaded assemblies using AppDomain.CurrentDomain.GetAssemblies(),
then get all types of these assemblies with .SelectMany(a => a.GetTypes())
then select the one type with the given name with .Single(t => t.Name == jobDesc).
Keep in mind that Single throws an exception if not exactly 1 element matches the condition.
If you have multiple types with the same name (in different namespaces) you should use Where instead.
If there may be no matching classes, use SingleOrDefault, which returns null if there is no matching element.
AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
.Single(t => t.Name == jobDesc)
If the desired class is in the assembly that is currently executed, you can also use
System.Reflection.Assembly.GetExecutingAssembly().GetTypes()
.Single(t => t.Name == jobDesc)
I managed to get a working solution using Properties with the code below
PropertyInfo[] properties = typeof(UIStaticFieldErrors).GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.ToString() == "Boolean " + jobDesc)
{
property.SetValue(errorSF, true);
}
}
I get the properties of UIStaticFieldErrors in properties and then iterate through until I get a match for jobDesc, then I update that field to true. The variable errorSF is an instance of UIStaticFieldErrors.
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.
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;
}