I am developing a small framework to access the database. I want to add a feature that makes a query using a lambda expression. How do I do this?
public class TestModel
{
public int Id {get;set;}
public string Name {get;set;}
}
public class Repository<T>
{
// do something.
}
For example:
var repo = new Repository<TestModel>();
var query = repo.AsQueryable().Where(x => x.Name == "test");
// This query must be like this:
// SELECT * FROM testmodel WHERE name = 'test'
var list = query.ToDataSet();
// When I call ToDataSet(), it will get the dataset after running the made query.
Go on and create a LINQ Provider (I am sure you don't want to do this, anyway).
It's a lot of work, so maybe you just want to use NHibernate or Entity Framework or something like that.
If your queries are rather simple, maybe you don't need a full blown LINQ Provider. Have a look at Expression Trees (which are used by LINQ Providers).
You can hack something like this:
public static class QueryExtensions
{
public static IEnumerable<TSource> Where<TSource>(this Repo<TSource> source, Expression<Func<TSource, bool>> predicate)
{
// hacks all the way
dynamic operation = predicate.Body;
dynamic left = operation.Left;
dynamic right = operation.Right;
var ops = new Dictionary<ExpressionType, String>();
ops.Add(ExpressionType.Equal, "=");
ops.Add(ExpressionType.GreaterThan, ">");
// add all required operations here
// Instead of SELECT *, select all required fields, since you know the type
var q = String.Format("SELECT * FROM {0} WHERE {1} {2} {3}", typeof(TSource), left.Member.Name, ops[operation.NodeType], right.Value);
return source.RunQuery(q);
}
}
public class Repo<T>
{
internal IEnumerable<T> RunQuery(string query)
{
return new List<T>(); // run query here...
}
}
public class TestModel
{
public int Id { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main(string[] args)
{
var repo = new Repo<TestModel>();
var result = repo.Where(e => e.Name == "test");
var result2 = repo.Where(e => e.Id > 200);
}
}
Please, don't use this as it is. This is just a quick and dirty example how expression trees can be analyzed to create SQL statements.
Why not just use Linq2Sql, NHibernate or EntityFramework...
if you want to do things like
db.Employee
.Where(e => e.Title == "Spectre")
.Set(e => e.Title, "Commander")
.Update();
or
db
.Into(db.Employee)
.Value(e => e.FirstName, "John")
.Value(e => e.LastName, "Shepard")
.Value(e => e.Title, "Spectre")
.Value(e => e.HireDate, () => Sql.CurrentTimestamp)
.Insert();
or
db.Employee
.Where(e => e.Title == "Spectre")
.Delete();
Then check out this, BLToolkit
You might want to look at http://iqtoolkit.codeplex.com/ Which is very complex and i dont recommend you to build something from scratch.
I just wrote something close to dkons's answer I will add it anyway. Just using fluent interface nothing more.
public class Query<T> where T : class
{
private Dictionary<string, string> _dictionary;
public Query()
{
_dictionary = new Dictionary<string, string>();
}
public Query<T> Eq(Expression<Func<T, string>> property)
{
AddOperator("Eq", property.Name);
return this;
}
public Query<T> StartsWith(Expression<Func<T, string>> property)
{
AddOperator("Sw", property.Name);
return this;
}
public Query<T> Like(Expression<Func<T, string>> property)
{
AddOperator("Like", property.Name);
return this;
}
private void AddOperator(string opName, string prop)
{
_dictionary.Add(opName,prop);
}
public void Run(T t )
{
//Extract props of T by reflection and Build query
}
}
Lets say you have a model like
class Model
{
public string Surname{ get; set; }
public string Name{ get; set; }
}
You can use this as :
static void Main(string[] args)
{
Model m = new Model() {Name = "n", Surname = "s"};
var q = new Query<Model>();
q.Eq(x => x.Name).Like(x=>x.Surname).Run(m);
}
Related
I am getting a System.StackOverflowException: 'Exception of type 'System.StackOverflowException' was thrown.' message.
My code as follows, Here I want to assign value to a variable recursively based on the condition and return the list.
public class FancyTree
{
public string title { get; set; }
public string key { get; set; }
public List<FancyTree> children { get; set; }
}
For example the FancyTree Class produces the output like parent->child or parent->parent->child or parent->parent->parent->child just like the Treeview structure.
public JsonResult EmployeesTree()
{
var output = converttoFancyTree(db.Database.GetEmployees(true));
return Json(output, JsonRequestBehavior.AllowGet);
}
public List<FancyTree> converttoFancyTree(List<EmpTable> emps)
{
var output = new List<FancyTree>();
foreach (var emp in emps)
{
var fancyTreeItem = new FancyTree();
fancyTreeItem.key = emp.EMP_ID.ToString();
fancyTreeItem.title = emp.EMP_NAME;
if (!string.IsNullOrEmpty(emp.TEAM))
{
//var empIDs = emp.TEAM?.Split(',')?.Select(Int32.Parse)?.ToList();
var tms = emp.TEAM.Split(',');
if (tms.Length > 0) {
var empIDs = new List<int>();
foreach (var t in tms)
{
empIDs.Add(int.Parse(t));
}
var TeamMembers = emps.Where(x => empIDs.Contains(x.EMP_ID)).ToList();
if (TeamMembers.Count > 0)
{
var childrens = converttoFancyTree(TeamMembers);
fancyTreeItem.children = childrens;
}
}
}
output.Add(fancyTreeItem);
}
return output;
}
I would assume your input is in the form of a plain list of objects, where each object contains the IDs of all the children, and you want to convert this to an object representation, i.e. something like:
public class Employee{
public int Id {get;}
public List<int> SubordinateIds {get;}
}
public class EmployeeTreeNode{
public IReadOnlyList<EmployeeTreeNode> Subordinates {get;} ;
public int Id {get;}
public EmployeeTreeNode(int id, IEnumerable<EmployeeTreeNode> subordinates){
Id = id;
Subordinates = subordinates;
}
To convert this to a tree representation we can start by finding the roots of the tree, i.e. employees that are not subordinate to anyone.
var allSubordinates = allEmployees.SelectMany(e => e.SubordinateIds).ToList();
var allRoots = allEmployees.Select(e => e.Id).Except(allSubordinates);
We then need an efficient way to find a specific employee by the Id, i.e. a dictionary:
var employeeById = allEmployees.ToDictionary(e => e.Id, e => e.SubordinateIds);
We can then finally do the actual recursion, and we can create a generic helper method to assist:
public static TResult MapChildren<T, TResult>(
T root,
Func<T, IEnumerable<T>> getChildren,
Func<T, IEnumerable<TResult>, TResult> map)
{
return RecurseBody(root);
TResult RecurseBody(T item) => map(item, getChildren(item).Select(RecurseBody));
}
...
var tree = allRoots.Select(r => MapChildren(
r,
id => employeeById[id],
(id, subordinates) => new EmployeeTreeNode(id, subordinates)));
This will recurse down to any employee without any subordinates, create EmployeeTreeNode for these, and then eventually traverse up the tree, creating node objects as it goes.
This assumes that there are no loops/cycles. If that is the case you do not have a tree, since trees are by definition acyclic, and the code will crash. You will instead need to handle the more general case of a graph, and this is a harder problem, and you will need to decide how the cycles should be handled.
Given the following query:
context.ToolingOrders
.Include(r => r.ToolingOrderDetails)
.Include(r => r.PurchaseOrder)
.Where(r => r.VendorId.EqualsOrNull(filter.VendorId) &&
r.PoNumber.ContainsOrEmpty(filter.PoNumber))
I use these extension methods to save some code:
public static class FilterExtensions
{
public static bool ContainsOrEmpty(this string source, string toCheck)
{
return string.IsNullOrWhiteSpace(toCheck) || source?.IndexOf(toCheck, StringComparison.OrdinalIgnoreCase) >= 0;
}
public static bool EqualsOrNull(this int source, int? toCheck)
{
return !toCheck.HasValue || source == toCheck;
}
}
The problem is, due to these extension methods, the Where part is not translated into SQL.
I use .Net Core 2.2, which has this client side evaluation feature, which totally hides this issue, but the SQL profiler shows it anyway.
Is there any way to make this work, or I have to write every piece of the where part explicitly?
You could extend the IQueryable for your specific object.
Take the following object for instance:
public class MyObject
{
public string MyProperty;
}
You could write an extension like this:
public static class MyQueryExtension
{
public static IQueryable<MyObject> WhereMyPropertyNull(this IQueryable<MyObject> queryable)
{
return queryable.Where(obj => obj.MyProperty == null);
}
}
And use it like this:
var queryable = new List<MyObject>().AsQueryable();
var result = queryable.WhereMyPropertyNull().ToList();
EDIT
Based on some feedback i updated my answer to handle generics.
public static class Extensions
{
public static IQueryable<TEntity> EqualOrNull<TEntity, TProperty>(this IQueryable<TEntity> source, Func<TEntity, TProperty> selector, TProperty match)
{
return source.Where(entity => Match(selector.Invoke(entity), match));
}
private static bool Match<TEntity, TProperty>(TEntity entity, TProperty match)
{
if (entity == null) {
return true;
} else {
return entity.Equals(match);
}
}
}
It can be used to pass the value of a property to the where statement:
var list = new List<MyObject>();
list.Add(new MyObject {MyProperty = "Test"});
list.Add(new MyObject {MyProperty = "NoMatch"});
list.Add(new MyObject {MyProperty = null});
var result = list.AsQueryable()
.EqualOrNull(o => o.MyProperty, "Test")
.ToList();
I have a DbContext where I would like to run a query to return only specific columns, to avoid fetching all the data.
The problem is that I would like to specify the column names with a set of strings, and I would like to obtain an IQueryable of the original type, i.e. without constructing an anonymous type.
Here is an example:
// Install-Package Microsoft.AspNetCore.All
// Install-Package Microsoft.EntityFrameworkCore
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
public class Person {
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class TestContext : DbContext {
public virtual DbSet<Person> Persons { get; set; }
public TestContext(DbContextOptions<TestContext> options) : base(options) {
}
}
class Program {
static void Main(string[] args) {
var builder = new DbContextOptionsBuilder<TestContext>();
builder.UseInMemoryDatabase(Guid.NewGuid().ToString());
var context = new TestContext(builder.Options);
context.Persons.Add(new Person { FirstName = "John", LastName = "Doe" });
context.SaveChanges();
// How can I express this selecting columns with a set of strings?
IQueryable<Person> query = from p in context.Persons select new Person { FirstName = p.FirstName };
}
}
I would like to have something like this method:
static IQueryable<Person> GetPersons(TestContext context, params string[] fieldsToSelect) {
// ...
}
Is there a way I can do this?
Since you are projecting (selecting) the members of the type T to the same type T, the required Expression<Func<T, T>> can relatively easy be created with Expression class methods like this:
public static partial class QueryableExtensions
{
public static IQueryable<T> SelectMembers<T>(this IQueryable<T> source, params string[] memberNames)
{
var parameter = Expression.Parameter(typeof(T), "e");
var bindings = memberNames
.Select(name => Expression.PropertyOrField(parameter, name))
.Select(member => Expression.Bind(member.Member, member));
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
var selector = Expression.Lambda<Func<T, T>>(body, parameter);
return source.Select(selector);
}
}
Expression.MemberInit is the expression equivalent of the new T { Member1 = x.Member1, Member2 = x.Member2, ... } C# construct.
The sample usage would be:
return context.Set<Person>().SelectMembers(fieldsToSelect);
This can be achieved by using Dynamic Linq.
and for .Net Core - System.Linq.Dynamic.Core
With Dynamic Linq you can pass in your SELECT and WHERE as a string.
Using your example, you could then do something like:
IQueryable<Person> query = context.Persons
.Select("new Person { FirstName = p.FirstName }");
Based on answer of Ivan I made crude version of caching function to eliminate the toll layed on us by using of reflexion. It allow as to lower this toll from milliseconds to microseconds on repeated requests (typical for DbAccess API, for example).
public static class QueryableExtensions
{
public static IQueryable<T> SelectMembers<T>(this IQueryable<T> source, IEnumerable<string> memberNames)
{
var result = QueryableGenericExtensions<T>.SelectMembers(source, memberNames);
return result;
}
}
public static class QueryableGenericExtensions<T>
{
private static readonly ConcurrentDictionary<string, ParameterExpression> _parameters = new();
private static readonly ConcurrentDictionary<string, MemberAssignment> _bindings = new();
private static readonly ConcurrentDictionary<string, Expression<Func<T, T>>> _selectors = new();
public static IQueryable<T> SelectMembers(IQueryable<T> source, IEnumerable<string> memberNames)
{
var parameterName = typeof(T).FullName;
var requestName = $"{parameterName}:{string.Join(",", memberNames.OrderBy(x => x))}";
if (!_selectors.TryGetValue(requestName, out var selector))
{
if (!_parameters.TryGetValue(parameterName, out var parameter))
{
parameter = Expression.Parameter(typeof(T), typeof(T).Name.ToLowerInvariant());
_ = _parameters.TryAdd(parameterName, parameter);
}
var bindings = memberNames
.Select(name =>
{
var memberName = $"{parameterName}:{name}";
if (!_bindings.TryGetValue(memberName, out var binding))
{
var member = Expression.PropertyOrField(parameter, name);
binding = Expression.Bind(member.Member, member);
_ = _bindings.TryAdd(memberName, binding);
}
return binding;
});
var body = Expression.MemberInit(Expression.New(typeof(T)), bindings);
selector = Expression.Lambda<Func<T, T>>(body, parameter);
_selectors.TryAdd(requestName, selector);
}
return source.Select(selector);
}
}
Example of results after sequential run with same params (please note that this is NANOseconds):
SelectMembers time ... 3092214 ns
SelectMembers time ... 145724 ns
SelectMembers time ... 38613 ns
SelectMembers time ... 1969 ns
I have no idea why the time decreases gradually, not from "without cache" to "with cache", may be it is because of my environment with loop of questioning 4 servers with same request and some deep-level magic with asyncs. Repeating request produces consistent results similar to the last one +/- 1-2 microseconds.
Try this code:
string fieldsToSelect = "new Person { FirstName = p.FirstName }"; //Pass this as parameter.
public static IQueryable<Person> GetPersons(TestContext context, string fieldsToSelect)
{
IQueryable<Person> query = context.Persons.Select(fieldsToSelect);
}
I was able to do this with the package https://github.com/StefH/System.Linq.Dynamic.Core so easily.
Here is an example code.
use namespacing, using System.Linq.Dynamic.Core;
//var selectQuery = "new(Name, Id, PresentDetails.RollNo)";
var selectQuery = "new(Name, Id, PresentDetails.GuardianDetails.Name as GuardianName)";
var students = dbContext.Students
.Include(s => s.PresentDetails)
.Include(s => s.PresentDetails.GuardianDetails)
.Where(s => s.StudentStatus == "Admitted")
.Select(selectQuery);
var students = dbContext.Students
.Include(s => s.PresentDetails)
.Where(s => s.StudentStatus == "Admitted")
.Select(p => new Person()
{
Id = p.Id,
Name = p.Name
});
Why not minimize the selected columns in the regular way? this is way cleaner.
This question already has answers here:
Dynamic LINQ OrderBy on IEnumerable<T> / IQueryable<T>
(24 answers)
Closed 2 years ago.
The community reviewed whether to reopen this question last year and left it closed:
Original close reason(s) were not resolved
I'm attempting to use a variable inside of a LINQ select statement.
Here is an example of what I'm doing now.
using System;
using System.Collections.Generic;
using System.Linq;
using Faker;
namespace ConsoleTesting
{
internal class Program
{
private static void Main(string[] args)
{
List<Person> listOfPersons = new List<Person>
{
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person(),
new Person()
};
var firstNames = Person.GetListOfAFirstNames(listOfPersons);
foreach (var item in listOfPersons)
{
Console.WriteLine(item);
}
Console.WriteLine();
Console.ReadKey();
}
public class Person
{
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Person()
{
FirstName = NameFaker.Name();
LastName = NameFaker.LastName();
City = LocationFaker.City();
CountryName = LocationFaker.Country();
}
public static List<string> GetListOfAFirstNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCities(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfCountries(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
public static List<string> GetListOfLastNames(IEnumerable<Person> listOfPersons)
{
return listOfPersons.Select(x => x.FirstName).Distinct().OrderBy(x => x).ToList();
}
}
}
}
I have a Some very not DRY code with the GetListOf... Methods
i feel like i should be able to do something like this
public static List<string> GetListOfProperty(
IEnumerable<Person> listOfPersons, string property)
{
return listOfPersons.Select(x =>x.property).Distinct().OrderBy(x=> x).ToList();
}
but that is not vaild code. I think the key Might Relate to Creating a Func
if That is the answer how do I do that?
Here is a second attempt using refelection But this is also a no go.
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Person person = new Person();
Type t = person.GetType();
PropertyInfo prop = t.GetProperty(property);
return listOfPersons.Select(prop).Distinct().OrderBy(x =>
x).ToList();
}
I think the refection might be a DeadEnd/red herring but i thought i would show my work anyway.
Note Sample Code is simplified in reality this is used to populate a datalist via AJAX to Create an autocomplete experience. That object has 20+ properties and I can complete by writing 20+ methods but I feel there should be a DRY way to complete this. Also making this one method also would clean up my controller action a bunch also.
Question:
Given the first section of code is there a way to abstract those similar methods into a single method buy passing some object into the select Statement???
Thank you for your time.
You would have to build the select
.Select(x =>x.property).
by hand. Fortunately, it isn't a tricky one since you expect it to always be the same type (string), so:
var x = Expression.Parameter(typeof(Person), "x");
var body = Expression.PropertyOrField(x, property);
var lambda = Expression.Lambda<Func<Person,string>>(body, x);
Then the Select above becomes:
.Select(lambda).
(for LINQ based on IQueryable<T>) or
.Select(lambda.Compile()).
(for LINQ based on IEnumerable<T>).
Note that anything you can do to cache the final form by property would be good.
From your examples, I think what you want is this:
public static List<string> GetListOfProperty(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (string)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
typeof is a built-in operator in C# that you can "pass" the name of a type to and it will return the corresponding instance of Type. It works at compile-time, not runtime, so it doesn't work like normal functions.
PropertyInfo has a GetValue method that takes an object parameter. The object is which instance of the type to get the property value from. If you are trying to target a static property, use null for that parameter.
GetValue returns an object, which you must cast to the actual type.
person => (string)prop.GetValue(person) is a lamba expression that has a signature like this:
string Foo(Person person) { ... }
If you want this to work with any type of property, make it generic instead of hardcoding string.
public static List<T> GetListOfProperty<T>(IEnumerable<Person>
listOfPersons, string property)
{
Type t = typeof(Person);
PropertyInfo prop = t.GetProperty(property);
return listOfPersons
.Select(person => (T)prop.GetValue(person))
.Distinct()
.OrderBy(x => x)
.ToList();
}
I would stay away from reflection and hard coded strings where possible...
How about defining an extension method that accepts a function selector of T, so that you can handle other types beside string properties
public static List<T> Query<T>(this IEnumerable<Person> instance, Func<Person, T> selector)
{
return instance
.Select(selector)
.Distinct()
.OrderBy(x => x)
.ToList();
}
and imagine that you have a person class that has an id property of type int besides those you already expose
public class Person
{
public int Id { get; set; }
public string City { get; set; }
public string CountryName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
all you need to do is fetch the results with type safe lambda selectors
var ids = listOfPersons.Query(p => p.Id);
var firstNames = listOfPersons.Query(p => p.FirstName);
var lastNames = listOfPersons.Query(p => p.LastName);
var cityNames = listOfPersons.Query(p => p.City);
var countryNames = listOfPersons.Query(p => p.CountryName);
Edit
As it seems you really need hardcoded strings as the property inputs, how about leaving out some dynamism and use a bit of determinism
public static List<string> Query(this IEnumerable<Person> instance, string property)
{
switch (property)
{
case "ids": return instance.Query(p => p.Id.ToString());
case "firstName": return instance.Query(p => p.FirstName);
case "lastName": return instance.Query(p => p.LastName);
case "countryName": return instance.Query(p => p.CountryName);
case "cityName": return instance.Query(p => p.City);
default: throw new Exception($"{property} is not supported");
}
}
and access the desired results as such
var cityNames = listOfPersons.Query("cityName");
You should be able to do it with Reflection. I use it something similar.
Just change your reflection try to this:
public static List<string> GetListOfValues(IEnumerable<Person> listOfPersons, string propertyName)
{
var ret = new List<string>();
PropertyInfo prop = typeof(Person).GetProperty(propertyName);
if (prop != null)
ret = listOfPersons.Select(p => prop.GetValue(p).ToString()).Distinct().OrderBy(x => x).ToList();
return ret;
}
I hope it helps.
It's based on C# 6
You can also use this. works for me.
public static class ObjectReflectionExtensions
{
public static object GetValueByName<T>(this T thisObject, string propertyName)
{
PropertyInfo prop = typeof(T).GetProperty(propertyName);
return prop.GetValue(thisObject);
}
}
And call like this.
public static List<string> GetListOfProperty(IEnumerable<Person> listOfPersons, string propertyName)
{
return listOfPersons.Select(x =>(string)x.GetValueByName(propertyName)).Distinct().OrderBy(x=> x).ToList();
}
If you want to select all the values:
object[] foos = objects.Select(o => o.GetType().GetProperty("PropertyName").GetValue(o)).ToArray();
I wrote the following method.
public T GetByID(int id)
{
var dbcontext = DB;
var table = dbcontext.GetTable<T>();
return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}
Basically it's a method in a Generic class where T is a class in a DataContext.
The method gets the table from the type of T (GetTable) and checks for the first property (always being the ID) to the inputted parameter.
The problem with this is I had to convert the table of elements to a list first to execute a GetType on the property, but this is not very convenient because all the elements of the table have to be enumerated and converted to a List.
How can I refactor this method to avoid a ToList on the whole table?
[Update]
The reason I can't execute the Where directly on the table is because I receive this exception:
Method 'System.Reflection.PropertyInfo[] GetProperties()' has no supported translation to SQL.
Because GetProperties can't be translated to SQL.
[Update]
Some people have suggested using an interface for T, but the problem is that the T parameter will be a class that is auto generated in [DataContextName].designer.cs, and thus I cannot make it implement an interface (and it's not feasible implementing the interfaces for all these "database classes" of LINQ; and also, the file will be regenerated once I add new tables to the DataContext, thus loosing all the written data).
So, there has to be a better way to do this...
[Update]
I have now implemented my code like Neil Williams' suggestion, but I'm still having problems. Here are excerpts of the code:
Interface:
public interface IHasID
{
int ID { get; set; }
}
DataContext [View Code]:
namespace MusicRepo_DataContext
{
partial class Artist : IHasID
{
public int ID
{
get { return ArtistID; }
set { throw new System.NotImplementedException(); }
}
}
}
Generic Method:
public class DBAccess<T> where T : class, IHasID,new()
{
public T GetByID(int id)
{
var dbcontext = DB;
var table = dbcontext.GetTable<T>();
return table.SingleOrDefault(e => e.ID.Equals(id));
}
}
The exception is being thrown on this line: return table.SingleOrDefault(e => e.ID.Equals(id)); and the exception is:
System.NotSupportedException: The
member
'MusicRepo_DataContext.IHasID.ID' has
no supported translation to SQL.
[Update] Solution:
With the help of Denis Troller's posted answer and the link to the post at the Code Rant blog, I finally managed to find a solution:
public static PropertyInfo GetPrimaryKey(this Type entityType)
{
foreach (PropertyInfo property in entityType.GetProperties())
{
ColumnAttribute[] attributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), true);
if (attributes.Length == 1)
{
ColumnAttribute columnAttribute = attributes[0];
if (columnAttribute.IsPrimaryKey)
{
if (property.PropertyType != typeof(int))
{
throw new ApplicationException(string.Format("Primary key, '{0}', of type '{1}' is not int",
property.Name, entityType));
}
return property;
}
}
}
throw new ApplicationException(string.Format("No primary key defined for type {0}", entityType.Name));
}
public T GetByID(int id)
{
var dbcontext = DB;
var itemParameter = Expression.Parameter(typeof (T), "item");
var whereExpression = Expression.Lambda<Func<T, bool>>
(
Expression.Equal(
Expression.Property(
itemParameter,
typeof (T).GetPrimaryKey().Name
),
Expression.Constant(id)
),
new[] {itemParameter}
);
return dbcontext.GetTable<T>().Where(whereExpression).Single();
}
What you need is to build an expression tree that LINQ to SQL can understand. Assuming your "id" property is always named "id":
public virtual T GetById<T>(short id)
{
var itemParameter = Expression.Parameter(typeof(T), "item");
var whereExpression = Expression.Lambda<Func<T, bool>>
(
Expression.Equal(
Expression.Property(
itemParameter,
"id"
),
Expression.Constant(id)
),
new[] { itemParameter }
);
var table = DB.GetTable<T>();
return table.Where(whereExpression).Single();
}
This should do the trick. It was shamelessly borrowed from this blog.
This is basically what LINQ to SQL does when you write a query like
var Q = from t in Context.GetTable<T)()
where t.id == id
select t;
You just do the work for LTS because the compiler cannot create that for you, since nothing can enforce that T has an "id" property, and you cannot map an arbitrary "id" property from an interface to the database.
==== UPDATE ====
OK, here's a simple implementation for finding the primary key name, assuming there is only one (not a composite primary key), and assuming all is well type-wise (that is, your primary key is compatible with the "short" type you use in the GetById function):
public virtual T GetById<T>(short id)
{
var itemParameter = Expression.Parameter(typeof(T), "item");
var whereExpression = Expression.Lambda<Func<T, bool>>
(
Expression.Equal(
Expression.Property(
itemParameter,
GetPrimaryKeyName<T>()
),
Expression.Constant(id)
),
new[] { itemParameter }
);
var table = DB.GetTable<T>();
return table.Where(whereExpression).Single();
}
public string GetPrimaryKeyName<T>()
{
var type = Mapping.GetMetaType(typeof(T));
var PK = (from m in type.DataMembers
where m.IsPrimaryKey
select m).Single();
return PK.Name;
}
What if you rework this to use GetTable().Where(...), and put your filtering there?
That would be more efficient, since the Where extension method should take care of your filtering better than fetching the entire table into a list.
Some thoughts...
Just remove the ToList() call, SingleOrDefault works with an IEnumerably which I presume table is.
Cache the call to e.GetType().GetProperties().First() to get the PropertyInfo returned.
Cant you just add a constraint to T that would force them to implement an interface that exposes the Id property?
Maybe executing a query might be a good idea.
public static T GetByID(int id)
{
Type type = typeof(T);
//get table name
var att = type.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
string tablename = att == null ? "" : ((TableAttribute)att).Name;
//make a query
if (string.IsNullOrEmpty(tablename))
return null;
else
{
string query = string.Format("Select * from {0} where {1} = {2}", new object[] { tablename, "ID", id });
//and execute
return dbcontext.ExecuteQuery<T>(query).FirstOrDefault();
}
}
Regarding:
System.NotSupportedException: The member 'MusicRepo_DataContext.IHasID.ID' has no supported translation to SQL.
The simple workaround to your initial problem is to specify an Expression. See below, it works like a charm for me.
public interface IHasID
{
int ID { get; set; }
}
DataContext [View Code]:
namespace MusicRepo_DataContext
{
partial class Artist : IHasID
{
[Column(Name = "ArtistID", Expression = "ArtistID")]
public int ID
{
get { return ArtistID; }
set { throw new System.NotImplementedException(); }
}
}
}
Ok, check this demo implementation. Is attempt to get generic GetById with datacontext(Linq To Sql). Also compatible with multi key property.
using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
public static class Programm
{
public const string ConnectionString = #"Data Source=localhost\SQLEXPRESS;Initial Catalog=TestDb2;Persist Security Info=True;integrated Security=True";
static void Main()
{
using (var dc = new DataContextDom(ConnectionString))
{
if (dc.DatabaseExists())
dc.DeleteDatabase();
dc.CreateDatabase();
dc.GetTable<DataHelperDb1>().InsertOnSubmit(new DataHelperDb1() { Name = "DataHelperDb1Desc1", Id = 1 });
dc.GetTable<DataHelperDb2>().InsertOnSubmit(new DataHelperDb2() { Name = "DataHelperDb2Desc1", Key1 = "A", Key2 = "1" });
dc.SubmitChanges();
Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb1>(), 1).Name);
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb2>(), new PkClass { Key1 = "A", Key2 = "1" }).Name);
}
}
//Datacontext definition
[Database(Name = "TestDb2")]
public class DataContextDom : DataContext
{
public DataContextDom(string connStr) : base(connStr) { }
public Table<DataHelperDb1> DataHelperDb1;
public Table<DataHelperDb2> DataHelperD2;
}
[Table(Name = "DataHelperDb1")]
public class DataHelperDb1 : Entity<DataHelperDb1, int>
{
[Column(IsPrimaryKey = true)]
public int Id { get; set; }
[Column]
public string Name { get; set; }
}
public class PkClass
{
public string Key1 { get; set; }
public string Key2 { get; set; }
}
[Table(Name = "DataHelperDb2")]
public class DataHelperDb2 : Entity<DataHelperDb2, PkClass>
{
[Column(IsPrimaryKey = true)]
public string Key1 { get; set; }
[Column(IsPrimaryKey = true)]
public string Key2 { get; set; }
[Column]
public string Name { get; set; }
}
public class Entity<TEntity, TKey> where TEntity : new()
{
public static TEntity SearchObjInstance(TKey key)
{
var res = new TEntity();
var targhetPropertyInfos = GetPrimaryKey<TEntity>().ToList();
if (targhetPropertyInfos.Count == 1)
{
targhetPropertyInfos.First().SetValue(res, key, null);
}
else if (targhetPropertyInfos.Count > 1)
{
var sourcePropertyInfos = key.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var sourcePi in sourcePropertyInfos)
{
var destinationPi = targhetPropertyInfos.FirstOrDefault(x => x.Name == sourcePi.Name);
if (destinationPi == null || sourcePi.PropertyType != destinationPi.PropertyType)
continue;
object value = sourcePi.GetValue(key, null);
destinationPi.SetValue(res, value, null);
}
}
return res;
}
}
public static IEnumerable<PropertyInfo> GetPrimaryKey<T>()
{
foreach (var info in typeof(T).GetProperties().ToList())
{
if (info.GetCustomAttributes(false)
.Where(x => x.GetType() == typeof(ColumnAttribute))
.Where(x => ((ColumnAttribute)x).IsPrimaryKey)
.Any())
yield return info;
}
}
//Move in repository pattern
public static TEntity GetByID<TEntity, TKey>(Table<TEntity> source, TKey id) where TEntity : Entity<TEntity, TKey>, new()
{
var searchObj = Entity<TEntity, TKey>.SearchObjInstance(id);
Console.WriteLine(source.Where(e => e.Equals(searchObj)).ToString());
return source.Single(e => e.Equals(searchObj));
}
}
Result:
SELECT [t0].[Id], [t0].[Name]
FROM [DataHelperDb1] AS [t0]
WHERE [t0].[Id] = #p0
Name:DataHelperDb1Desc1
SELECT [t0].[Key1], [t0].[Key2], [t0].[Name]
FROM [DataHelperDb2] AS [t0]
WHERE ([t0].[Key1] = #p0) AND ([t0].[Key2] = #p1)
Name:DataHelperDb2Desc1