in this piece of code:
protected async Task<IEnumerable<KeyValuePair<int, string>>> GetListAsync(string tableName, string key, string value, string orderBy = "Id")
{
var query = $"SELECT {value},{key} FROM {tableName} ORDER BY {orderBy}";
var result = await QueryAsync<dynamic>(query);
return result.Select(x => new KeyValuePair<int, string>(x.Id, x.RoleName));
}
I'm trying to Create a dynamic generic method to handle getting list of any of my tables
the problem is I cannot recognize how to send key and value overloads instead of x.Id, x.RoleName
Try using column aliases:
protected async Task<IEnumerable<KeyValuePair<int, string>>> GetListAsync(string tableName, string key, string value, string orderBy = "Id")
{
var query = $"SELECT [{key}] as [KEY] , [{value}] as [VALUE] FROM [{tableName}] ORDER BY [{orderBy}]";
var result = await QueryAsync<dynamic>(query);
return result.Select(x => new KeyValuePair<int, string>(x.KEY, x.VALUE));
}
Update: I added square brackets everywhere to make it work. Not only is KEY a reserved word, the column names that are passed in might also be keywords, or they could contain spaces/special characters. This will make it work for almost any case (as long as you don't have square brackets inside column names).
It's hard to retrieve properties by name from dynamic, especially if it is implementing IDynamicMetaObjectProvider
So, hack is to use serialization to JSON, and access it's properies by name then.
Return statement should look like this (used Newtonsoft JSON here):
return result.Select(x => {
var jo = (JObject)JToken.FromObject(d);
return new KeyValuePair<int, string>(jo[key], jo[value]);
});
Related
I've a method that returns IEnumerable with Dapper Row.
But I'm trying to access the data without typecasting it to a particular class and I'm getting null value.
Assuming that you are connecting to an SQL database
public List<IDictionary<string, object>> DapperSelect(string connectionString, string query, object parameters)
{
using (var connection = new SqlConnection(connectionString))
{
var result = connection.Query(query, parameters).ToList();
return result.Select(x => (IDictionary<string, object>)x).ToList();
}
}
I don't think that you should be converting your result to IDictionary<string, string> I don't think that has the desired effect you want, not every item in the dictionary is going to be a string, it could be bool, int, double, etc...
But if you insist, You could try to do something like
result.Select(x => ((IDictionary<string, object>)x).ToDictionary(ks => ks.Key, vs => vs.ToString())).ToList();
but I don't recommend it.
Better than all of that is that with dapper you can always strongly type the result returned from SQL, so instead of
connection.Query(query, parameters).ToList();
you would write
connection.Query<YOURTYPE>(query, parameters).ToList();
Something like this:
var foo = db.Query(
"MySp",
new { parameters },
commandType: CommandType.StoredProcedure)
.ToDictionary(
row => (int) row.Id,
row => (string) row.Name);
With row. being the names of the columns and foo being of type Dictionary .
I'm using generics because I need a lot of reusability with different types of data. my main problem is querying data. I'm looking for a way to query something like this:
public void test<T>(int id, T type) where T : class
{
using (var ctx = myDbContext())
{
var myTbl = ctx.Set<T>();
//this line gets the primary key of the table
string key = myTbl.GetPrimaryKey(ctx);
//this is the query I want:
var myResult = myTbl.FirstOrDefault(x => x.key == id);
//let's say if key = "UserId", then (x => x.UserId == id) or something that translates to this.
}
}
also I have implemented following method:
public object GetPropertyValue(object src, string propertyName)
that I can use to get value of a specific property.
but my problem is that I can't use it inside the .FirstOrDefault() call because of the LINQ to query issues with methods.
I currently use this code instead:
var myResult = myTbl.ToList().FirstOrDefault(x => (int)GetPropertyValue(x, key) == id);
which is fine with a few number of rows in database, but when data grows in future it will have a lot of performance impact.
P.S: I'm using EF power tools for reverse engineering code first
Umm, your code sample is completely unclear. Please, atleast provide correct variable names, because now I don't even know where you use 'key' variable.
Also, if you want to store and query objects and it's properties of various inheritance and nestings, consider to use NoSQL databases instead of relation based SQL engines.
First -> Dont use myTbl.ToList().FirstOrDefault(x => (int)GetPropertyValue(x, key) == id) because it generates a select that brings all rows from that table, then filter by id in memory. You should translate your filter to an Expression> that will generare a select filtered by the Id column.
Build your linq expression like this:
var x = Expression.Parameter(typeof(T), "x");
string keyPropName = type.GetPrimaryKey(ctx);
var equalExp = Expression.Equal(
Expression.Property(x, keyPropName),
Expression.Constant(id)
);
var lambda = Expression.Lambda<Func<T, bool>>(equalExp, x); //x => x.Id == idValue
var myResult = myTbl.FirstOrDefault(lambda);
I used your GetPropertyValue and GetPrimaryKey methods.
I have an existing function like this
public int sFunc(string sCol , int iId)
{
string sSqlQuery = " select " + sCol + " from TableName where ID = " + iId ;
// Executes query and returns value in column sCol
}
The table has four columns to store integer values and I am reading them separately using above function.
Now I am converting it to Entity Framework .
public int sFunc(string sCol , int iId)
{
return Convert.ToInt32(TableRepository.Entities.Where(x => x.ID == iId).Select(x => sCol ).FirstOrDefault());
}
but the above function returns an error
input string not in correct format
because it returns the column name itself.
I don't know how to solve this as I am very new to EF.
Any help would be appreciated
Thank you
Not going to be useful for the OP 8 years later, but this question has plenty of views, so I thought it could be helpful for others to have a proper answer.
If you use Entity Framework, you should do Linq projection (Select()), because that leads to the correct, efficient query on the db side, instead of pulling in the entire entity.
With Linq Select() you normally have to provide a lambda, though, so having your your column/property name in a string poses the main difficulty here.
The easiest solution is to use Dynamic LINQ (EntityFramework.DynamicLinq Nuget package). This package provides alternatives to the original Linq methods, which take strings as parameters, and it translates those strings into the appropriate expressions.
Example:
async Task<int> GetIntColumn(int entityId, string intColumnName)
{
return await TableRepository.Entities
.Where(x => x.Id == entityId)
.Select(intColumnName) // Dynamic Linq projection
.Cast<int>()
.SingleAsync();
}
I also made this into an async call, because these days all database calls should be executed asynchronously. When you call this method, you have to await it to get the result (i.e.: var res = await GetIntColumn(...);).
Generic variation
Probably it's more useful to change it into an extension method on IQueryable, and make the column/property type into a generic type parameter, so you could use it with any column/property:
(Provided you have a common interface for all your entities that specifies an Id property.)
public static async Task<TColumn> GetColumn<TEntity, TColumn>(this IQueryable<TEntity> queryable, int entityId, string columnName)
where TEntity : IEntity
{
return await queryable
.Where(x => x.Id == entityId)
.Select(columnName) // Dynamic Linq projection
.Cast<TColumn>()
.SingleAsync();
}
This is called like this: var result = await TableRepository.Entities.GetColumn<Entity, int>(id, columnName);
Generic variation that accepts a list of columns
You can extend it further to support selecting multiple columns dynamically:
public static async Task<dynamic> GetColumns<TEntity>(this IQueryable<TEntity> queryable, int entityId, params string[] columnNames)
where TEntity : IEntity
{
return await queryable
.Where(x => x.Id == entityId)
.Select($"new({string.Join(", ", columnNames)})")
.Cast<dynamic>()
.SingleAsync();
}
This is called like this: var result = await TableRepository.Entities.GetColumns(id, columnName1, columnName2, ...);.
Since the return type and its members are not known compile-time, we have to return dynamic here. Which makes it difficult to work with the result, but if all you want is to serialize it and send it back to the client, it's fine for that purpose.
This might help to solve your problem:
public int sFunc(string sCol, int iId)
{
var _tableRepository = TableRepository.Entities.Where(x => x.ID == iId).Select(e => e).FirstOrDefault();
if (_tableRepository == null) return 0;
var _value = _tableRepository.GetType().GetProperties().Where(a => a.Name == sCol).Select(p => p.GetValue(_tableRepository, null)).FirstOrDefault();
return _value != null ? Convert.ToInt32(_value.ToString()) : 0;
}
This method now work for dynamically input method parameter sCol.
Update:
This is not in context of current question but in general how we can select dynamic column using expression:
var parameter = Expression.Parameter(typeof(EntityTable));
var property = Expression.Property(parameter, "ColumnName");
//Replace string with type of ColumnName and entity table name.
var selector = Expression.Lambda<Func<EntityTable, string>>(property, parameter);
//Before using queryable you can include where clause with it. ToList can be avoided if need to build further query.
var result = queryable.Select(selector).ToList();
You have to try with dynamic LINQ. Details are HERE
Instead of passing the string column name as a parameter, try passing in a lambda expression, like:
sFunc(x => x.FirstColumnName, rowId);
sFunc(x => x.SecondColumnName, rowId);
...
This will in the end give you intellisense for column names, so you avoid possible errors when column name is mistyped.
More about this here: C# Pass Lambda Expression as Method Parameter
However, if you must keep the same method signature, i.e. to support other/legacy code, then you can try this:
public string sFunc(string sCol , int iId)
{
return TableRepository.Entities.Where(x => x.ID == iId).Select(x => (string) x.GetType().GetProperty(sCol).GetValue(x)});
}
You might need to adjust this a bit, I didn't have a quick way of testing this.
You can do this:
var entity = _dbContext.Find(YourEntity, entityKey);
// Finds column to select or update
PropertyInfo propertyInfo = entity.GetType().GetProperty("TheColumnVariable");
I have a table in my database called Citites. I want to retrieve all cities whose name contain any of the values from the strings list.
List<string> strings = new List<string>(new string[] {"burg", "wood", "town"} );
I tried this but it will only match the exact value from the strings list. I need to find values that contain e.g town, like cape town and townsend
List<City> cities = db.Cities.Where(c => strings.Contains(c.name));
EDIT
I'm using LINQ to SQL and Any() doesn't seem to be supported here:
Local sequence cannot be used in LINQ to SQL implementations of query
operators except the Contains operator.
This will do what you need, assuming your LINQ provider supports it - since you did not mention what are you using, we can't test it.
List<City> cities = db.Cities.Where(c => strings.Any(s => c.name.Contains(s)));
In detail: for a single value (like Capetown) you would write
strings.Any(s => "Capetown".Contains(s))
Then you just apply this expression inside your current Where condition as shown in the initial code example.
Since you mention that your LINQ provider does not support .Any() in this context, here is a much more complicated code that builds the query expression dynamically.
var strings = new [] { "burg", "wood", "town" };
// just some sample data
var cities = new[] { new City("Capetown"), new City("Hamburg"), new City("New York"), new City("Farwood") };
var param = Expression.Parameter(typeof(City));
var cityName = Expression.PropertyOrField(param, "Name"); // change the property name
Expression condition = Expression.Constant(false);
foreach (var s in strings)
{
var expr = Expression.Call(cityName, "Contains", Type.EmptyTypes, Expression.Constant(s));
condition = Expression.OrElse(condition, expr);
}
// you can apply the .Where call to any query. In the debugger view you can see that
// the actual expression applied is just a bunch of OR statements.
var query = cities.AsQueryable().Where(Expression.Lambda<Func<City, bool>>(condition, param));
var results = query.ToList();
// the class used in the test
private class City
{
public City(string name) { this.Name = name; }
public string Name;
}
But note that since you mentioned in other comments that the strings collection is rather large, you should really look into building a stored procedure and pass the values as XML parameter to that procedure (then load the XML as table and join it in the query) because this approach of building the query will probably soon run into some sort of "query has too many operands" exception.
I'm not sure if it is supported by your LINQ-provider, but at least in LINQ-To-Objects this works:
List<City> cities = db.Cities.Where(c => strings.Any(s=> c.Name.Contains(s)));
You need to check if the City name contains any of the string in the list, not the other way around:
protected bool ContainsSubstring(string cityName, List<string> strings)
{
foreach(string subString in strings)
{
if (cityName.Contains(subString)) return true;
}
return false;
}
...
List<City> cities = db.Cities.Where(c => this.ContainsSubstring(c.name, strings));
If you find it with a lot of loops, try using FUNC<> which will be better (in performance). I have a sample for that :
List<string> _lookup = new List<string>() { "dE", "SE","yu" };
IEnumerable<string> _src = new List<string> { "DER","SER","YUR" };
Func<string, List<string>, bool> test = (i,lookup) =>
{
bool ispassed = false;
foreach (string lkstring in lookup)
{
ispassed = i.Contains(lkstring, StringComparison.OrdinalIgnoreCase);
if (ispassed) break;
}
return ispassed;
};
var passedCities = _src.Where(i => test(i, _lookup));
var cities = from c in db.Cities.AsEnumerable()
from s in strings
where c.name.ToLower().Contains(s.ToLower())
select c.name;
Do you have the ability to call a stored proc or sql? - you could use SQL fulltextsearch, especially if you're searching multiple terms. It'd probably be a lot quicker than doing string comparisons in SQL.
http://technet.microsoft.com/en-us/library/ms142583.aspx
You could create your search terms by doing string.Join(" ", strings)
I have a array of string say:
String[] Fields=new String[]{RowField,RowField1}
In which I can use the below query to get the values by specifying the values is query i.e RowField and RowField1:
var Result = (
from x in _dataTable.AsEnumerable()
select new
{
Name = x.Field<object>(RowField),
Name1 = x.Field<object>(RowField1)
})
.Distinct();
But if suppose I have many values in the Array like:
String[] Fields= new String[]
{
RowField,
RowField1,
RowField2,
.......
RowField1000
};
How can I use the query here without specifying each of the rowfield in the query?
How can i iterate through the array items inside the LINQ?
var Result = (
from x in _dataTable.AsEnumerable()
select (
from y in Fields
select new KeyValuePair<string, object>(y, x))
.ToDictionary())
.Distinct(DictionariesComparer);
You'll also need to write your own .ToDictionary() extension method and DictionariesComparer method (as Dictionary doesn't implement IEquatable).
Essentially, you want to retrieve specific fields from a DataTable without hardcoding the field names.
The following code will return a single dictionary object per row with the fields you specify in your array. There is no need to create additional extension methods or comparers:
var result = (from row in _dataTable.AsEnumerable()
let projection = from fieldName in fields
select new {Name = fieldName, Value = row[fieldName]}
select projection.ToDictionary(p=>p.Name,p=>p.Value));
The inner select picks the field values you need from each table row and stores them in the projection variable. The outer select converts this variable in a Dictionary
You can iterate over the result to get specific fields like this:
foreach (var row in result)
{
Console.WriteLine(row["field1"]);
}
EDIT:
The above code doesn't return distinct values. It is possible to return distinct values without writing a special comparer using group by but the code is not very pretty:
var result = (from row in table.AsEnumerable()
let projection = from fieldName in fields
select new { Name = fieldName, Value = row[fieldName] }
group projection by projection.Aggregate((v, p) =>
new {
Name = v.Name + p.Name,
Value = (object)String.Format("{0}{1}", v.Value, p.Value)
}) into g
select g.FirstOrDefault().ToDictionary(p=>p.Name,p=>p.Value));
The Aggregate creates a new projection whose Name and Value properties are the concatenation of all name and value fields. The result of the aggregate is used to group all rows and return the first row of each group. It works but it is definitely ugly.
It would be better to create a simple DictionaryComparer like the following code:
public class DictionaryComparer<TKey,TValue>: EqualityComparer<Dictionary<TKey,TValue>>
{
public override bool Equals(Dictionary<TKey, TValue> x, Dictionary<TKey, TValue> y)
{
//True if both sequences of KeyValuePair items are equal
var sequenceEqual = x.SequenceEqual(y);
return sequenceEqual;
}
public override int GetHashCode(Dictionary<TKey, TValue> obj)
{
//Quickly detect differences in size, defer to Equals for dictionaries
//with matching sizes
return obj.Count;
}
}
This allows you to write:
var result = (from row in table.AsEnumerable()
let projection = from fieldName in fields
select new {Name = fieldName, Value = row[fieldName]}
select projection.ToDictionary(p=>p.Name,p=>p.Value))
.Distinct(new DictionaryComparer<string, object>());
There is no foreach linq expression. I typically create my own extension method
Something along the lines of:
public static void Foreach<T>(this IEnumerable<T> items, Action<T> action)
{
foreach(T t in items)
{
action(t);
}
}
However beware if you're planning on using this with Linq2SQL as it could create a lot of db hits!