Background:
Bit of a strange one. Using SSIS to deal with data coming from an API, my first script gets the data and puts it against a strongly typed model called "TestModel"
The SSIS package then saves the resulting information to a variable which is then passed into the next part of the SSIS package. Being SSIS name spaces are not carried around so each script task is in isolation.
Script Task One Namespace: ST_a048e0de86e1432d8da6f60b5d7055db
Script Task Two Namespace: SC_0573a66ec6c0486a98ee00cea4365654
The Issue:
The second SSIS script task picks up the variable and reads it fine, I can in debug see all my rows and all the relevant data. Now things start getting weird, the type of the List when it reaches my second script
object {System.Collections.Generic.List<ST_a048e0de86e1432d8da6f60b5d7055db.TestModel>}
Each of the objects in this Generic.List has the Type of:
ST_a048e0de86e1432d8da6f60b5d7055db.TestModel
What can I do with this list? Pretty much nothing...
I have duplicated the TestModel.cs from script namespace one into namespace two hoping that it would just match on nicely if I created a new list and passed the object too it, alas no.
What I have tried so far:
IEnumerable e = data as List<TestModel>; //Returns 0 rows
IEnumerable list = (IEnumerable)data; // returns all rows, type System.Collections.IEnumerable {System.Collections.Generic.List<ST_a048e0de86e1432d8da6f60b5d7055db.TestModel>}
List<TestModel> listtest = ((TestModel[])data).ToList() // Runtime error
List<TestModel> listtest2 = list.Cast<TestModel>().ToList(); //Runtime error - Unable to cast type 'ST_a048e0de86e1432d8da6f60b5d7055db.TestModel' to type 'SC_0573a66ec6c0486a98ee00cea4365654.TestModel'
My end goal is that I need something I can loop through and manipulate into an object SSIS can digest. That part is easy but getting it to loop is proving very difficult!
Extra Note: SSIS Packages are really annoying with dependencies so really looking to avoid using anything special. Also the name spaces are 100% isolated from on another no communication between the two is possible.
Try using Enumerable.OfType Method (IEnumerable)
List<TestModel> list = ((IEnumerable)data).OfType<TestModel>().ToList();
UPDATE:
IEnumerable list = (IEnumerable)data;
foreach(var testModel in list)
{
// manipulate testModel into an object SSIS can digest
}
You could try using AutoMapper to map your TestModel classes to each other. It's easy to setup and use.
This is off the top of my head so it might need tweaking, but the code would look very similar to this:
var config = new MapperConfiguration(cfg => {
CreateMap<ST_a048e0de86e1432d8da6f60b5d7055db.TestModel
, SC_0573a66ec6c0486a98ee00cea4365654.TestModel>();
cfg.AddProfile<testModel>();
});
var mapper = config.CreateMapper();
Then you could convert the listTest using the mapper like this:
var convertedList = mapper.Map<ST_a048e0de86e1432d8da6f60b5d7055db.TestModel>
, IList<SC_0573a66ec6c0486a98ee00cea4365654.TestModel>>(listTest);
EDIT: Attempt 2
You said you tried moving the TestModel class to the second namespace. If not you may need to add it in or something equivalent. Then it might be as simple as trying this:
var listTest = data.Select(x => new TestModel
{ property1 = x.property1,
property2 = x.property2
//etc...
}).ToList();
That should give you a new List<TestModel>
I wanted to expand on John Ephraim Tugado's solution since it seemed a little incomplete
public void Main()
{
try
{
List<TestModel> testModelList = BuildObjectList();
TestModel testModel = new TestModel();
testModel.prop1 = "new prop";
testModel.prop2 = true;
testModelList.Add(testModel);
//Now that we have a testModelList in the correct local class we can
//modify it and load it back into the globally held variable for
//another Component to use
Dts.Variables["User::myObjectList"].Value = testModelList;
Dts.TaskResult = (int)ScriptResults.Success;
}
catch
{
Dts.TaskResult = (int)ScriptResults.Failure;
}
}
private List<TestModel> BuildObjectList()
{
try
{
List<TestModel> RunningList = new List<TestModel>();
TestModel localModel = new TestModel();
var data = Dts.Variables["User::myObjectList"].Value;
IEnumerable enumDataList = (IEnumerable)data;
foreach (var currentObj in enumDataList)
{
localModel = GetSingleResult(currentObj);
RunningList.Add(localModel);
localModel = new TestModel();
}
return RunningList;
}
catch
{
return new List<TestModel>();
}
}
private TestModel GetSingleResult(object currentObj)
{
try
{
TestModel returnedResult = new TestModel();
PropertyInfo[] properties = currentObj.GetType().GetProperties();
foreach (PropertyInfo pi in properties)
{
switch (pi.Name)
{
case "prop1":
returnedResult.prop1 = pi.GetValue(currentObj, null).ToString();
break;
case "prop2":
returnedResult.prop2 = Convert.ToBoolean(pi.GetValue(currentObj, null));
break;
default:
break;
}
}
return returnedResult;
}
catch {
return new TestModel();
}
}
internal class TestModel
{
internal string prop1 { get; set; }
internal bool prop2 { get; set; }
}
Related
As a headsup, I'm new to stackoverflow so please do tell me if I'm just bad at searching for a solution.
I have a class library that's able to read csv files depending on the public members of a class and then assign them. However, I don't know a way to avoid a long switch or if else when I want to call/invoke the method.
Here's how I currently use it:
public class DataSetup
{
List<object> ObjList = new List<object>();
public DataSetup(string file)
{
switch (file)
{
case "Persons.csv":
AssignCsvData<Person>(file);
break;
case "Address.csv":
AssignCsvData<Address>(file);
break;
}
}
public void AssignCsvData<T>(string file) where T : CsvableBase, new()
{
var cr = new CsvReader<T>();
var csvObjs = cr.Read(file);
foreach (var obj in csvObjs)
{
ObjList.Add(obj);
}
}
}
The switch is not "done", since I'm hoping for a better way.
So is there a way to call the AssignCsvData without the switch?
So far I've tried taking a type as a parameter for the constructor, but I can't use it as a type when it's a variable.
If the name of the file always represents a class. In your case there is a mismatch between Persons.csv and Person, but say they do match then the following could work using reflection, rather than a switch statement:
var type = Type.GetType(Path.GetFileNameWithoutExtension(file));
var method = typeof(Processor)
.GetMethod("AssignCsvData")
.MakeGenericMethod(type);
method.Invoke(new Processor(), file);
NOTE: If you'd like to test this in LinqPad, don't forget to use the "UserQuery+" namespace to get the type. i.e.:
var type = Type.GetType($"UserQuery+{Path.GetFileNameWithoutExtension(file)}");
I made a sandbox example that generates the exception:
public class Account
{
public string Status { get; set; }
}
[Fact]
public void MyTest()
{
dynamic scope = new ExpandoObject();
scope.Account = new Account();
scope.Account.Status = "test0";
// Can we get the value of the property?
var result = new CompiledExpression("Account.Status").ScopeCompile().DynamicInvoke((object)scope);
Assert.Same("test0", result); // Yes we can!
// Can we set the value of the property?
new CompiledExpression("Account.Status = 'test1'").ScopeCompile().DynamicInvoke((object)scope); // This throws an exception
Assert.Same("test1", ((string)(scope.Account.Status)));
}
If it's not clear I have a scope ExpandoObject that has properties that are not dynamic - and I'm trying to set a property (Status) of a property (Account) through parsing an expression. What am I doing wrong/why am I getting an exception setting when getting works fine?
I'm using this library:
https://csharpeval.codeplex.com/
> Install-Package ExpressionEvaluator
Alternatively, if there's a better library to do this (I've already given up on Simpro) then let me know and I can use it, instead.
(I'm using xunit as my tester, but I don't think that matters - hence [Fact] and not MsTest attributes.)
Per the documentation for CompiledExpression you need to use single quotes for string literals. So, the setter should be:
new CompiledExpression("Account.Status = 'test1'").ScopeCompile()
.DynamicInvoke((object)scope);
EDIT:
I've got this working:
var exp = new CompiledExpression("Status = 'test1'").ScopeCompile<Account>();
exp(scope.Account);
This also works:
var x = new CompiledExpression("Status = 'test2'").ScopeCompile<Account>();
var y = new CompiledExpression("Account").ScopeCompile();
x(y(scope));
I asked a similar question to this over on Unity3D Q&A, but I didn't get a response. I've finally got back round to this project and still have the same issue.
Link To Unity Question
I have searched, but I don't think I've hit the right keywords cause I still haven't found an answer that fits. Anyway, I will ask the question a different way and hopefully someone can point me in the right direction.
So below is the code that I have come up with, it's not the actual code in the game, but it does a good job at showing my problem.
Basically, in the build() method I want to check that both lists each contain the same type of tool, without actually hard coding the type. I don't care about the specific tool instance, just that it is of a certain type. The aim is that I can create new tools without having to modify the build method to incorporate these new types.
If there is a better way to do it I'm all ears.
Thanks
namespace TypeExample
{
class Tool
{
}
class Spanner : Tool
{
}
class Wrench : Tool
{
}
class Builder
{
List<Tool> toolsAvailable = new List<Tool>();
List<Tool> toolsRequired = new List<Tool>();
public Builder()
{
Spanner spanner = new Spanner();
Wrench wrench = new Wrench();
toolsRequired.Add(spanner);
toolsRequired.Add(wrench);
}
public void GiveTool(Tool tool)
{
toolsAvailable.Add(tool);
}
public void build()
{
// if true
Console.WriteLine("Building");
// else
Console.WriteLine("I don't have the tools to build!");
}
}
class Program
{
static void Main(string[] args)
{
Spanner spanner = new Spanner();
Wrench wrench = new Wrench();
Builder builder = new Builder();
builder.GiveTool(spanner);
builder.GiveTool(wrench);
builder.build();
Console.ReadLine();
}
}
}
Basicly you should get all the types from collections using Linq and then compair the result.
var toolsAvailableTypes = toolsAvailable.Select(t => t.GetType()).Distinct();
var toolsRequiredTypes = toolsRequired.Select(t => t.GetType()).Distinct();
if (toolsRequiredTypes.Except(toolsAvailableTypes).Any())
{
//building
}
It is not clear, should you compair only types of instruments or quantity also. My answer assume you don't care about quantity and you can build with one spanner when you require two.
-Update
To comply the requirement about subclasses (Sriram Sakthivel mentioned it) you can check if available tool type is subclass to required tool type so you can use any SpecialSpanner when you need a Spanner
var toolsAvailableTypes = toolsAvailable.Select(t => t.GetType()).Distinct().ToList();
var toolsRequiredTypes = toolsRequired.Select(t => t.GetType()).Distinct().ToList();
if (CanBuild(toolsAvailableTypes, toolsRequiredTypes))
{
Console.WriteLine("building");
}
else
{
Console.WriteLine("not enough minerals");
}
CanBuild method:
bool CanBuild(List<Type> toolsAvailableTypes, List<Type> toolsRequiredTypes)
{
foreach (var requiredType in toolsRequiredTypes)
{
bool isAvailable = toolsAvailableTypes.Any(availableType => availableType.IsSubclassOf(requiredType) || availableType == requiredType);
if (!isAvailable)
{
return false;
}
}
return true;
}
var reqdTypes = toolsRequired.Select(x => x.GetType());
var availableTypes = toolsAvailable.Select(x => x.GetType());
if (reqdTypes.Except(availableTypes).Any())
{
//Something exist in reqdTypes which is not in availableTypes
}
Note:This will fail if you provide more derived type than expected type. For example if you provide SpecialSpanner in place or Spanner this won't work.
I need some help "fine-tuning" a block of code in my program which populates an array and binds that array to a dropdown list. Here is the code:
using ([SQL Data Connection])
{
var stakes = from st in ddl.STK_Stakes
where st.STK_EVT_FK == eventId
select new
{
st.STK_Description
};
string[] stakeDesc = new string[stakes.Count()];
foreach (var stake in stakes)
{
stakeDesc[stakeCount] = stake.STK_Description;
stakeCount++;
}
foundDDL.DataSource = stakeDesc;
foundDDL.DataBind();
This code populates Drop-Down List "foundDDL" with options only when foundDDL is on the screen. This code works, but as it's currently used, it is executing every time an instance of foundDDL is created on the screen.
Since the options populated in foundDDL will always be the same while on that page, I want to move this code to its own method, which I can then run once at load time, populate my array, and then just feed that pre-populated array to foundDDL as needed. This will reduce the number of calls to my database and make my program that much more efficient.
The issue I'm having is that I can't figure out how to instantiate my array outside the method, since the number of spaces I'll need in the array is subject to change.
Try using a static property?
private static string[] stakeDesc;
public static string[] StakeDesc {
get {
if(stakeDesc == null)
//initialize it
return stakeDesc;
}
}
You don't have to specify how big it is when you declare it, just declare it outside the method and instantiate it as you are already doing, and if it's already instantiated, just return it.
//In global scope somewhere
string[] stakeDesc;
if (stakeDesc==null || OtherReasonToRefreshData())
{
using ([SQL Data Connection])
{
var stakes = from st in ddl.STK_Stakes
where st.STK_EVT_FK == eventId
select new
{
st.STK_Description
};
stakeDesc = new string[stakes.Count()];
foreach (var stake in stakes)
{
stakeDesc[stakeCount] = stake.STK_Description;
stakeCount++;
}
}
}
foundDDL.DataSource = stakeDesc;
foundDDL.DataBind();
Note you can instantiate the array with 10 elements, than reassign it to a new array with 50 elements without issue.
Because the information does not change, you should be caching this information instead of hitting the DB.
Roughly the code should go (have not compiled this):
class MyCodeBehindClass
{
private List<string> _StakeDesc;//use a dynamically sized array
private List<string> StakeDesc
{
get
{
if (Session[StakeDescKey] != null)
_StakeDesc = (List<string>)Session[StakeDescKey];
else
{
_StakeDesc = GetStakeDesc();
}
return _StakeDesc;
}
set { _StakeDesc = value; }
}
private List<string> GetStakeDesc()
{
using ([SQL Data Connection])
{
var stakes = from st in ddl.STK_Stakes
where st.STK_EVT_FK == eventId
select new
{
st.STK_Description
};
Session[StakeDescKey] = stakes.ToList();//Populate cache
}
}
private void PageLoad()
{
//if not postback...
foundDDL.DataSource = StakeDesc;//Smart property that checks cache
foundDDL.DataBind();
}
}
I'm trying to use Weka in my C# application. I've used IKVM to bring the Java parts into my .NET application. This seems to be working quite well. However, I am at a loss when it comes to Weka's API. How exactly do I classify instances if they are programmatically passed around in my application and not available as ARFF files.
Basically, I am trying to integrate a simple co-reference analysis using Weka's classifiers. I've built the classification model in Weka directly and saved it to disk, from where my .NET application opens it and uses the IKVM port of Weka to predict the class value.
Here is what I've got so far:
// This is the "entry" method for the classification method
public IEnumerable<AttributedTokenDecorator> Execute(IEnumerable<TokenPair> items)
{
TokenPair[] pairs = items.ToArray();
Classifier model = ReadModel(); // reads the Weka generated model
FastVector fv = CreateFastVector(pairs);
Instances instances = new Instances("licora", fv, pairs.Length);
CreateInstances(instances, pairs);
for(int i = 0; i < instances.numInstances(); i++)
{
Instance instance = instances.instance(i);
double classification = model.classifyInstance(instance); // array index out of bounds?
if(AsBoolean(classification))
MakeCoreferent(pairs[i]);
}
throw new NotImplementedException(); // TODO
}
// This is a helper method to create instances from the internal model files
private static void CreateInstances(Instances instances, IEnumerable<TokenPair> pairs)
{
instances.setClassIndex(instances.numAttributes() - 1);
foreach(var pair in pairs)
{
var instance = new Instance(instances.numAttributes());
instance.setDataset(instances);
for (int i = 0; i < instances.numAttributes(); i++)
{
var attribute = instances.attribute(i);
if (pair.Features.ContainsKey(attribute.name()) && pair.Features[attribute.name()] != null)
{
var value = pair.Features[attribute.name()];
if (attribute.isNumeric()) instance.setValue(attribute, Convert.ToDouble(value));
else instance.setValue(attribute, value.ToString());
}
else
{
instance.setMissing(attribute);
}
}
//instance.setClassMissing();
instances.add(instance);
}
}
// This creates the data set's attributes vector
private FastVector CreateFastVector(TokenPair[] pairs)
{
var fv = new FastVector();
foreach (var attribute in _features)
{
Attribute att;
if (attribute.Type.Equals(ArffType.Nominal))
{
var values = new FastVector();
ExtractValues(values, pairs, attribute.FeatureName);
att = new Attribute(attribute.FeatureName, values);
}
else
att = new Attribute(attribute.FeatureName);
fv.addElement(att);
}
{
var classValues = new FastVector(2);
classValues.addElement("0");
classValues.addElement("1");
var classAttribute = new Attribute("isCoref", classValues);
fv.addElement(classAttribute);
}
return fv;
}
// This extracts observed values for nominal attributes
private static void ExtractValues(FastVector values, IEnumerable<TokenPair> pairs, string featureName)
{
var strings = (from x in pairs
where x.Features.ContainsKey(featureName) && x.Features[featureName] != null
select x.Features[featureName].ToString())
.Distinct().ToArray();
foreach (var s in strings)
values.addElement(s);
}
private Classifier ReadModel()
{
return (Classifier) SerializationHelper.read(_model);
}
private static bool AsBoolean(double classifyInstance)
{
return classifyInstance >= 0.5;
}
For some reason, Weka throws an IndexOutOfRangeException when I call model.classifyInstance(instance). I have no idea why, nor can I come up with an idea how to rectify this issue.
I am hoping someone might know where I went wrong. The only documentation for Weka I found relies on ARFF files for prediction, and I don't really want to go there.
For some odd reason, this exception was raised by the DTNB classifier (I was using three in a majority vote classification model). Apparently, not using DTNB "fixed" the issue.