I am trying to set up the following code:
Controller
[HttpGet]
public ActionResult LoadReport(string actionName, string reportInput, string reportCriteria)
{
var type = Assembly.Load("Company.TaxCollection.Reports").GetTypes().First(t => t.Name == reportInput);
var typeCriteria = Assembly.Load("Company.TaxCollection.Reports").GetTypes().First(t => t.Name == reportInput + "Criteria");
var reportObject = Activator.CreateInstance(type);
var reportObjectCriteria = Activator.CreateInstance(typeCriteria);
IEnumerable<ReportCriteria> reportList = getReportCriteria(reportObject);
foreach (ReportCriteria r in reportList)
{
reportObjectCriteria t = (reportObjectCriteria)r;
}
return Json(Url.Action(actionName, "Reports", reportList.Where(x => x.CriteriaName == reportCriteria)));
}
I get the error reportObjectCriteria is a variable but is used like a type within the foreach loop.
I have also tried not using a variable and just using Activator.CreateInstance directly, but that didn't work either.
foreach (ReportCriteria r in reportList)
{
Activator.CreateInstance(typeCriteria) t =
(Activator.CreateInstance(typeCriteria)) r;
}
The purpose of these lines of code is to cast the ReportCriteria object to another type dynamically during runtime. The object type to cast to is decided by the reportInput parameter in the controller.
As long as you're not loading up a .dll dynamically at runtime, I would recommend using a library like Automapper. You can create multiple mappings like below and use conditional logic to determine which type to map to.
var config = new MapperConfiguration(cfg => {
cfg.CreateMap<SourceType, DestType1>();
cfg.CreateMap<SourceType, DestType2>();
cfg.CreateMap<SourceType, DestType3>();
});
DestType1 dt1 = mapper.Map<DestType1>(sourceTypeInstance);
DestType2 dt2 = mapper.Map<DestType2>(sourceTypeInstance);
DestType3 dt3 = mapper.Map<DestType3>(sourceTypeInstance);
The library is very feature rich where you can configure mappings to a much deeper levels and lists/arrays etc.
Related
I have a scenario that should be really simple but it isnt!
I have a type that is determined at runtime
I want to call
JSonConvert.DeserialiseObject<List<type>>(json);
via Make Generic Method as I need to determine the type at runtime
var entityTypes = _dbContext.Model.GetEntityTypes();
foreach (var entityType in entityTypes)
{
var requiredType = entityType.ClrType;
var testMethod = typeof(JsonConvert).GetMethods().FirstOrDefault(
x => x.Name.Equals("DeserializeObject", StringComparison.OrdinalIgnoreCase) &&
x.IsGenericMethod && x.GetParameters().Length == 1)
?.MakeGenericMethod(requiredType);
var filename = $"my json.json";
var json = File.ReadAllText(filename);
var actualData2 = testMethod?.Invoke(null, new object[] { json });
}
This works perfectly if my json is for a single object
However, this is not the case, my json is for a list obviously the code above wont work because it expects my json to be a single object
So I tried to change to
var requiredType = typeof(List<entityType.ClrType>);
and entityType cannot resolved
What am I doing wrong? Sorry but I find generics really frustrating!
My ideal option was to be able to take a list of objects and convert that into a list of the required type but I cant get that to work in a generic way either hence having to try this
Cheers
Paul
This:
.MakeGenericMethod(requiredType)
change to:
.MakeGenericMethod(typeof(List<>).MakeGenericType(requiredType))
More readable as:
var listType = typeof(List<>).MakeGenericType(requiredType);
and then
...
.MakeGenericMethod(listType)
I have a table called "Account." Account has 3 columns: id, acct_name, is_privileged.
When I write something like "account.", visual studio provides me with a list of attributes/methods I can use. Hence, I get the option of using account.id, account.acct_name, and account.is_privileged.
However, I would like to change a particular column's value dynamically, without typing in my column's name. I am getting the column's name dynamically as a string variable. Is it possible to achieve it? If so, how?
My code is as follows:
set_col_name = rowRule.Cells["setcolumnnameDataGridViewTextBoxColumn"].Value.ToString();
set_col_value = rowRule.Cells["setcolumnvalueDataGridViewTextBoxColumn"].Value.ToString();
foreach (DataGridViewRow rowAcc in dgvAccount.Rows)
{
if (isComparable(rowAcc.Cells[col_name].Value.ToString(), comp_operator, col_value))
{
account.id = (int)rowAcc.Cells["idDataGridViewTextBoxColumn2"].Value;
using (ae = new AccountEntities())
{
var temp = ae.Accounts.SingleOrDefault(a => a.id == account.id);
temp.is_privileged = set_col_value; //learn how to do this dynamically
ae.SaveChanges();
}
}
}
Where I do temp.is_privileged, I'd like to achieve something like, temp."set_col_name" = set_col_value;
Instead of specifying the column name directly as being "is_privileged" in this case, I'd like to pass a string to specify it.
Thank you.
If I understand your problem statement correctly, you want something like this to work:
Account temp = // with temp coming from a library such as EntityFramework
temp.SetValue(set_col_name, set_col_value);
this is quite easy to achieve with either pure reflection or Linq Expression Trees (which I opted for):
static class Ext
{
public static void Set<T, TProperty>(this T instance, string propertyName, TProperty value)
{
var instanceExpression = Expression.Parameter(typeof(T), "p");
var propertyGetterExpression = Expression.PropertyOrField(instanceExpression, propertyName);
//generate setter
var newValueExpression = Expression.Parameter(typeof(TProperty), "value");
var assignmentExpression = Expression.Assign(propertyGetterExpression, newValueExpression);
var lambdaExpression = Expression.Lambda<Action<T, TProperty>>(assignmentExpression, instanceExpression, newValueExpression);
var setter = lambdaExpression.Compile();// the generated lambda will look like so: (p, value) => p.{your_property_name} = value;
setter(instance, value);
}
}
one advantage of this method over pure reflection is that you can build the setter delegate once and call it multiple times at later stage (I will leave this with you to experiment)
with the above in place, hopefully you should be able to do something like this:
var set_col_name = "is_privileged";
var set_col_value = true;
using (ae = new AccountEntities())
{
var temp = ae.Accounts.SingleOrDefault(a => a.id == account.id);
temp.Set(set_col_name, set_col_value);
temp.Set("acct_name", "test");
ae.SaveChanges();
}
You need some reflection in this one. For example
public static void CopyValues<T>(T obj1, T obj2)
{
var type = typeof(T);
foreach (var prop in type.GetProperties())
{
prop.SetValue(obj1, prop.GetValue(obj2));
}
}
And use the above function like this:
var source = new Accounts(){is_privileged = false};
var destiny = new Accounts();
CopyValues(source, destiny);
It depends of what you are loking for, but the key is to use REFLECTION!
My question: is it possible - and if so, how? - to have results from a query using Entity Framework populated with fully typed objects when initialized using reflection?
The conditions are the context and entities must be able to be in an external dll not referenced directly in the project.
I do not want to have to manually loop through the object graph and reflect all types.
This is the code I have currently.
Assembly metaAssembly = AppDomain.CurrentDomain.GetAssemblies().
SingleOrDefault(assembly => assembly.GetName().Name == "Data_Meta");
TypeHelper myTypeHelper = new TypeHelper();
Type dbContextType = myTypeHelper.FindDerivedTypes(metaAssembly, typeof(System.Data.Entity.DbContext)).ToList().FirstOrDefault();
using (var ctx = (DbContext)Activator.CreateInstance(dbContextType))
{
ctx.Configuration.LazyLoadingEnabled = false;
var curEntityPI = ctx.GetType().GetProperties().Where(pr => pr.Name == "Worker").First();
var curEntityType = curEntityPI.PropertyType.GetGenericArguments().First();
var set = ctx.Set(curEntityType);
var t = set.ToString();
Type generic = typeof(DataAccess.Models.Repository.EF.GenericEfDataRepository<,>);
Type[] typeArgs = { curEntityType, dbContextType };
Type constructed = generic.MakeGenericType(typeArgs);
MethodInfo methodInfo = constructed.GetMethod("GetAll");
object repositoryInstance = Activator.CreateInstance(constructed, new object[] { ctx });
var navigationPropertyType = typeof(Expression<>).MakeGenericType(
typeof(Func<,>).MakeGenericType(curEntityType, typeof(object)));
var navigationProperties = Array.CreateInstance(navigationPropertyType, 0);
var result = methodInfo.Invoke(repositoryInstance, new object[] { navigationProperties });
}
I can generate a database query and get the correct number of results, however items in the list returned to do not evaluate to a type unless I put a reference in the project to the dll which defeats my purpose. I should say I am resolving the location of the dll and loading it via the AppDomain.CurrentDomain.AssemblyResolve event.
Many thanks
I'm using System.Linq.Dynamic to query an IQueryable datasource dynamically using a where-clause in a string format, like this:
var result = source.Entities.Where("City = #0", new object[] { "London" });
The example above works fine. But now I want to query on a foreign key-property of type Guid like this:
var result = source.Entities.Where("CompanyId = #0", new object[] { "838AD581-CEAB-4B44-850F-D05AB3D791AB" });
This won't work because a Guid can't be compared to a string by default. And I have to provide the guid as a string because it's originally coming from a json-request and json does not support guid's.
Firstly, is this even a correct way of querying over a relationship or is there an other syntax for doing this?
Secondly, how do I modify Dynamic.cs from the Dynamic Linq-project to automatically convert a string to guid if the entity-property being compared with is of type guid?
You have many ways for solving. Simplest, as i think, will be change your query like this
var result = source.Entities.Where("CompanyId.Equals(#0)", new object[] { Guid.Parse("838AD581-CEAB-4B44-850F-D05AB3D791AB") });
If you want use operators = and == then in Dynamic.cs you need change interface IEqualitySignatures : IRelationalSignatures like this
interface IEqualitySignatures : IRelationalSignatures
{
....
F(Guid x, Guid y);
....
}
after that you can use next query
var result = source.Entities.Where("CompanyId=#0", new object[] { Guid.Parse("838AD581-CEAB-4B44-850F-D05AB3D791AB") });
OR
var result = source.Entities.Where("CompanyId==#0", new object[] { Guid.Parse("838AD581-CEAB-4B44-850F-D05AB3D791AB") });
But if you want use string parameter you need change ParseComparison method in ExpressionParser class. You need add yet another checking for operand types like this
....
//you need add this condition
else if(left.Type==typeof(Guid) && right.Type==typeof(string)){
right = Expression.Call(typeof(Guid).GetMethod("Parse"), right);
}
//end condition
else {
CheckAndPromoteOperands(isEquality ? typeof(IEqualitySignatures) : typeof(IRelationalSignatures), op.text, ref left, ref right, op.pos);
}
....
and then you query will be work
var result = source.Entities.Where("CompanyId = #0", new object[] { "838AD581-CEAB-4B44-850F-D05AB3D791AB" });
I like the approach of having property bag objects (DTOs) which define the interface to my server, but I don't like writing code like this:
void ModifyDataSomeWay(WibbleDTO wibbleDTO)
{
WibbleBOWithMethods wibbleBO = new WibbleBOWithMethods();
wibbleBO.Val1 = wibbleDTO.Val1;
wibbleBO.Val2 = wibbleDTO.Val2;
}
This copying code is quite laborious to write. If the copying code is unavoidable, then where do you put it? In the BO? In a factory? If it is possible to manually avoid writing the boiler plate code then how?
Thanks in advance.
That looks like a job for AutoMapper, or (simpler) just add some interfaces.
This needs more error handling, and you may need to modify it accommodate properties where the data types don't match, but this shows the essence of a simple solution.
public void CopyTo(object source, object destination)
{
var sourceProperties = source.GetType().GetProperties()
.Where(p => p.CanRead);
var destinationProperties = destination.GetType()
.GetProperties().Where(p => p.CanWrite);
foreach (var property in sourceProperties)
{
var targets = (from d in destinationProperties
where d.Name == property.Name
select d).ToList();
if (targets.Count == 0)
continue;
var activeProperty = targets[0];
object value = property.GetValue(source, null);
activeProperty.SetValue(destination, value, null);
}
}
Automapper (or similar tools) might be the way forward here. Another approach may be the factory pattern.
Simplest of all would be something like this:
class WibbleBO
{
public static WibbleBO FromData(WibbleDTO data)
{
return new WibbleBO
{
Val1 = data.Val1,
Val2 = data.Val2,
Val3 = ... // and so on..
};
}
}