This question already has answers here:
Passing dynamic object to C# method changes return type
(2 answers)
Closed 3 years ago.
I have a problem passing dynamic parameter to the async method. Compile time error says Argument 1: cannot convert from 'System.Collections.Generic.IEnumerable<dynamic>' to 'System.Collections.Generic.IEnumerable<System.Threading.Tasks.Task>'.
Actually, I am trying to log entries for each DB table column entries. So, I am using a generic method which takes dynamic oldervalue and newvalue and perform insertion into the AuditDB.
Code
public async Task Translog(int Id, string Action, DateTime RecordTimeStamp, dynamic oldervalue, dynamic newvalue, VIBRANT db, VibrantAuditEntities context)
{
try
{
#region FrameLog
Type typeOfMyObject;
if (oldervalue != null)
typeOfMyObject = oldervalue.GetType();
else
typeOfMyObject = newvalue.GetType();
PropertyInfo[] properties = typeOfMyObject.GetProperties();
var tasks = properties.ToList().Select(i => SetPropValuess(typeOfMyObject, i, Id, Action, RecordTimeStamp, oldervalue, newvalue, db, context));
await Task.WhenAll(tasks); // Compile error at this line
#endregion
}
catch
{
throw;
}
}
public async Task SetPropValuess(Type typeOfMyObject, PropertyInfo item, int Id, string Action, DateTime RecordTimeStamp, dynamic oldervalue, dynamic newvalue, VIBRANT db, VibrantAuditEntities context)
{
await Task.Run(() =>
{
string PropertyTypeNamespace = typeOfMyObject.GetProperty(item.Name).PropertyType.Namespace;
if (PropertyTypeNamespace != "System.Collections.Generic" && PropertyTypeNamespace != "ClassLibrary")
{
if (oldervalue != null && newvalue != null)
{
//Edit
bool chk = Comparer.Equals(oldervalue.GetType().GetProperty(item.Name).GetValue(oldervalue, null), newvalue.GetType().GetProperty(item.Name).GetValue(newvalue, null));
if (chk == false)
{
// make and add log record
AuditLog al = new AuditLog();
al.TableName = (typeOfMyObject.BaseType.Name == "Object" ? typeOfMyObject.Name : typeOfMyObject.BaseType.Name);
al.EventDateUTC = RecordTimeStamp;
al.OriginalValue = Convert.ToString(oldervalue.GetType().GetProperty(item.Name).GetValue(oldervalue, null));
al.NewValue = Convert.ToString(newvalue.GetType().GetProperty(item.Name).GetValue(newvalue, null));
al.ColumnName = item.Name;
context.AuditLogs.Add(al);
}
}
else if (oldervalue != null && newvalue == null)
{
//Delete
AuditLog al = new AuditLog();
al.TableName = (typeOfMyObject.BaseType.Name == "Object" ? typeOfMyObject.Name : typeOfMyObject.BaseType.Name);
al.EventDateUTC = RecordTimeStamp;
al.OriginalValue = Convert.ToString(typeOfMyObject.GetProperty(item.Name).GetValue(oldervalue, null));
al.NewValue = null;
al.ColumnName = item.Name;
al.EventType = Action;
context.AuditLogs.Add(al);
}
else if (oldervalue == null && newvalue != null)
{
//Create
AuditLog al = new AuditLog();
al.TableName = (typeOfMyObject.BaseType.Name == "Object" ? typeOfMyObject.Name : typeOfMyObject.BaseType.Name);
al.EventDateUTC = RecordTimeStamp;
al.OriginalValue = null;
al.NewValue = Convert.ToString(typeOfMyObject.GetProperty(item.Name).GetValue(newvalue, null));
al.ColumnName = item.Name;
context.AuditLogs.Add(al);
}
}
});
}
It's very strange, for some reason the fact that there is a parameter of type 'dynamic' affects the type of Tasks. If the dynamic parameters are removed then it works. As a workaround you can add a cast, but would be interesting to understand why this happens:.
var tasks = properties
.ToList()
.Select(i => SetPropValuess(typeOfMyObject, i, Id, Action, RecordTimeStamp, oldervalue, newvalue, db, context))
.Cast<Task>();
Related
Hi I have Controller method as below
[HttpPost]
public JsonResult Post(string vehiclesString, string Entity, int EntityId, ApplicationUser CurrentUser)
{
//https://stackify.com/understanding-asp-net-performance-for-reading-incoming-data/
List<Vehicle> vehicles = Newtonsoft.Json.JsonConvert.DeserializeObject<List<Vehicle>>(vehiclesString);
InputFieldController c = new InputFieldController();
var errors = new List<string>();
try
{
//let's get model for each of the input field
InputFieldController icController = new InputFieldController();
List<InputField> fields = icController.GetNamesValues("VehicleField", -1, "Vehicle", 0);
foreach (Vehicle vehicle in vehicles)
{
//convert array of strings into array of input fields
if (fields.Count != vehicle.ValueStrings.Count)
{
throw new Exception("Vehicle columns mismatch. Expected "
+ fields.Count + " fields, but received " + vehicle.ValueStrings.Count);
}
for (int i = 0; i < fields.Count; i++)
{
InputField field = fields[i];
string cell = vehicle.ValueStrings[i];
if ((cell != null || cell != String.Empty) && (field.Type == "radio" || field.Type == "dropdown"))
{
var f = field.InputDropdowns.Where(x => x.Name == cell).FirstOrDefault();
if (f != null)
{
field.InputValue.InputDropdownId = f.InputDropdownId;
}
else
field.InputValue.InputDropdownId = null;
}
else
{
field.InputValue.Value = cell;
}
vehicle.Values.Add(field);
}
vehicle.Blob = Newtonsoft.Json.JsonConvert.SerializeObject(vehicle.Values);
Vehicle v = new Vehicle();
if (vehicle.VehicleId == 0)
{
v = this.DomainLogicUnitOfWork.VehicleManager.Create(vehicle, Entity, EntityId);
}
}
JsonResult data = Json(new
{
success = true,
});
List<Vehicle> vehiclesList = this.DomainLogicUnitOfWork.VehicleManager.List(Entity, EntityId);
if (vehiclesList != null)
foreach (Vehicle v in vehiclesList)
{
if ((v != null) && (v.Blob != null))
v.Values = Newtonsoft.Json.JsonConvert.DeserializeObject<List<InputField>>(v.Blob);
}
//Task task = Task.Run(async () => await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(Entity, EntityId));
/*
* Here I have to call the this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(string Entity, int EntityId) asynchronously
* but return the data without waiting for the CreateOrUpdate to complete
*/
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(vehiclesList, Entity, EntityId);
});
return data;
}
catch (Exception ex)
{
LogHandler.LogError(9000, "Error updating input fields", ex);
errors.Add("Error 9000:" + ex.Message);
return Json(new
{
error = ex.Message
});
}
}
And I have CreateOrUpdate method defined as below in VehicleInfoManager class
public async Task CreateOrUpdate(string Entity, int EntityId)
{
//do some stuff
var task = Task.Run(() => Test(Entity, EntityId));
//do other stuff
await task;
//some more stuff
}
And Test method is as follows
private void Test(string Entity, int EntityId)
{
List<VehicleInfo> addList; List<VehicleInfo> updateList;
try
{
this.GetAddAndUpdateList(Entity, EntityId, out addList, out updateList);
if ((addList != null) && (addList.Count > 0))
using (var cont = this.UnitOfWork.Context)
{
foreach (var a in addList)
{
cont.VehicleInfos.Add(a);
}
cont.SaveChanges();
}
if ((updateList != null) && (updateList.Count > 0))
using (var cont = this.UnitOfWork.Context)
{
foreach (var a in updateList)
{
var aa = cont.VehicleInfos?.Where(x => x.VehicleInfoId == a.VehicleInfoId)?.FirstOrDefault();
aa.Address_City = a.Address_City;
aa.Address_Country = a.Address_Country;
aa.Address_StateCode = a.Address_StateCode;
aa.Address_Street1 = a.Address_Street1;
aa.Address_Street2 = a.Address_Street2;
aa.Address_Zip = a.Address_Zip;
aa.ChassisYear = a.ChassisYear;
aa.EngineFamilyName = a.EngineFamilyName;
aa.Entity = a.Entity;
aa.EntityId = a.EntityId;
aa.InputFieldEntity = a.InputFieldEntity;
aa.InputFieldEntityId = a.InputFieldEntityId;
aa.InputFieldGroup = a.InputFieldGroup;
aa.LicensePlate = a.LicensePlate;
aa.Manufacturer = a.Manufacturer;
aa.ModelYear = a.ModelYear;
aa.PurchasedDate = a.PurchasedDate;
aa.RegHoldClearBy = a.RegHoldClearBy;
aa.RegHoldClearDate = a.RegHoldClearDate;
aa.RegHoldComment = a.RegHoldComment;
aa.RegHoldSet = a.RegHoldSet;
aa.RegHoldSetBy = a.RegHoldSetBy;
aa.RegHoldSetDate = a.RegHoldSetDate;
aa.TrailerPlate = a.TrailerPlate;
aa.UpdatedBy = a.UpdatedBy;
aa.UpdatedDate = a.UpdatedDate;
aa.VehicleId = a.VehicleId;
aa.VehicleOperator = a.VehicleOperator;
aa.VehicleOwner = a.VehicleOwner;
aa.VIN = a.VIN;
}
cont.SaveChanges();
}
}
catch (Exception ex)
{
ARB.Logging.LogHandler.LogError(9001, "CreateOrUpdate(string Entity, int EntityId) in class VehicleInfoManager", ex);
throw ex;
}
}
What I want is, I want two things here
the Post method to call or start the CreateOrUpdate method as background call but instead of waiting until the CreateOrUpdate method finishes, it should return the result data to UI and continue the big task CreateOrUpdate in the background.
Is there anyway to start the background method CreateOrUpdate after sometime like (10 mins etc) post method returns to UI, if it can't be done, its OK we don't have to worry but just asking if there is anyway to trigger this from within the same application
When I implemented it in the above way, even after using System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem I am still getting the null http context at the following location
user = System.Web.HttpContext.Current.User.Identity.Name; url = System.Web.HttpContext.Current.Request.Url.ToString();
System.Web.HttpContext.Current is coming out as null.
and the application is breaking,
I chaned my async call to the following to use HostingEnvironment.QueueBackgroundWorkItem, still the same htt current context is coming out as null, any help please
System.Web.Hosting.HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken =>
{
await this.DomainLogicUnitOfWork.VehicleInfoManager.CreateOrUpdate(Entity, EntityId);
});
Thank you
System.Web.HttpContext.Current is maintained by the ASP.NET's SynchronizationContext.
When you start a new Task, the code will be executing on another thread pool thread without a SynchronizationContext and the value of System.Web.HttpContext.Current is not safe to use, whatever its value.
When the execution of the action method (Post) ends, the request will end and the HttpContext instance will be invalid, even if you mange to get a reference to it.
Also, there is no guarantee that that code you posted to the thread pool will run to complete, since it's out of ASP.NET control and ASP.NET won't be aware of it if IIS decides, for some reason, to recycle the application pool or the web application.
If you to post background work, use HostingEnvironment.QueueBackgroundWorkItem. Beware if its constraints.
I want to be able to pass FieldName as a string into Method that updates a table. I have looked at Linq.Expressions but this doesn't seem to expose all the details I need about the field Name and dataType and I don't really want to go through the existing string builder solution that executes a sql command directly.
public static Main(string[] args)
{
UpdateLicenceField(2284, "laststoragefullalert", DateTime.Now);
UpdateLicenceField(2284, "numberofalerts", int(tooMany);
UpdateLicenceField(2284, "lastalertmessage", "Oops");
}
public static void UpdateLicenceField(int LicenceID, string FieldName, object value)
{
using (myContext db = new MyContext())
{
Licence licence = db.Licence.Where(x => x.ID == LicenceID &&
x.Deleted == false).FirstOrDefault();
// so db.Licence has fields .laststoragefullalert .numberofalerts .lastalertmessage
// (in real life this has hundred of settings and we very often just want to update one)
// I'm tring to get a single Method that will act dynamically like the current ADO function that creates a SQL string and executes that.
// 1. check that the FieldName type is the same type as the object passed in value.
// 2. update that FieldName with value and Saves the Licence table.
}
}
You can use "System.Linq.Dynamic.Core" library from https://github.com/StefH/System.Linq.Dynamic.Core and use:
Licence licence = db.Licence.Where(FieldName + "==#0", fieldValue).FirstOrDefault();
or use a method like this:
public Expression<Func<TEntity, bool>> GetPredicate(string methodName, string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(TEntity), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod(methodName, new[] { typeof(string) });
if (method == null)
{
var containsMethods = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public).Where(m => m.Name == methodName);
foreach (var m in containsMethods)
{
if (m.GetParameters().Count() == 2)
{
method = m;
break;
}
}
}
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);
return Expression.Lambda<Func<TEntity, bool>>(containsMethodExp, parameterExp);
}
Licence licence = db.Licence.Where(GetPredicate("Contains", fieldName, fieldValue)).FirstOrDefault();
I hope it helps you.
Thanks very much for your responses. Using a bit from both Renan and Roberto's reply I put something together that does the job. It's obviously not ideal to update in this way but this is instead of having 50 or 60 functions with similar code within an automated process. There's still error checking and validations to be added but this will reduce my codebase considerably.
Thanks again Glenn.
public static int Main(string[] args)
{
UpdateLicenceField(2284, "LastStorageFullAlert", DateTime.Now);
UpdateLicenceField(2284, "IsProcessing", true);
UpdateLicenceField(2284, "RegSource", "this is a string");
UpdateLicenceField(2284, "ProcessFilesDelay", 200);
return 1;
}
public static void UpdateLicenceField(int LicenceID, string Field, object Value)
{
using (BACCloudModel BACdb = new BACCloudModel())
{
Licence licence = BACdb.Licence.Where(x => x.ID == LicenceID && x.Deleted == false).FirstOrDefault();
if (licence != null)
{
var fieldvalue = licence.GetType().GetProperty(Field);
var ftype = fieldvalue.PropertyType;
var vtype = Value.GetType();
if (ftype == vtype)
{
object[] Values = { Value };
licence.GetType().GetProperty(Field).SetMethod.Invoke(licence, Values);
BACdb.SaveChanges();
}
}
}
}
I am basically trying to handle unique constraint validation in my .Net API. I have two unique key cosntraints on two fields in my table.
If you see my code below , I am trying to check if the record exists. I am looking at returning a boolean value if the record exist. is that possible. For the time being, I am returning null.
Is this the best way to do it.
[HttpPost]
[SkipTokenAuthorization]
[Route("api/classificationoverrides/create")]
public IHttpActionResult Create(ClassificationItemViewModelCreate model)
{
var mgrClassificationService = GetService<MGR_STRT_CLASSIFICATION>();
var isExists = mgrClassificationService.Where(x =>
x.MANAGERSTRATEGYID == model.ManagerStrategyId && x.PRODUCT_ID == model.ProductId).FirstOrDefault();
if (isExists == null)
{
var mgrClassficationOverride = new MGR_STRT_CLASSIFICATION();
if (model != null)
{
mgrClassficationOverride.PRODUCT_ID = model.ProductId;
mgrClassficationOverride.LEGACY_STRATEGY_ID = model.LegacyStrategyId;
mgrClassficationOverride.STRATEGY_ID = model.StrategyId;
mgrClassficationOverride.MANAGERSTRATEGY_TYPE_ID = model.ManagerStrategyTypeId;
mgrClassficationOverride.MANAGERSTRATEGYID = model.ManagerStrategyId;
mgrClassficationOverride = mgrClassificationService.Create(mgrClassficationOverride);
}
return Ok(mgrClassficationOverride);
}
else
{
return null;
}
}
I have a fairly simple method:
public static LinkItemCollection ToList<T>(this LinkItemCollection linkItemCollection)
{
var liCollection = linkItemCollection.ToList(true);
var newCollection = new LinkItemCollection();
foreach (var linkItem in liCollection)
{
var contentReference = linkItem.ToContentReference();
if (contentReference == null || contentReference == ContentReference.EmptyReference)
continue;
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
IContentData content = null;
var epiObject = contentLoader.TryGet(contentReference, out content);
if (content is T)
newCollection.Add(linkItem);
}
return newCollection;
}
This works fine - I can call the method and provide a type as T. However, what I want to be able to do is to be able to specify multiple types. I therefore, wrongly, assumed I could refactor the method to:
public static LinkItemCollection ToList(this LinkItemCollection linkItemCollection, Type[] types)
{
var liCollection = linkItemCollection.ToList(true);
var newCollection = new LinkItemCollection();
foreach (var linkItem in liCollection)
{
var contentReference = linkItem.ToContentReference();
if (contentReference == null || contentReference == ContentReference.EmptyReference)
continue;
foreach (var type in types)
{
var contentLoader = ServiceLocator.Current.GetInstance<IContentLoader>();
IContentData content = null;
var epiObject = contentLoader.TryGet(contentReference, out content);
if (content is type)
newCollection.Add(linkItem);
}
}
return newCollection;
}
However, Visual Studio is showing it cannot resolve the symbol for type on the line if(content is type).
I know I'm doing something wrong, and I'm guessing I need to use Reflection here.
What you're looking for is:
type.IsAssignableFrom(content.GetType())
is is only used for checking against a type known at compile time, not at runtime.
Question:
I wrote a method to retrieve a SQL result as a list of a class instead of a datatable.
The problem is, I have a int field in the database, which is nullable.
If I hit a row with a NULL int, DataReader returns DbNull.Value instead of null.
So System.Convert.ChangeType(objVal, fi.FieldType) throws an exception, because it can't convert DbNull to an int.
So far so bad.
I thought I had solved the problem, when I just compared objVal to DbNull.Value and if true, did this instead:
System.Convert.ChangeType(null, fi.FieldType)
unfortunately, I just realized, the resulting integer type is 0 instead of NULL.
So I just tried changing the int type in my class to Nullable<int>, but now I have the problem that when a value is not DbNull.Value, ChangeType throws an exception because it can't convert int to nullable<int>...
So now I try to detect the type of the object returned by datareader, and convert it to a nullable value.
tTypeForNullable is correctly shown as Nullable<int>.
But when I look at the result type, I get: int.
Why is that ? And more important: How can I do that properly ?
Please note that because type is an object, I can't use a generic method to create Nullable<int>.
bool bisnull = IsNullable(objVal);
bool bisnullt = IsNullable(fi.FieldType);
if (bisnullt)
{
Type tTypeForNullable = typeof(Nullable<>).MakeGenericType(objVal.GetType());
//object result = Activator.CreateInstance(tTypeForNullable, new object[] { objVal });
//object result = Activator.CreateInstance(typeof(Nullable<int>), new object[] { objVal });
object result = Activator.CreateInstance(tTypeForNullable, objVal);
Type tres = result.GetType();
fi.SetValue(tThisValue, System.Convert.ChangeType(result, fi.FieldType));
}
Here's the complete routine for reference:
public virtual System.Collections.Generic.IList<T> GetList<T>(System.Data.IDbCommand cmd)
{
System.Collections.Generic.List<T> lsReturnValue = new System.Collections.Generic.List<T>();
T tThisValue = default(T);
Type t = typeof(T);
lock (cmd)
{
using (System.Data.IDataReader idr = ExecuteReader(cmd))
{
lock (idr)
{
while (idr.Read())
{
//idr.GetOrdinal("")
tThisValue = Activator.CreateInstance<T>();
// Console.WriteLine(idr.FieldCount);
for (int i = 0; i < idr.FieldCount; ++i)
{
string strName = idr.GetName(i);
object objVal = idr.GetValue(i);
System.Reflection.FieldInfo fi = t.GetField(strName);
//Type tttttt = fi.FieldType;
if (fi != null)
{
//fi.SetValue(tThisValue, System.Convert.ChangeType(objVal, fi.FieldType));
if (objVal == System.DBNull.Value)
{
objVal = null;
fi.SetValue(tThisValue, null);
}
else
{
//System.ComponentModel.TypeConverter conv = System.ComponentModel.TypeDescriptor.GetConverter(fi.FieldType);
bool bisnull = IsNullable(objVal);
bool bisnullt = IsNullable(fi.FieldType);
if (bisnullt)
{
Type tTypeForNullable = typeof(Nullable<>).MakeGenericType(objVal.GetType());
//object result = Activator.CreateInstance(tTypeForNullable, new object[] { objVal });
//object result = Activator.CreateInstance(typeof(Nullable<int>), new object[] { objVal });
object result = Activator.CreateInstance(tTypeForNullable, objVal);
Type tres = result.GetType();
fi.SetValue(tThisValue, System.Convert.ChangeType(result, fi.FieldType));
}
fi.SetValue(tThisValue, System.Convert.ChangeType(objVal, fi.FieldType));
}
}
else
{
System.Reflection.PropertyInfo pi = t.GetProperty(strName);
if (pi != null)
{
//pi.SetValue(tThisValue, System.Convert.ChangeType(objVal, pi.PropertyType), null);
if (objVal == System.DBNull.Value)
{
objVal = null;
pi.SetValue(tThisValue, null, null);
}
else
{
pi.SetValue(tThisValue, System.Convert.ChangeType(objVal, pi.PropertyType), null);
}
}
// Else silently ignore value
} // End else of if (fi != null)
//Console.WriteLine(strName);
} // Next i
lsReturnValue.Add(tThisValue);
} // Whend
idr.Close();
} // End Lock idr
} // End Using idr
} // End lock cmd
return lsReturnValue;
} // End Function GetList
with this:
public System.Data.IDataReader ExecuteReader(System.Data.IDbCommand cmd)
{
System.Data.IDataReader idr = null;
lock(cmd)
{
System.Data.IDbConnection idbc = GetConnection();
cmd.Connection = idbc;
if (cmd.Connection.State != System.Data.ConnectionState.Open)
cmd.Connection.Open();
idr = cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
} // End Lock cmd
return idr;
} // End Function ExecuteReader
Please note that because type is an object, I can't use a generic method to create Nullable<int>.
You're boxing - and the result of a boxing operation for a nullable value type is never a boxed value of that type. It's either null or a non-nullable value type. See MSDN for more information.