I am trying to use NRefactory to modify an existing piece of C# code. I have tried to use the method described in this article. I'm getting an exception complaining about duplicate changes. Can someone point out what I am doing wrong, and maybe point me in the right direction?
I have the following section of code:
// contains the source code that needs to be modified
var text = "...";
// is the name of the file in which the target class exists
var fileName = "Foo";
// is the name of the target class
var className = "Foo.cs";
// FormattingOptions and TextEditorOptions are properties available to
// the class containing this code...
var parser = new CSharpParser();
var syntaxTree = parser.Parse(text, fileName);
syntaxTree.Freeze();
var document = new StringBuilderDocument();
document.Text = syntaxTree.ToString(FormattingOptions);
using (var documentScript = new DocumentScript(document, FormattingOptions, TextEditorOptions))
{
var typeDeclarations = syntaxTree.Descendants.OfType<TypeDeclaration>().Where(typeDeclaration => typeDeclaration.Name == className);
var templateProviderDeclaration = typeDeclarations.SingleOrDefault();
if (templateProviderDeclaration != null)
{
var constructorDeclarations = templateProviderDeclaration.Descendants.OfType<ConstructorDeclaration>();
foreach (var constructorDeclaration in constructorDeclarations)
{
var assignmentExpressions = constructorDeclaration.Descendants.OfType<AssignmentExpression>();
foreach (var assignmentExpression in assignmentExpressions)
{
var leftExpression = assignmentExpression.Left;
if (leftExpression != null && leftExpression.ToString(FormattingOptions) == "this.templates")
{
var rightExpression = assignmentExpression.Right;
foreach (var arrayInitializerExpression in rightExpression.Children.OfType<ArrayInitializerExpression>())
{
// uses AddTemplate(), which is an extension method
// which takes in an expression and modifies it, and
// returns an expression
var newExpression = ((ArrayInitializerExpression)arrayInitializerExpression).AddTemplate();
documentScript.Replace(arrayInitializerExpression, newExpression);
}
}
}
}
}
// text retrieved here is *clobbered*, by a few characters
text = documentScript.CurrentDocument.Text;
}
Other Code...
public static class ExtensionMethods
{
public static ArrayInitializerExpression AddTemplate(this ArrayInitializerExpression expression)
{
// TODO : will modify the AST for the target expression
return (ArrayInitializerExpression)expression.Clone();
}
}
Update:
I am trying to change a line like this:
this.templates = new Dictionary<string, ITemplate>
{
{ "Foo", new FooTemplate() }
};
to:
this.templates = new Dictionary<string, ITemplate>
{
{ "Foo", new FooTemplate() },
{ "Bar", new BarTemplate() }
};
For the purposes of this question, the exact specifics of the change I am trying to make aren't important though. I suspect that I have something not constructed quite correctly, but I cannot figure out if that's the issue.
Related
If I was to use the high level model, I might try something like this:
public async void GetBooksData()
{
GetItemRequest request = new GetItemRequest
{
TableName = "Customer",
Key = new Dictionary<string, AttributeValue>
{
{"UserName", new AttributeValue{S="a"} },
{"BookNum", new AttributeValue { S = starts_with(queryTerm)} }
}
};
try
{
var response = await client.GetItemAsync(request);
if (response.HttpStatusCode == System.Net.HttpStatusCode.OK)
{
if (response.Item.Count > 0)
{
foreach (var item in response.Item)
{
MessageBox.Show("Value : \n" + item.Value.S);
}
}
}
}
catch (InternalServerErrorException iee)
{
MessageBox.Show(iee);
}
}
I need to use the method 'begins_with' for getting 2 items what UserName is 'a' and the BookNum are book_1 and book_2. This is possible in the high level interface in Java. As an example as to what can be done on the range key in Java:
public List<Comment> allForItemWithMinRating(String itemId, int minRating) {
Comment comment = new Comment();
comment.setItemId(itemId);
Condition condition = new Condition()
.withComparisonOperator(ComparisonOperator.GE)
.withAttributeValueList(
new AttributeValue()
.withN(Integer.toString(minRating)));
DynamoDBQueryExpression<Comment> queryExpression
= new DynamoDBQueryExpression<Comment>()
.withHashKeyValues(comment)
.withRangeKeyCondition(
"rating",
condition
)
.withScanIndexForward(false);
return mapper.query(Comment.class, queryExpression);
}
In the low level interface for C# you can achieve this as so:
var requestDynamodb = new QueryRequest
{
TableName = "GroupEdEntries",
KeyConditionExpression = "partition_key = :s_Id and begins_with(sort_key, :sort)",
ExpressionAttributeValues = new Dictionary<string, AttributeValue> {
{":s_Id", new AttributeValue { S = my_id }},
{":sort", new AttributeValue { S = sort_key_starts_with }}
},
ConsistentRead = true
};
var results = await client.QueryAsync(requestDynamodb);
where the keys are called partition_key and sort_key. However, this returns the results as attribute values, which then need to be converted into POCOs one property at a time. It requires using reflection and is made more complicated using converters. It seems strange that this fundamental functionality (as well as other functionality) isn't supported in the C# SDK.
I ended up using reflection to create the tables based on the attributes, when this is also supported by default in Java. Am I missing a high level API for C#?
It's a bit of a different syntax and I can't find it documented anywhere (other than in code comments), but this works for me:
string partition_key = "123";
string sort_key_starts_with = "#type"
List<object> queryVal = new List<object>();
queryVal.Add(sort_key_starts_with);
var myQuery = context.QueryAsync<GroupEdEntry>(partition_key, QueryOperator.BeginsWith, queryVal);
var queryResult = await myQuery.GetRemainingAsync();
I'd like to do:
var myClassInstance = myFactory.CreateMyClass() { MyPropertyA = "blah", MyPropertyB = "Bleh"};
but the compiler doesn't like that.
Is there away around this? I'm trying to avoid:
var myClassInstance = myFactory.CreateMyClass();
myClassInstance.MyPropertyA = "blah";
myClassInstance.MyPropertyB = "Bleh";
I know I said myFactory, but it isn't mine. I can't change CreateMyClass
While it isn't possible to do exactly what you are asking, you can use the Builder design pattern to increase readability.
Something along these lines:
var myClassInstance = myFactory.CreateMyClass()
.WithMyPropertyA("blah")
.WithMyPropertyB("Bleh");
This can be done with either extension methods or wrapping myFactory in a builder class of your own.
What i would do (depending on how often this is called and how important the performance is):
public class Factory
{
MyObject CreateMyObject(object source)
{
var target = new MyObject();
CopyPropertiesFromSource(target, source):
return target;
}
static void CopyPropertiesFromSource(MyObject target, object source)
{
var propertiesToInitialize = typeof(MyObject).GetProperties();
var sourceProperties = source.GetType().GetProperties();
foreach(var property in propertiesToInitialize)
{
var correspondingProperty = sourceProperties.FirstOrDefault(x => x.Name == property.Name && x.PropertyType == property.PropertyType);
if(correspondingProperty == null)
continue;
property.SetValue(target, correspondingProperty.GetValue(source));
}
}
}
var myClassInstance = new Factory().CreateMyObject({ MyPropertyA = "blah" });
Based on David Culp's answer, I made this extension function:
public static object With(this object obj, object additionalProperties)
{
var type = additionalProperties.GetType();
foreach (var sourceField in type.GetFields())
{
var name = sourceField.Name;
var value = sourceField.GetValue(additionalProperties);
if (type.GetMember(name)[0] is FieldInfo)
type.GetField(name).SetValue(obj, value);
else
type.GetProperty(name).SetValue(obj, value);
}
return obj;
}
and it's used like this:
var myClassInstance = myFactory.CreateMyClass().With(new { MyPropertyA = "blah", MyPropertyB = "bleh"});
It really shines when there's a lot of properties.
// insert a new version
T newVersion = (T)MemberwiseClone();
newVersion.IsSuspended = true;
newVersion.RealEffectiveDate = RealExpirationDate;
newVersion.RealExpirationDate = NullDate;
newVersion.Version++;
newVersion.BusinessEffectiveDate = BusinessExpirationDate;
newVersion.BusinessExpirationDate = NullDate;
becomes:
// insert a new version
T newVersion = (T)MemberwiseClone().With(new
{
IsSuspended = true,
RealEffectiveDate = RealExpirationDate,
RealExpirationDate = NullDate,
Version = Version + 1,
BusinessEffectiveDate = BusinessExpirationDate,
BusinessExpirationDate = NullDate
});
I created code to load definitions from an external API. The code iterates through a list of words, looks up a definition for each and then I thought to use EF to insert these into my SQL Server database.
However if I run this twice it will load the same definitions the second time. Is there a way that I could make it so that EF does not add the row if it already exists?
public IHttpActionResult LoadDefinitions()
{
var words = db.Words
.AsNoTracking()
.ToList();
foreach (var word in words)
{
HttpResponse<string> response = Unirest.get("https://wordsapiv1.p.mashape.com/words/" + word)
.header("X-Mashape-Key", "xxxx")
.header("Accept", "application/json")
.asJson<string>();
RootObject rootObject = JsonConvert.DeserializeObject<RootObject>(response.Body);
var results = rootObject.results;
foreach (var result in results)
{
var definition = new WordDefinition()
{
WordId = word.WordId,
Definition = result.definition
};
db.WordDefinitions.Add(definition);
}
db.SaveChanges();
}
return Ok();
}
Also would appreciate if anyone has any suggestions as to how I could better implement this loading.
foreach (var result in results)
{
if(!(from d in db.WordDefinitions where d.Definition == result.definition select d).Any())
{
var definition = new WordDefinition()
{
WordId = word.WordId,
Definition = result.definition
};
db.WordDefinitions.Add(definition);
}
}
You can search for Definition value.
var wd = db.WordDefinition.FirstOrDefault(x => x.Definition == result.definition);
if(wd == null) {
var definition = new WordDefinition() {
WordId = word.WordId,
Definition = result.definition
};
db.WordDefinitions.Add(definition);
}
In this way you can get a WordDefinition that already have your value.
If you can also use WordId in the same way:
var wd = db.WordDefinition.FirstOrDefault(x => x.WordId == word.WordId);
I code below that works well with an API for the DataTables plugin; for each column that the DataTables searches in, regardless of type, the filter works as expected based on what is supplied.
DataTables also has a "Global" search feature where you can search in one field and if there is a match in ANY of the rows for said data then a match is returned.
What I am Hoping For:
A way to perform a search on an IEnumerable such that if any of the fields match the search the result is returned.
NameValueCollection nvc = HttpUtility.ParseQueryString(Request.RequestUri.Query);
var generalSearch = nvc["sSearch"];
if (!string.IsNullOrWhiteSpace(generalSearch))
{
var generalSearchProperties = typeof(T).GetProperties();
foreach (var currentProperty in generalSearchProperties)
{
Type propType = currentProperty.PropertyType;
set = set.Where(StaticUtility.PropertyEquals<T>(currentProperty, generalSearch, propType));
/* ^^^^^ */
/*
Instead of the "Where" here I am looking for something like "where or" which can be added to the IEnumerable.
*/
}
}
Original Code:
protected virtual IQueryable<T> FilterEntitiesBySearchParameters(IQueryable<T> set)
{
try
{
NameValueCollection nvc = HttpUtility.ParseQueryString(Request.RequestUri.Query);
var props = typeof(T).GetProperties();
foreach (var prop in props)
{
var name = prop.Name;
var val = nvc[name];
Type propType = prop.PropertyType;
if (val != null)
{
set = set.Where(StaticUtility.PropertyEquals<T>(prop, val, propType));
}
if (nvc.GetPairs().Where(p => p.Value == name).Where(p => p.Key.StartsWith("mDataProp")).Any())
{
var key = nvc.GetPairs().Where(p => p.Value == name).Where(p => p.Key.StartsWith("mDataProp")).FirstOrDefault().Key;
key = key.Replace("mDataProp", "sSearch");
val = nvc[key];
if (!String.IsNullOrEmpty(val))
set = set.Where(StaticUtility.PropertyEquals<T>(prop, val, propType));
}
}
return set;
} catch (Exception exc)
{
return set;
}
}
If I understand your request correctly, you basically want to search through your data and match any field for equality? If that's true, then simply add your matching data to a new set, and filter it by .Distinct() after the fact, to make sure you get one record of each. Something like so...
NameValueCollection nvc = HttpUtility.ParseQueryString(Request.RequestUri.Query);
var results = new IEnumerable<T>();
var generalSearch = nvc["sSearch"];
if (!string.IsNullOrWhiteSpace(generalSearch))
{
var generalSearchProperties = typeof(T).GetProperties();
foreach (var currentProperty in generalSearchProperties)
{
Type propType = currentProperty.PropertyType;
results.AddRange(set.Where(StaticUtility.PropertyEquals<T>(currentProperty, generalSearch, propType)));
}
}
return results.Distinct();
You could try to first create a BinaryExpression consisting of all the different options, then pass that expression to the Where() method of the query.
Assuming your StaticUtility class is used to create expressions, you might try something like the following:
NameValueCollection nvc = HttpUtility.ParseQueryString(Request.RequestUri.Query);
// Container for filter expression
BinaryExpression filter = null;
var generalSearch = nvc["sSearch"];
if (!string.IsNullOrWhiteSpace(generalSearch)) {
var generalSearchProperties = typeof(T).GetProperties();
foreach (var currentProperty in generalSearchProperties) {
Type propType = currentProperty.PropertyType;
if (filter == null) {
// Start with first filter expression
filter = StaticUtility.PropertyEquals<T>(currentProperty, generalSearch, propType);
} else {
// Add another filter using OR
BinaryExpression other = StaticUtility.PropertyEquals<T>(currentProperty, generalSearch, propType);
filter = BinaryExpression.OrElse(filter, other);
}
}
}
// Add actual filter to query
set = set.Where(filter);
How do I do this dynamically using DelimitedClassBuilder so that the columns in the file can expand but not break my program?
[DelimitedRecord(",")]
public class MyRecord
{
public string Name;
[FieldOptional, FieldArrayLength(0, 100)]
public string[] I_DONT_CARE_WHAT_COMES_AFTER_THIS;
}
i.e. how do I finish this:
var cb = new DelimitedClassBuilder("xyz", ",");
cb.AddField("Name", "string");
... how do I add the array field here?
Type type = cb.CreateRecordClass();
var engine = new DelimitedFileEngine(type);
Good question. The best I can find is:
var cb = new DelimitedClassBuilder("xyz", ",");
cb.AddField("Name", "string");
cb.AddFields(100);
foreach (var field in cb.Fields.Where(f => f.FieldName.StartsWith("Field")))
{
field.FieldOptional = true;
}
var type = cb.CreateRecordClass();
var engine = new DelimitedFileEngine(type);
I can't get it to work with
cb.AddField("I_DONT_CARE", typeof(string[]));
Nor with
cb.AddField("I_DONT_CARE", typeof(string[]).FullName);
both of which ought to work.