i'm having some issues getting the .Filter() method to work in subsonic, and i'm constantly getting errors like the one below:
Exception Details: System.NullReferenceException: Object reference not set to an instance of an object.
Line 36: bool remove = false;
Line 37: System.Reflection.PropertyInfo pi = o.GetType().GetProperty(w.ColumnName);
Line 38: if (pi.CanRead)
Line 39: {
Line 40: object val = pi.GetValue(o, null);
i'm making calls like the one below- is this the corrent way to use it? There seems to be no documentation on the use of this method
NavCollection objTopLevelCol = objNavigation.Where(Nav.Columns.NavHigherID,Comparison.Equals, 0).Filter();
thanks in advance
The value you filter against needs to be the Property Name, not the database column name.
You might try this:
lCol = objNavigation.Where(Nav.HigherIDColumn.PropertyName,Comparison.Equals, 0).Filter();
Or here's a slightly more verbose method that works for me based on custom overrides of the .Filter() method. It seemed to work better (for me at least) by explicitly creating the Where beforehand:
SubSonic.Where w = new SubSonic.Where();
w.ColumnName = Nav.HigherIDColumn.PropertyName;
w.Comparison = SubSonic.Comparison.NotIn;
w.ParameterValue = new string[] { "validvalue1", "validvalue2" };
lCol = objNavigation.Filter(w, false);
Here's the overrides:
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// All existing wheres are retained.
/// </summary>
/// <returns>NavCollection</returns>
public NavCollection Filter(SubSonic.Where w)
{
return Filter(w, false);
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Existing wheres can be cleared if not needed.
/// </summary>
/// <returns>NavCollection</returns>
public NavCollection Filter(SubSonic.Where w, bool clearWheres)
{
if (clearWheres)
{
this.wheres.Clear();
}
this.wheres.Add(w);
return Filter();
}
/// <summary>
/// Filters an existing collection based on the set criteria. This is an in-memory filter.
/// Thanks to developingchris for this!
/// </summary>
/// <returns>NavCollection</returns>
public NavCollection Filter()
{
for (int i = this.Count - 1; i > -1; i--)
{
Nav o = this[i];
foreach (SubSonic.Where w in this.wheres)
{
bool remove = false;
System.Reflection.PropertyInfo pi = o.GetType().GetProperty(w.ColumnName);
if (pi != null && pi.CanRead)
{
object val = pi.GetValue(o, null);
if (w.ParameterValue is Array)
{
Array paramValues = (Array)w.ParameterValue;
foreach (object arrayVal in paramValues)
{
remove = !Utility.IsMatch(w.Comparison, val, arrayVal);
if (remove)
break;
}
}
else
{
remove = !Utility.IsMatch(w.Comparison, val, w.ParameterValue);
}
}
if (remove)
{
this.Remove(o);
break;
}
}
}
return this;
}
And SubSonic 2.0 doesn't actually support In/NotIn for the IsMatch function, so here's the customized version that does (in SubSonic\Utility.cs):
public static bool IsMatch(SubSonic.Comparison compare, object objA, object objB)
{
if (objA.GetType() != objB.GetType())
return false;
bool isIntegerVal = (typeof(int) == objA.GetType());
bool isDateTimeVal = (typeof(DateTime) == objA.GetType());
switch (compare)
{
case SubSonic.Comparison.In:
case SubSonic.Comparison.Equals:
if (objA.GetType() == typeof(string))
return IsMatch((string)objA, (string)objB);
else
return objA.Equals(objB);
case SubSonic.Comparison.NotIn:
case SubSonic.Comparison.NotEquals:
return !objA.Equals(objB);
case SubSonic.Comparison.Like:
return objA.ToString().Contains(objB.ToString());
case SubSonic.Comparison.NotLike:
return !objA.ToString().Contains(objB.ToString());
case SubSonic.Comparison.GreaterThan:
if (isIntegerVal)
{
return ((int)objA > (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA > (DateTime)objB);
}
break;
case SubSonic.Comparison.GreaterOrEquals:
if (isIntegerVal)
{
return ((int)objA >= (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA >= (DateTime)objB);
}
break;
case SubSonic.Comparison.LessThan:
if (isIntegerVal)
{
return ((int)objA < (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA < (DateTime)objB);
}
break;
case SubSonic.Comparison.LessOrEquals:
if (isIntegerVal)
{
return ((int)objA <= (int)objB);
}
else if (isDateTimeVal)
{
return ((DateTime)objA <= (DateTime)objB);
}
break;
}
return false;
}
IF you're using .net 3.5 you could just do this with a lambda function:
NavCollection objTopLevelCol =
objNavigation.Where(nav => nav.NavHigherID == 0);
Filter is designed to work on a collection - is "objNavigation" a collection? The problem you're running into is that the criteria for Filter() can't be met with the column name "NavHigherID".
I was having same prob, try doing your filter like this :
lCol = objNavigation.Where("NavHigherID",Comparison.Equals, 0).Filter();
Related
Let say we have an object:
#:checkDirty
class Test {
var a:Int;
var b(default, default):String;
var c(get, set):Array<Int>;
public function new() {
...
}
public function get_c() {
...
}
public function set_c(n) {
...
}
}
Could we write a macro checkDirty so that any change to field/properties would set property dirty to true. Macro would generate dirty field as Bool and clearDirty function to set it to false.
var test = new Test();
trace(test.dirty); // false
test.a = 12;
trace(test.dirty); // true
test.clearDirty();
trace(test.dirty); //false
test.b = "test"
trace(test.dirty); //true
test.clearDirty();
test.c = [1,2,3];
trace(test.dirty); //true
Just to note - whenever you consider proxying access to an object, in my experience, there are always hidden costs / added complexity. :)
That said, you have a few approaches:
First, if you want it to be pure Haxe, then either a macro or an abstract can get the job done. Either way, you're effectively transforming every property access into a function call that sets the value and also sets dirty.
For example, an abstract using the #:resolve getter and setter can be found in the NME source code, replicated here for convenience:
#:forward(decode,toString)
abstract URLVariables(URLVariablesBase)
{
public function new(?inEncoded:String)
{
this = new URLVariablesBase(inEncoded);
}
#:resolve
public function set(name:String, value:String) : String
{
return this.set(name,value);
}
#:resolve
public function get(name:String):String
{
return this.get(name);
}
}
This may be an older syntax, I'm not sure... also look at the operator overloading examples on the Haxe manual:
#:op(a.b) public function fieldRead(name:String)
return this.indexOf(name);
#:op(a.b) public function fieldWrite(name:String, value:String)
return this.split(name).join(value);
Second, I'd just point out that if the underlying language / runtime supports some kind of Proxy object (e.g. JavaScript Proxy), and macro / abstract isn't working as expected, then you could build your functionality on top of that.
I wrote a post (archive) about doing this kind of thing (except for emitting events) before - you can use a #:build macro to modify class members, be it appending an extra assignment into setter or replacing the field with a property.
So a modified version might look like so:
class Macro {
public static macro function build():Array<Field> {
var fields = Context.getBuildFields();
for (field in fields.copy()) { // (copy fields so that we don't go over freshly added ones)
switch (field.kind) {
case FVar(fieldType, fieldExpr), FProp("default", "default", fieldType, fieldExpr):
var fieldName = field.name;
if (fieldName == "dirty") continue;
var setterName = "set_" + fieldName;
var tmp_class = macro class {
public var $fieldName(default, set):$fieldType = $fieldExpr;
public function $setterName(v:$fieldType):$fieldType {
$i{fieldName} = v;
this.dirty = true;
return v;
}
};
for (mcf in tmp_class.fields) fields.push(mcf);
fields.remove(field);
case FProp(_, "set", t, e):
var setter = Lambda.find(fields, (f) -> f.name == "set_" + field.name);
if (setter == null) continue;
switch (setter.kind) {
case FFun(f):
f.expr = macro { dirty = true; ${f.expr}; };
default:
}
default:
}
}
if (Lambda.find(fields, (f) -> f.name == "dirty") == null) fields.push((macro class {
public var dirty:Bool = false;
}).fields[0]);
return fields;
}
}
which, if used as
#:build(Macro.build())
#:keep class Some {
public function new() {}
public var one:Int;
public var two(default, set):String;
function set_two(v:String):String {
two = v;
return v;
}
}
Would emit the following JS:
var Some = function() {
this.dirty = false;
};
Some.prototype = {
set_two: function(v) {
this.dirty = true;
this.two = v;
return v;
}
,set_one: function(v) {
this.one = v;
this.dirty = true;
return v;
}
};
I try to put a file into a richtext but it crashes !
In my first code, I try to use directly "getFirstItem", in first time it was ok but now i try to use it again and it crashed.
In second time i pass with an object and it find my obj doesn't an richtextItem (instanceof) ???
I don't understand.
I have the message : "lotus.domino.local.Item cannot be cast to lotus.domino.RichTextItem" ?
Could you help me ?
public void copieFichierDansRichText(String idDocument, String nomRti, File file,
String nameFichier, String chemin) throws NotesException {
lotus.domino.Session session = Utils.getSession();
lotus.domino.Database db = session.getCurrentDatabase();
lotus.domino.Document monDoc = db.getDocumentByUNID(idDocument);
lotus.domino.RichTextItem rtiNew = null;
try {
try {
if (monDoc != null) {
// if (monDoc.getFirstItem(nomRti) != null) {
// rtiNew = (lotus.domino.RichTextItem)
// monDoc.getFirstItem(nomRti);
// } else {
// rtiNew = (lotus.domino.RichTextItem)
// monDoc.createRichTextItem(nomRti);
// }
Object obj = null;
if (monDoc.getFirstItem(nomRti) != null) {
obj = monDoc.getFirstItem(nomRti);
if (obj instanceof lotus.domino.RichTextItem) {
rtiNew = (lotus.domino.RichTextItem) obj;
}
} else {
obj = monDoc.createRichTextItem(nomRti);
if (obj instanceof lotus.domino.RichTextItem) {
rtiNew = (lotus.domino.RichTextItem) obj;
}
}
PieceJointe pieceJointe = new PieceJointe();
pieceJointe = buildPieceJointe(file, nameFichier, chemin);
rtiNew.embedObject(EmbeddedObject.EMBED_ATTACHMENT, "", pieceJointe.getChemin()
+ pieceJointe.getNomPiece(), pieceJointe.getNomPiece());
monDoc.computeWithForm(true, false);
monDoc.save(true);
}
} finally {
rtiNew.recycle();
monDoc.recycle();
db.recycle();
session.recycle();
}
} catch (Exception e) {
e.printStackTrace();
}
}
EDIT : I try to modify my code with yours advices but the items never considerate as richtextitem. It is my problem. I don't understand why, because in my field it is a richtext ! For it, the item can't do :
rtiNew = (lotus.domino.RichTextItem) item1;
because item1 not be a richtext !!!
I was trying to take all the fields and pass in the item one by one, and it never go to the obj instance of lotus.domini.RichTextItem....
Vector items = doc.getItems();
for (int i=0; i<items.size(); i++) {
// get next element from the Vector (returns java.lang.Object)
Object obj = items.elementAt(i);
// is the item a RichTextItem?
if (obj instanceof RichTextItem) {
// yes it is - cast it as such // it never go here !!
rt = (RichTextItem)obj;
} else {
// nope - cast it as an Item
item = (Item)obj;
}
}
A couple of things. First of all I would set up a util class method to handle the object recycling in a neater way:
public enum DominoUtil {
;
public static void recycle(Base... bases) {
for (Base base : bases) {
if (base != null) {
try {
base.recycle();
} catch (Exception e) {
// Do nothing
}
}
}
}
}
Secondly I would remove the reduntants try/catch blocks and simplify it like this:
private void copieFichierDansRichText(String idDocument, String nomRti, File file,
String nameFichier, String chemin) {
Session session = DominoUtils.getCurrentSession();
Database db = session.getCurrentDatabase();
Document monDoc = null;
try {
monDoc = db.getDocumentByUNID(idDocument);
Item item = monDoc.getFirstItem(nomRti);
if (item == null) {
item = monDoc.createRichTextItem(nomRti);
} else if (item.getType() != Item.RICHTEXT) {
// The item is not a rich text item
// What are you going to do now?
}
RichTextItem rtItem = (RichTextItem) item;
PieceJointe pieceJointe = new PieceJointe();
pieceJointe = buildPieceJointe(file, nameFichier, chemin);
rtItem.embedObject(EmbeddedObject.EMBED_ATTACHMENT, "", pieceJointe.getChemin()
+ pieceJointe.getNomPiece(), pieceJointe.getNomPiece());
monDoc.computeWithForm(true, false);
monDoc.save(true);
} catch (NotesException e) {
throw new FacesException(e);
} finally {
DominoUtil.recycle(monDoc);
}
}
Finally, apart from the monDoc, you need not recycle anything else. Actually Session would be automatically recycled and anything beneath with it (so no need to recycle db, let alone the session!, good rule is don't recycle what you didn't instantiate), but it's not bad to keep the habit of keeping an eye on what you instantiate. If it were a loop with many documents you definitively want to do that. If you also worked with many items you would want to recycle them as early as possible. Anyway, considered the scope of the code it's sufficient like this. Obviously you would call DominoUtil.recycle directly from the try block. If you have multiple objects you can recycle them at once possibly by listing them in the reverse order you set them (eg. DominoUtil.recycle(item, doc, view)).
Also, what I think you miss is the check on the item in case it's not a RichTextItem - and therefore can't be cast. I put a comment where I think you should decide what to do before proceeding. If you let it like that and let the code proceed you will have the code throw an error. Always better to catch the lower level exception and re-throw a higher one (you don't want the end user to know more than it is necessary to know). In this case I went for the simplest thing: wrapped NotesException in a FacesException.
I have a Bar chart on Kentico reporting section. And I know Kentico uses Microsoft Chart Controls. Microsoft Chart controls have the capability of creating a trending line on Bar graph - But I do see any option how I can utilize those on Kentico Reporting.
Is there any option there on reporting tool to get this trending line ?
If there is no option can anybody suggest anything else ?
Using custom module is the last option for me to try. If anybody has any specific suggestion regarding this custom module, please share that, too.
I am using Kentico 7.
Got it working the way Brend suggested however the mean is not coming up
ChartArea area = graph.ChartControl.ChartAreas[chartAreas - 1];
StripLine line = new StripLine();
// Set threshold line so that it is only shown once
line.Interval = 0;
// Set the threshold line to be drawn at the calculated mean of the first series
line.IntervalOffset = graph.ChartControl.DataManipulator.Statistics.Mean(graph.ChartControl.Series[0].Name);
line.BackColor = System.Drawing.Color.DarkGreen;
line.StripWidth = 0.25;
// Set text properties for the threshold line
//line.Text = "Mean";
line.ForeColor = System.Drawing.Color.Black;
// Add strip line to the chart
area.AxisY.StripLines.Add(line);
Also, for other trend line I am using bellow code, again having no luck as it looks like the datapoints are not set at the point where my code runs :
int chartAreas = graph.ChartControl.ChartAreas.Count;
if (chartAreas > 0)
{
graph.ChartControl.Series.Add("TrendLine");
graph.ChartControl.Series["TrendLine"].ChartType = SeriesChartType.Line;
graph.ChartControl.Series["TrendLine"].BorderWidth = 3;
graph.ChartControl.Series["TrendLine"].Color = System.Drawing.Color.Red;
// Line of best fit is linear
string typeRegression = "Linear";//"Exponential";//
// The number of days for Forecasting
string forecasting = "1";
// Show Error as a range chart.
string error = "false";
// Show Forecasting Error as a range chart.
string forecastingError = "false";
// Formula parameters
string parameters = typeRegression + ',' + forecasting + ',' + error + ',' + forecastingError;
graph.ChartControl.Series[0].Sort(PointSortOrder.Ascending, "X");
// Create Forecasting Series.
graph.ChartControl.DataManipulator.FinancialFormula(FinancialFormula.Forecasting, parameters, graph.ChartControl.Series[0], graph.ChartControl.Series["TrendLine"]);
}
The actual issue, I guess, is not having the graph.ChartControl.Series[0] at the place I am running my TrendLine generation code. Any idea how can I get it ?
Report charts are rendered through \CMSModules\Reporting\Controls\ReportGraph.ascx
You can modify the method GetReportGraph and add additional setup to the chart control ucChart based on some condition, e.g. the report name and chart name (you will have to hardcode that)
Note that you will need to modify Kentico code directly, so keep the changes at the lowest possible level, I recommend:
Put the extra setup code to an external class
Call it with just one extra line of code
Add comment to mark that extra line of code as customization
e.g.:
/* YourCompany */
MyChartExtender.ExtendChart(ucChart, ...);
/* YourCompany end */
Make sure you note that change for future upgrades
I've modified the controls before and you can use this code in the GetReportGraph() method just before enabling the subscription.
// apply the trendline
if (TrendValue > 0)
{
int chartAreas = graph.ChartControl.ChartAreas.Count;
if (chartAreas > 0)
{
ChartArea area = graph.ChartControl.ChartAreas[chartAreas - 1];
StripLine line = new StripLine();
line.IntervalOffset = TrendValue;
line.BorderColor = System.Drawing.ColorTranslator.FromHtml(TrendColor);
line.BackColor = System.Drawing.ColorTranslator.FromHtml(TrendColor);
line.StripWidth = TrendLineWidth;
line.ToolTip = TrendToolTip;
line.Text = TrendText;
line.TextLineAlignment = trendLineAlignment;
line.TextOrientation = TextOrientation.Horizontal;
line.TextAlignment = trendTextAlignment;
area.AxisY.StripLines.Add(line);
}
}
Of course you'll have to add the appropriate properties and pass the values through from the rest of the pages/controls using this control.
#region Trending
/// <summary>
/// Value for the single trendline for whole chart
/// </summary>
public int TrendValue
{
get
{
return mTrendValue;
}
set
{
mTrendValue = value;
}
}
/// <summary>
/// Color of the trend line in hex format (i.e. #0000FF)
/// </summary>
public string TrendColor
{
get
{
return mTrendColor;
}
set
{
mTrendColor = value;
}
}
/// <summary>
/// Tool tip of the trend line
/// </summary>
public string TrendToolTip
{
get
{
return mTrendToolTip;
}
set
{
mTrendToolTip = value;
}
}
/// <summary>
/// Text of the trend line
/// </summary>
public string TrendText
{
get
{
return mTrendText;
}
set
{
mTrendText = value;
}
}
/// <summary>
/// Trend line width
/// </summary>
public double TrendLineWidth
{
get
{
return mTrendLineWidth;
}
set
{
mTrendLineWidth = value;
}
}
string mTrendLineAlignment;
public string TrendLineAlignment
{
get
{
return mTrendLineAlignment;
}
set
{
mTrendLineAlignment = value;
}
}
private System.Drawing.StringAlignment trendLineAlignment
{
get
{
switch (TrendLineAlignment)
{
case "center":
return System.Drawing.StringAlignment.Center;
case "near":
return System.Drawing.StringAlignment.Near;
case "far":
return System.Drawing.StringAlignment.Far;
default:
return System.Drawing.StringAlignment.Near;
}
}
}
string mTrendTextAlignment;
public string TrendTextAlignment
{
get
{
return mTrendTextAlignment;
}
set
{
mTrendTextAlignment = value;
}
}
private System.Drawing.StringAlignment trendTextAlignment
{
get
{
switch (TrendTextAlignment)
{
case "center":
return System.Drawing.StringAlignment.Center;
case "near":
return System.Drawing.StringAlignment.Near;
case "far":
return System.Drawing.StringAlignment.Far;
default:
return System.Drawing.StringAlignment.Near;
}
}
}
#endregion
I am trying to get multithreading more unraveled in my head. I made these three classes.
A global variable class
public partial class globes
{
public bool[] sets = new bool[] { false, false, false };
public bool boolChanged = false;
public string tmpStr = string.Empty;
public int gcount = 0;
public bool intChanged = false;
public Random r = new Random();
public bool gDone = false;
public bool first = true;
}
Drop in point
class Driver
{
static void Main(string[] args)
{
Console.WriteLine("start");
globes g = new globes();
Thread[] threads = new Thread[6];
ParameterizedThreadStart[] pts = new ParameterizedThreadStart[6];
lockMe _lockme = new lockMe();
for (int b = 0; b < 3; b++)
{
pts[b] = new ParameterizedThreadStart(_lockme.paramThreadStarter);
threads[b] = new Thread(pts[b]);
threads[b].Name = string.Format("{0}", b);
threads[b].Start(b);
}
}
}
And then my threading class
class lockMe
{
#region Fields
private string[] words = new string[] {"string0", "string1", "string2", "string3"};
private globes g = new globes();
private object myKey = new object();
private string[] name = new string[] { String.Empty, String.Empty, String.Empty };
#endregion
#region methods
// first called for all threads
private void setName(Int16 i)
{
Monitor.Enter(myKey);
{
try
{
name[i] = string.Format("{0}:{1}", Thread.CurrentThread.Name, g.r.Next(100, 500).ToString());
}
finally
{
Monitor.PulseAll(myKey);
Monitor.Exit(myKey);
}
}
}
// thread 1
private void changeBool(Int16 a)
{
Monitor.Enter(myKey);
{
try
{
int i = getBools();
//Thread.Sleep(3000);
if (g.gcount > 5) { g.gDone = true; return; }
if (i == 3) resets();
else { for (int x = 0; x <= i; i++) { g.sets[x] = true; } }
Console.WriteLine("Thread {0} ran through changeBool()\n", name[a]);
}
finally
{
Monitor.PulseAll(myKey);
Monitor.Exit(myKey);
}
}
}
// thread 2
private void changeInt(Int16 i)
{
Monitor.Enter(myKey);
{
try
{
g.gcount++;
//Thread.Sleep(g.r.Next(1000, 3000));
Console.WriteLine("Thread {0}: Count is now at {1}\n", name[i], g.gcount);
}
finally
{
Monitor.PulseAll(myKey);
Monitor.Exit(myKey);
}
}
}
// thread 3
private void printString(Int16 i)
{
Monitor.Enter(myKey);
{
try
{
Console.WriteLine("...incoming...");
//Thread.Sleep(g.r.Next(1500, 2500));
Console.WriteLine("Thread {0} printing...{1}\n", name[i], words[g.r.Next(0, 3)]);
}
finally
{
Monitor.PulseAll(myKey);
Monitor.Exit(myKey);
}
}
}
// not locked- called from within a locked peice
private int getBools()
{
if ((g.sets[0] == false) && (g.sets[1] == false) && (g.sets[2] == false)) return 0;
else if ((g.sets[0] == true) && (g.sets[1] == false) && (g.sets[2] == false)) return 1;
else if ((g.sets[2] == true) && (g.sets[3] == false)) return 2;
else if ((g.sets[0] == true) && (g.sets[1] == true) && (g.sets[2] == true)) return 3;
else return 99;
}
// should not need locks- called within locked statement
private void resets()
{
if (g.first) { Console.WriteLine("FIRST!!"); g.first = false; }
else Console.WriteLine("Cycle has reset...");
}
private bool getStatus()
{
bool x = false;
Monitor.Enter(myKey);
{
try
{
x = g.gDone;
}
finally
{
Monitor.PulseAll(myKey);
Monitor.Exit(myKey);
}
}
return x;
}
#endregion
#region Constructors
public void paramThreadStarter(object starter)
{
Int16 i = Convert.ToInt16(starter);
setName(i);
do
{
switch (i)
{
default: throw new Exception();
case 0:
changeBool(i);
break;
case 1:
changeInt(i);
break;
case 2:
printString(i);
break;
}
} while (!getStatus());
Console.WriteLine("fin");
Console.ReadLine();
}
#endregion
}
So I have a few questions. The first- is it better to have my global class set like this? Or should I be using a static class with properties and altering them that way? Next question is, when this runs, at random one of the threads will run, pulse/exit the lock, and then step right back in (sometimes like 5-10 times before the next thread picks up the lock). Why does this happen?
Each thread is given a certain amount of CPU time, I doubt that one particular thread is getting more actual CPU time over the others if you are locking all the calls in the same fashion and the thread priorities are the same among the threads.
Regarding how you use your global class, it doesn't really matter. The way you are using it wouldn't change it one way or the other. Your use of globals was to test thread safety, so when multiple threads are trying to change shared properties all that matters is that you enforce thread safety.
Pulse might be a better option knowing that only one thread can actually enter, pulseAll is appropriate when you lock something because you have a task to do, once that task is complete and won't lock the very next time. In your scenario you lock every time so doing a pulseAll is just going to waste cpu because you know that it will be locked for the next request.
Common example of when to use static classes and why you must make them thread safe:
public static class StoreManager
{
private static Dictionary<string,DataStore> _cache = new Dictionary<string,DataStore>(StringComparer.OrdinalIgnoreCase);
private static object _syncRoot = new object();
public static DataStore Get(string storeName)
{
//this method will look for the cached DataStore, if it doesn't
//find it in cache it will load from DB.
//The thread safety issue scenario to imagine is, what if 2 or more requests for
//the same storename come in? You must make sure that only 1 thread goes to the
//the DB and all the rest wait...
//check to see if a DataStore for storeName is in the dictionary
if ( _cache.ContainsKey( storeName) == false )
{
//only threads requesting unknown DataStores enter here...
//now serialize access so only 1 thread at a time can do this...
lock(_syncRoot)
{
if (_cache.ContainsKey(storeName) == false )
{
//only 1 thread will ever create a DataStore for storeName
DataStore ds = DataStoreManager.Get(storeName); //some code here goes to DB and gets a DataStore
_cache.Add(storeName,ds);
}
}
}
return _cache[storeName];
}
}
What's really important to see is that the Get method only single threads the call when there is no DataStore for the storeName.
Double-Check-Lock:
You can see the first lock() happens after an if, so imagine 3 threads simultaneously run the if ( _cache.ContainsKey(storeName) .., now all 3 threads enter the if. Now we lock so that only 1 thread can enter, now we do the same exact if statement, only the very first thread that gets here will actually pass this if statement and get the DataStore. Once the first thread .Add's the DataStore and exits the lock the other 2 threads will fail the second check (double check).
From that point on any request for that storeName will get the cached instance.
So we single threaded our application only in the spots that required it.
I have the following code which copies property values from one object to another objects by matching their property names:
public static void CopyProperties(object source, object target,bool caseSenstive=true)
{
PropertyInfo[] targetProperties = target.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
PropertyInfo[] sourceProperties = source.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo tp in targetProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(p => p.Name == tp.Name);
if (sourceProperty == null && !caseSenstive)
{
sourceProperty = sourceProperties.FirstOrDefault(p => p.Name.ToUpper() == tp.Name.ToUpper());
}
// If source doesn't have this property, go for next one.
if(sourceProperty ==null)
{
continue;
}
// If target property is not writable then we can not set it;
// If source property is not readable then cannot check it's value
if (!tp.CanWrite || !sourceProperty.CanRead)
{
continue;
}
MethodInfo mget = sourceProperty.GetGetMethod(false);
MethodInfo mset = tp.GetSetMethod(false);
// Get and set methods have to be public
if (mget == null)
{
continue;
}
if (mset == null)
{
continue;
}
var sourcevalue = sourceProperty.GetValue(source, null);
tp.SetValue(target, sourcevalue, null);
}
}
This is working well when the type of properties on target and source are the same. But when there is a need for casting, the code doesn't work.
For example, I have the following object:
class MyDateTime
{
public static implicit operator DateTime?(MyDateTime myDateTime)
{
return myDateTime.DateTime;
}
public static implicit operator DateTime(MyDateTime myDateTime)
{
if (myDateTime.DateTime.HasValue)
{
return myDateTime.DateTime.Value;
}
else
{
return System.DateTime.MinValue;
}
}
public static implicit operator MyDateTime(DateTime? dateTime)
{
return FromDateTime(dateTime);
}
public static implicit operator MyDateTime(DateTime dateTime)
{
return FromDateTime(dateTime);
}
}
If I do the following, the implicit cast is called and everything works well:
MyDateTime x= DateTime.Now;
But when I have a two objects that one of them has a DateTime and the other has MyDateTime, and I am using the above code to copy properties from one object to other, it doesn't and generate an error saying that DateTime can not converted to MyTimeDate.
How can I fix this problem?
One ghastly approach which should work is to mix dynamic and reflection:
private static T ConvertValue<T>(dynamic value)
{
return value; // This will perform conversion automatically
}
Then:
var sourceValue = sourceProperty.GetValue(source, null);
if (sourceProperty.PropertyType != tp.PropertyType)
{
var method = typeof(PropertyCopier).GetMethod("ConvertValue",
BindingFlags.Static | BindingFlags.NonPublic);
method = method.MakeGenericMethod(new[] { tp.PropertyType };
sourceValue = method.Invoke(null, new[] { sourceValue });
}
tp.SetValue(target, sourceValue, null);
We need to use reflection to invoke the generic method with the right type argument, but dynamic typing will use the right conversion operator for you.
Oh, and one final request: please don't include my name anywhere near this code, whether it's in comments, commit logs. Aargh.