I am trying to create a telerik reporting doughnut chart. The problem is, my values aren't in the format expected.
My data looks like this:
{ GoodHours: 120, Downtime: 43.5, PlannedTime: 12.77 }
It seems the way the charts are set up is to expect data like this:
{
Time: 60, Type: "GoodHours",
Time: 45, Type: "GoodHours",
Time: 43.5, Type: "Downtime",
Time: 15, Type: "GoodHours",
Time: 12.77, Type: "PlannedTime"
}
The reason my data is formatted this way is because it comes from a rather complex Stored Procedure that does the record aggregation itself before sending the data to the report. It's much faster to allow MsSql to crunch the numbers than getting telerik reporting to do it.
I have no clue how to even begin setting up the chart.
I followed the online instructions for creating a doughnut (pie) chart, but it assumes my data is not already digested. I tried adding multiple Series but they ended up being displayed on different levels, sort of like doughnuts within doughnuts.
How would I set this up?
First, write your stored procedure and call it from your C# code.
Create a serializable object to store your data from the SP.
[Serializable()]
public class reportTimeTypeObj
{
public decimal time { get; set; }
public string type { get; set; }
}
Then create a function that will consume the data and transform it into the format required.
public List<reportTimeTypeObj> getTimeSpentPatientByVisitTypeObj()
{
//Create a list of objects for your donut.
reportTimeTypeObj list = new List<reportTimeTypeObj>();
//Add code to call stored procedure here
//ds is the data set returned from the stored procedure
if (ds.Tables.Count > 0)
foreach (DataRow dr in ds.Tables[0].Rows)
{
list.Add(new reportTimeSpentPatientByVisitTypeObj()
{
time = dr["time "] != DBNull.Value ?
Convert.ToDecimal(dr["time "]) : 0,
type = dr["type "] != DBNull.Value ?
string.IsNullOrEmpty(dr["visit_type"].ToString()) ?
"Not recorded" :
Convert.ToString(dr["visit_type"]) : "Not recorded"
});
}
return list;
}
Next, create an ObjectDataSource (ODS) component using the Report Designer. Assign the function to the ODS. Follow How to: Create Pie Chart to create your chart.
Next, right click on your pie-chart. Click "Change Chart Type...". In the option shows select donut chart.
Related
Due to a strange behavior in my application, i am forced to reload the designer before calling WorkflowInvoker.Invoke on it.
wd.Flush();
SaveXamlFile(currentXamlPath, wd.Text);
I just flush the content, and write the wd.Text to a file.
//cleanup the previous designer
if (wd != null)
{
wd.ModelChanged -= new EventHandler(Designer_ModelChanged);
}
//designer
wd = new WorkflowDesigner();
designerArea.Child = wd.View;
this.DebuggerService = this.wd.DebugManagerView;
//property grid
propertiesArea.Child = wd.PropertyInspectorView;
//event handler
wd.ModelChanged += new EventHandler(Designer_ModelChanged);
//error service
wd.Context.Services.Publish<IValidationErrorService>(errorService);
wd.Context.Items.Subscribe<Selection>(OnItemSelected);
I then recreate a new instance of the WorkflowDesigner and load the previously saved file.
wd.Load(currentXamlPath);
I call WorkflowInvoker.Invoke and inside my custom activity which derives from CodeActivity i am taking it's name:
OK, fine until now, i have a 1.2 Id there.
I want to update some of the fields of this Activity via its ModelItem in order to display them in the GUI right away.
IEnumerable<ModelItem> activityCollection = currentWorkflow.Find(currentWorkflow.Root, typeof(Activity));
But here comes the issue:
I can't find that my Activity id there. Is now transformed from 1.2 to 2. Why is this happening?
I've tried to send a this reference from my Activity Execute method and searched it by ref but all i get is nulls.
ModelItem temp = activityCollection.FirstOrDefault((m) => (m.GetCurrentValue() == a));
I am sure i am missing something here, but i can't figure out what is it.
I found a workaround on this :
On my custom activities i am adding a Guid property and I override CacheMetadata:
public Guid unique { get; set; }
protected override void CacheMetadata(CodeActivityMetadata metadata)
{
if (unique.ToString() == "00000000-0000-0000-0000-000000000000")
unique = Guid.NewGuid();
}
When i drag the activity on the designer, the unique id is generated. I make sure that this portion of code is called only once.
Why is that?
Because after a call like this,
IEnumerable<ModelItem> activityCollection = currentWorkflow.Find(currentWorkflow.Root, typeof(Activity));
each model in the activity collection contains that property ( unique of type Guid ) with the value of the first assignment made in CacheMetadata. I can't explain this behavior, i've just taken it into consideration.
Who calls again that CacheMetadata ? something like this :
Activity root = ActivityXamlServices.Load(currentXamlPath);
WorkflowInspectionServices.CacheMetadata(root);
And so, the Guid is changed and its utility is gone.
This way, i am able to get the ModelItem for my custom activity and update some of its properties which are immediately displayed in the GUI.
I have two pick-list in Car details entity. I'm setting the Model (cir_model) Picklist value with from the input parameter (that is CrmNumber) of Custom Workflow activity and it's working as expected, and the second pick-list Marque (cir_marque) will be set logically using the Model pick-list.
Logic should be if Model is set to 'Ac Ace' then Marque should be set to 'Ac'. Take value 'Ac' using Split() from the string 'Ac Ace'.
Normally in C# this can be done easily but in CRM 4.0 how this can be achieve (How I'll set 'Ac' to Marque)
public static DependencyProperty modelProperty = DependencyProperty.Register("model",
typeof(int), typeof(CreateCardetails));
[CrmInput("Model")]
public int model
{
get
{
return (int)base.GetValue(modelProperty);
}
set
{
base.SetValue(modelProperty, value);
}
}
public static DependencyProperty ContactProperty =
DependencyProperty.Register("Contact", typeof(Lookup), typeof(CreateCardetails));
[CrmInput("Contact ID")]
[CrmReferenceTarget("contact")]
public Lookup Contact
{
get
{
return (Lookup)base.GetValue(ContactProperty);
}
set
{
base.SetValue(ContactProperty, value);
}
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext
executionContext)
{
//Create an car details record which will be linked to the contact record
DynamicEntity cardetails = new DynamicEntity("cir_cardetails");
cardetails["cir_carsdetailsid"] = Contact;
//Setting the picklist value of Model
Picklist modelPickList = new Picklist();
modelPickList.Value = model.Value;
cardetails.Properties.Add(new PicklistProperty("cir_model",modelPickList));
/*
Here the logic should be done for setting Marque (cir_model) value
Picklist marquePickList = new Picklist();
marquePickList.Value = ???
cardetails.Properties.Add(new PicklistProperty("cir_marque",marquePickList));
*/
//Creating the car details record
Guid carkey = crmService.Create(cardetails);
}
How we can set the Marque value logically, I have left the code blank for this like below
/*
Here the logic should be done for setting Marque (cir_marque) value
Picklist marquePickList = new Picklist();
marquePickList.Value = ???
cardetails.Properties.Add(new PicklistProperty("cir_marque",marquePickList));
*/
Please arrange to help me out on this, all suggestions are welcome.
There is no language CRM 4.0, in CRM 4.0 you code in c#. The only thing that change is the way you work with new types.
In Workflow you don't work with controls, you work with entities and the related attributes. So you "just" need to get the attribute cir_model, do a subtring and find the available options in Marque and set the corrected value. Check this sample from SDK.
You can use JavaScript or C# (Plug-In, Workflow) to accomplish this. There are some considerations to think of when choosing which approach to use.
If you want the user to be able to see the result in real time (when they select) then you can use JavaScript.
If you don't care for the user to see the result, or there is data coming in from an outside source (not the user form), then think about using a plugin.
I don't think you should have to use a WF to do this, plugins are just as easy to write and will happen instantaneously instead of waiting for the async process to complete.
I'm in the process of building the capability for a user to perform ad-hoc queries on a SQL Server database. The resulting query will take on the following basic form:
SELECT <ONE TO MANY USER SELECTED FIELDS>
FROM <ONE TO MANY TABLES DETERMINED BY FIELDS SELECTED BY USER>
WHERE <ZERO TO MANY CRITERIA FOR THE SELECTED FIELDS>
It's a guarantee that the selection will most likely span more than one table.
Some (not all) of the fields may have 0 or more filter criteria for a particular field.
My application is using the default EF4 classes within ASP.NET MVC 2 using C#. I am currently passing in an object called QueryItem that contains all the information for a particular criteria.
My question(s) are:
What is the best approach for coding this? (Code samples of what I have to date below).
Can this be done with Linq2SQL or should I use ADO.NET(My current approach)
If ADO.NET is the best way, how do you access the DBConnection within EF4?
Note: I intend to refactor this into SQLParameter objects, to protect against SQL injection. My goal right now is best practice in developing the query first.
QueryItem class:
public class QueryItem
{
public bool IsIncluded { get; set; }
public bool IsRequired { get; set; }
public string LabelText { get; set; }
public string DatabaseLoc { get; set; }
public List<string> SelectedValue { get; set; }
public List<SelectListItem> SelectList { get; set; }
}
Query Parsing Code
foreach(QueryItem qi in viewModel.StandardQueryItems)
{
string[] dLoc = qi.DatabaseLoc.Split(new Char[] { '.' }); //Split the table.fieldname value into a string array
if(qi.IsIncluded == true) //Check if the field is marked for inclusion in the final query
{
fields.Append(qi.DatabaseLoc + ","); //Append table.fieldname to SELECT statement
if(!tables.ToString().Contains(dLoc[0])) // Confirm that the table name has not already been added to the FROM statement
{
tables.Append(dLoc[0] + ","); //Append the table value to the FROM statement
}
}
if(qi.SelectedValue != null)
{
if(qi.SelectedValue.Count == 1)
{
query.Append(qi.DatabaseLoc + " = '" + qi.SelectedValue[0].ToString() + "'");
}
else
{
foreach(string s in qi.SelectedValue)
{
//Needs to handle "IN" case properly
query.Append(qi.DatabaseLoc + " IN " + qi.SelectedValue.ToString());
}
}
}
}
I have built a similar system to what you are describing in the past by passing in a single parameter to a stored procedure of type xml. By doing so, you can actually specify(in xml), what all you would like to report off of and build the SQL necessary to return the results you want.
This also makes your C# code easier, as all you have to do is generate some xml that your procedure will read. Generating Dynamic SQL is definitely not something you should use unless you have to, but when you want to allow users to dynamically select what they want to report off of, it's pretty much the only way to go about doing it.
Another option for you might be to look into Reporting Services - that will allow the user to pick what fields they want to view and save that particular 'report' in their own section where they can then go back and run it again at any time.. You could also create the reports for them if they aren't computer savvy(which is a lot easier to do with report builder, provided that all they need is data and no special features).
Either way you go about it, their are pros and cons to both solutions.. You'll have to determine which option is best for you.
xml/dynamic sql: Hard to maintain/make changes to.(I feel sorry for anyone who has to come behind someone who is generating dynamic sql and try to understand the logic behind the mess).
reporting services: very easy to spit out reports that look good, but it's a little less flexible and it's not free.
Is it possible to get the values for a TestCaseAttribute from an external data source such as an Excel Spreadsheet, CSV file or Database? i.e. Have a .csv file with 1 row of data per test case and pass that data to NUnit one at a time.
Here's the specific situation that I'd like to use this for. I'm currently merging some features from one system into another. This is pretty much just a copy and paste process from the old system into the new one. Unfortunately, the code being moved not only does not have any tests, but is not written in a testable manner (i.e. tightly coupled with the database and other code.) Taking the time to make the code testable isn't really possible since its a big mess, i'm on a tight schedule and the entire feature is scheduled to be re-written from the ground up in the next 6-9 months. However, since I don't like the idea of not having any tests around the code, I'm going to create some simple Selenium tests using WebDriver to test the page through the UI. While this is not ideal, it's better than nothing.
The page in question has about 10 input values and about 20 values that I need to assert against after the calculations are completed, with about 30 valid combinations of values that I'd like to test. I already have the data in a spreadsheet so it'd be nice to simply be able to pull that out rather than having to re-type it all in Visual Studio.
I was finally able to accomplish what I wanted using NUnit's TestCaseSource attribute. The code looks a little ugly but it works.
Here is an example of pullind the data from a .csv file and passing it to the test method. The test is for the Add method of a simple calculator that takes two ints, adds them together and returns the sum.
Class to load the test data from the file.
public class TestData
{
public int number1 { get; set; }
public int number2 { get; set; }
public int sum { get; set; }
public static IEnumerable TestCases
{
get
{
string inputLine;
using(FileStream inputStream =
new FileStream("C:\\Code\\TestData\\TestData.csv",
FileMode.Open,
FileAccess.Read))
{
StreamReader streamReader = new StreamReader(inputStream);
while((inputLine = streamReader.ReadLine()) != null)
{
var data = inputLine.Split(',');
yield return new TestData {
number1 = Convert.ToInt32(data[0])
,number2 = Convert.ToInt32(data[1])
,sum = Convert.ToInt32(data[2])
};
}
streamReader.Close();
inputStream.Close();
}
}
}
}
Class with the actual tests:
[TestFixture]
public class CalculatorTests
{
[Test]
[TestCaseSource(typeof(TestData), "TestCases")]
public void AddTwoNumbers(TestData data)
{
int sum = Calculator.Add(data.number1, data.number2);
sum.ShouldEqual(data.sum);
}
}
Contents of TestData.csv
4,4,8
15,20,35
8,8,16
5,5,10
42,13,55
It should be fairly simple to modify the get property in the TestData class to pull data from any datasource you want (i.e. Database, Web Service, Excel...)
You could always open your csv file in excel or any spreadsheet tool and then add a new column that concatenates the input/output values into the test case syntax.
Something like: =CONCATENATE("[TestCase(", A1, ",", B1, ",", C1, ",", D1, ",", E1, ")]")
Then copy/paste the column into the code.
I am trying to make a simple plugin for MS Dynamics CRM 4.0 where send data of a salesorder in a SOAP message on the update of the order.
The strange thing is that I get this error every other time i try to save /(execute the plugin).
So when I update (any field) of a salesorder and then save I get the error:
The given key was not present in the dictionary.
When I save again right away after that(without even changing anything in between the two saves) it executes correctly and gives me all data I want. It is really every time the same thing: first save: error, second save: execute correctly.
Any ideas what this could be?
This is the first part of my code; where it actually gets the dataset of the salesorder in this case:
public class CompleteOrderPlugin : IPlugin
{
public void Execute(IPluginExecutionContext context)
{
DynamicEntity entity = null;
if (context.InputParameters.Properties.Contains(ParameterName.Target) &&
context.InputParameters.Properties[ParameterName.Target] is DynamicEntity)
{
entity = (DynamicEntity)context.InputParameters[ParameterName.Target];
if (entity.Name != EntityName.salesorder.ToString()) { return; }
}
else
{
return;
}
The rest is where I use values from attributes to fill my own variables.
I fixed this by first making a Post Image of the salesorder in the plugin regsitration tool and then using the values in the Post Image instead of the ones comming directly from the salesorder. This I did because on a update you get only the values that actually changed.