I have a method which returns a generic list of something.
I want to be able to pass this method a string value which will represent one of the fields in the class's name, and based on this I want to order the data by this field. I want to do this in a different way other than a switch.
For example;
private void GetList()
{
var list = GetResearchStocks("Sedol");
}
private List<Stocks> GetResearchStocks(string orderBy = "")
{
var currentResearchStockList = _reports.GetZeusData("tblResearchStocks");
var researchStocklist = currentResearchStockList.AsEnumerable().ToList();
_zeusResearchStocks = researchStocklist.Select(item => new ZEUS_ResearchStocks
{
Sedol = item[0].ToString(),
StockName = item[1].ToString(),
}
).ToList();
if (orderBy != "")
{
return _zeusResearchStocks.OrderBy(o=>o.) ?????? < What to do here?
}
return _zeusResearchStocks;
}
You may either use Dynamic Linq library (DLinq) or you may just write an extension method using reflection to use string variables in order statements.
For the second option, refer to this example.
Related
I'm trying to make it so I can send queries like: But I am having trouble using the bitwise functions to do so. The var orders is wrong because TypeEnum can't be used with IEnumerable
I think you're doing more work than necessary. Enum.TryParse is able to take a comma separated list of names and parse it into the correct value:
public static void Get(string orderTypes)
{
var orders = Enumerable.Empty<OrderList>();
if (Enum.TryParse(typeof(TypeEnum), orderTypes, out var enumOrderTypes))
{
orders = _context.OrderLists.Where(o => (o.orderType & enumOrderTypes) > 0);
}
return Ok(orders);
}
However, this assumes that orderTypes only contains valid enum value names. So if orderTypes was "Standard,IDontExist" Enum.TryParse would return false.
If you want to allow invalid names and just filter them out, you can do it like so:
public static void Get(string orderTypes)
{
var typeList = orderTypes.Split(',',
StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
// I'd recommend adding 'None = 0' as a default value for the enum
TypeEnum enumOrderTypes = TypeEnum.None;
foreach (var strType in typeList)
{
if (Enum.TryParse(typeof(TypeEnum), strType, out var enumType))
{
enumOrderTypes |= (TypeEnum)enumType;
}
}
var orders = _context.OrderLists.Where(o => (o.orderType & enumOrderTypes) > 0);
return Ok(orders);
}
In this example if orderTypes was "Standard,IDontExist", then enumOrderTypes would end up just containing Standard.
Check out this fiddle for a demonstration.
I write a LINQ query and for Select clause I created an Expression to reuse it.
My query looks like this
DataContext.Single.Select(SearchSelector).ToList();
Where as Search Selector defined as
private Expression<Func<Singles, SearchSingles>> SearchSelector = s =>
new SearchSingles
{
};
The above works fine, but what if I want to use two input parameters? How would I invoke it?
private Expression<Func<Singles,string, SearchSingles>> SearchSelector = (s,y) =>
new SearchSingles
{
};
Rather than having a field that stores the expression, have a method that creates the expression that you need given a particular string:
private static Expression<Func<Singles, SearchSingles>> CreateSearchSelector(
string foo)
{
return s =>
new SearchSingles
{
Foo = foo,
};
}
You can then use this method like so:
DataContext.Single.Select(CreateSearchSelector("Foo")).ToList();
what about leaving the signature alone and passing additional parameters as captured values? It might have limited use as an initialized member variable, like this, but if you assign from within some worker function, rather than initialize it during class construction you'd have more power.
private Func<Singles, SearchSingles> SearchSelector = s =>
new SearchSingles
{
someVal = capturedVariable,
someOther = s.nonCapturedVar
};
that would work if capturedVariable were a static member
or
private Func<Singles, SearchSingles> SearchSelector = null;
private void WorkerFunction(string capturedVariable, bool capAgain, bool yetAgain)
{
SearchSelector = s => {
bool sample = capAgain;
if (capturedTestVar)
sample = yetAgain;
return new SearchSingles
{
someVal = capturedVariable,
someOther = s.nonCapturedVar,
availability = sample
};
};
};
you could have all sorts of fun
*EDIT* references to Expression removed and clarifications
I want one method that can query my entire RavenDB database.
My method signature looks like this:
public static DataTable GetData(string className, int amount, string orderByProperty, string filterByProperty, string filterByOperator, string filterCompare)
I figured I can accomplish all of the above with a dynamic LuceneQuery.
session.Advanced.LuceneQuery<dynamic>();
The problem is: Since I'm using dynamic in the type given, how do I ensure that the query only includes the types matching the className?
I'm looking for something like .WhereType(className) or .Where("type: " + className).
Solution
This returns the results of the correct type:
var type = Type.GetType("Business.Data.DTO." + className);
var tagName = RavenDb.GetTypeTagName(type);
using (var session = RavenDb.OpenSession())
{
var result = session.Advanced
.LuceneQuery<object, RavenDocumentsByEntityName>()
.WhereEquals("Tag", tagName)
.ToList();
}
Note, it is not possible to add additional "WhereEquals" or other filters to this. This is because nothing specific to that document type is included in the "RavenDocumentByEntityName" index.
This means that this solution cannot be used for what I wanted to accomplish.
What I ended up doing
Although it doesn't fulfill my requirement completely, this is what I ended up doing:
public static List<T> GetData<T>(DataQuery query)
{
using (var session = RavenDb.OpenSession())
{
var result = session.Advanced.LuceneQuery<T>();
if (!string.IsNullOrEmpty(query.FilterByProperty))
{
if (query.FilterByOperator == "=")
{
result = result.WhereEquals(query.FilterByProperty, query.FilterCompare);
}
else if (query.FilterByOperator == "StartsWith")
{
result = result.WhereStartsWith(query.FilterByProperty, query.FilterCompare);
}
else if (query.FilterByOperator == "EndsWith")
{
result = result.WhereEndsWith(query.FilterByProperty, query.FilterCompare);
}
}
if (!string.IsNullOrEmpty(query.OrderByProperty))
{
if (query.Descending)
{
result = result.OrderBy(query.OrderByProperty);
}
else
{
result = result.OrderByDescending(query.OrderByProperty);
}
}
result = result.Skip(query.Skip).Take(query.Amount);
return result.ToList();
}
}
Although this is most certainly an anti-pattern, it's a neat way to just look at some data, if that's what you want. It's called very easily like this:
DataQuery query = new DataQuery
{
Amount = int.Parse(txtAmount.Text),
Skip = 0,
FilterByProperty = ddlFilterBy.SelectedValue,
FilterByOperator = ddlOperator.SelectedValue,
FilterCompare = txtCompare.Text,
OrderByProperty = ddlOrderBy.SelectedValue,
Descending = chkDescending.Checked
};
grdData.DataSource = DataService.GetData<Server>(query);
grdData.DataBind();
"Server" is one of the classes/document types I'm working with, so the downside, where it isn't completely dynamic, is that I would have to define a call like that for each type.
I strongly suggest you don't go down this road. You are essentially attempting to hide the RavenDB Session object, which is very powerful and intended to be used directly.
Just looking at the signature of the method you want to create, the parameters are all very restrictive and make a lot of assumptions that might not be true for the data you're working on. And the return type - why would you return a DataTable? Maybe return an object or a dynamic, but nothing in Raven is structured in tables, so DataTable is a bad idea.
To answer the specific question, the type name comes from the Raven-Entity-Name metadata, which you would need to build an index over. This happens automatically when you index using the from docs.YourEntity syntax in an index. Raven does this behind the scenes when you use a dynamic index such as .Query<YourEntity> or .Advanced.LuceneQuery<YourEntity>.
Still, you shouldn't do this.
With Dynamic LINQ, what changes need to be done to have fields of the given class?
For example, how can the following C# query be reproduced in DLinq:
var carsPartial = cars.Select(c => new {c.year, c.name, make = new maker() {name = c.make.name} }).ToList();
I have applied the changes mentioned in this answer https://stackoverflow.com/a/1468357/288747 to allow the return type to be the calling type rather than an anonymous type.
With the class definition is as follows (if it helps):
class car
{
public int vid;
public int odo;
public int year;
public string name;
public maker make;
}
class maker
{
public string name;
public int firstYear;
}
The following doesn't work (but I think is close, but still doesn't work as I don't have the changes necessary to the dynamic linq library, which is what I need):
var carsPartial = cars.Select("new(name, year, new maker() {name = make.name})").ToList();
But it fails at the new maker() { (as expected).
I'm sure I need to change the DynamicLibrary.cs to get this working and could do with some direction on how to alter it to achieve this.
UPDATE: I have turned my answer into a little bit more extensive blog post.
I have not really ever used Dynamic Linq library, but I have taken a look at the DynamicLibrary.cs code and the change to support generating type classes provided in another stackoverflow question you provided link to in your question. Analyzing them all, it seems that the nested new-s should work out of the box in your configuration.
However, it seems your query is not the correct Dynamic Linq's language query. Note, that the query string for DLinq is not equivalent to C# and has its own grammar.
The query should read out, I believe, the following:
var carsPartial = cars.Select("new(name, year, new maker(make.name as name) as make)").ToList();
EDIT:
Rereading this stackoverflow question more carefully, I realizes, that it actually does not extend the Dynamic Linq's language with the possibility for creating new strong-typed classes. They just put the result to the class specified as a generic parameter of Select() instead of specifying it in the query string.
To obtain what you need you will need to revert their changes (get generic DLinq) and apply my changes, I have just verified to work:
Locate the ParseNew method of ExpressionParser class and change it to the following:
Expression ParseNew() {
NextToken();
bool anonymous = true;
Type class_type = null;
if (token.id == TokenId.Identifier)
{
anonymous = false;
StringBuilder full_type_name = new StringBuilder(GetIdentifier());
NextToken();
while (token.id == TokenId.Dot)
{
NextToken();
ValidateToken(TokenId.Identifier, Res.IdentifierExpected);
full_type_name.Append(".");
full_type_name.Append(GetIdentifier());
NextToken();
}
class_type = Type.GetType(full_type_name.ToString(), false);
if (class_type == null)
throw ParseError(Res.TypeNotFound, full_type_name.ToString());
}
ValidateToken(TokenId.OpenParen, Res.OpenParenExpected);
NextToken();
List<DynamicProperty> properties = new List<DynamicProperty>();
List<Expression> expressions = new List<Expression>();
while (true) {
int exprPos = token.pos;
Expression expr = ParseExpression();
string propName;
if (TokenIdentifierIs("as")) {
NextToken();
propName = GetIdentifier();
NextToken();
}
else {
MemberExpression me = expr as MemberExpression;
if (me == null) throw ParseError(exprPos, Res.MissingAsClause);
propName = me.Member.Name;
}
expressions.Add(expr);
properties.Add(new DynamicProperty(propName, expr.Type));
if (token.id != TokenId.Comma) break;
NextToken();
}
ValidateToken(TokenId.CloseParen, Res.CloseParenOrCommaExpected);
NextToken();
Type type = anonymous ? DynamicExpression.CreateClass(properties) : class_type;
MemberBinding[] bindings = new MemberBinding[properties.Count];
for (int i = 0; i < bindings.Length; i++)
bindings[i] = Expression.Bind(type.GetProperty(properties[i].Name), expressions[i]);
return Expression.MemberInit(Expression.New(type), bindings);
}
Then, find the class Res and add the following error message:
public const string TypeNotFound = "Type {0} not found";
Et voilĂ , you will be able to construct queries like:
var carsPartial = cars.Select("new(name, year, (new your_namespace.maker(make.name as name)) as make)").ToList();
Make sure, you include the full type name including the whole namespace+class path.
To explain my change, it just checks if there is some identifier between new and opening parenthesis (see the added "if" at the begging). If so we parse full dot-separated class name and try to get its Type through Type.GetType instead of constructing own class in case of anonymous news.
If I've understood you correctly you want to make a plain anonymous class that contains fields from both class car and class maker. If it's the case you can just provide new names in that class, something like the following:
var carsPartial = cars.Select(c => new { year = c.year, name = c.name, make_name = c.make.name });
Or even provide names only to conflicting fields:
var carsPartial = cars.Select(c => new { c.year, c.name, make_name = c.make.name });
I am really missing something with anonymous types, because I can't figure out what to do with the Combobox.SelectedItem property.
Here's the code that populates the combobox, and it works just fine
var stocks = from st in brdc.tb_dStocks
join su in brdc.tb_rStockUsers on st.StockID equals su.StockID
where su.UserID == userRec.UserID
select new { st.StockID, su.StockUserID, st.Ticker };
cboStocks.ItemsSource = stocks;
cboStocks.DisplayMemberPath = "Ticker";
Then, when someone selects an item using the cboStocks combobox I need to figure out what that item is, but I have no idea how to do it. Clearly, this is a simple problem, but its confusing me greatly. cboStocks.SelectedItem is an object, and that object is of the anonymous type created by Linq, but thats all I can figure out.
Anonymous types are only really useful (and should only be used) with a method. Here you're creating the type in one method when you initialise the combo box and then try and access it in another when reading the selected item. This isn't going to work.
You need to create an actual type to assign to the combo box's ItemsSource.
Unfortunately, there's no good way to do that without reflection. Anonymous types aren't really meant to be stashed and retrieved from later in absence of some big reflection framework to check them out. They're pretty much just designed for temporary convenience in methods that are rearranging data internally.
I suggest that you make a named type with the same three fields; then it's a trivial matter to cast it and get what you want back out.
Found the following approach on this blog a while ago, try the following:
private List<T> MakeList<T>(T itemOftype)
{
List<T> newList = new List<T>();
return newList;
}
//create a fake type for anonymous type
var stockType = new {StockID = 0, StockUserId =0, Ticker = string.Empty};
var listOfStocks = MakeList(stockType);
var listOfStocksAnonymous = from st in brdc.tb_dStocks
join su in brdc.tb_rStockUsers on st.StockID equals su.StockID
where su.UserID == userRec.UserID
select new { st.StockID, su.StockUserID, st.Ticker };
listOfStocks = listOfStocksAnonymous.ToList<stockType>();
//now you have a direct access to all anonymous properties
I agree with ChrisF. You should use a concrete type here. However this workaround works if you want to try it out:
T Cast<T>(object obj, T type)
{
return (T)obj;
}
...
var myItem = Cast(cboStocks.SelectedItem, new { st.StockID = 0, su.StockUserID = 0, st.Ticker = "" });
...
So here's what I ended up doing, seems to work pretty well
private class StockInfo
{
public int StockID { get; set; }
public int StockUserID { get; set; }
public string Ticker { get; set; }
public StockInfo(int stockID, int stockUserID, string ticker)
{
StockID = stockID;
StockUserID = stockUserID;
Ticker = ticker;
}
}
BaxRunDataContext brdc = new BaxRunDataContext();
IEnumerable<StockInfo> stocks = from st in brdc.tb_dStocks
join su in brdc.tb_rStockUsers on st.StockID equals su.StockID
where su.UserID == userRec.UserID
select new StockInfo(st.StockID, su.StockUserID, st.Ticker);
cboStocks.ItemsSource = stocks;
cboStocks.DisplayMemberPath = "Ticker";