c# Linq to SQL and Generics - c#

Im quite new to generics and as a learning exercise Im trying to create a simple method that picks an entity from an entity set (table) with a specified ID.
public T GetEntity<T>(int id) where T : class
{
return db.GetTable<T>().SingleOrDefault(o => o.id == id);
}
The above obviously wont work because o in o.id is unknown and thus cannot access the property id - can someone help with how to achieve this?

If all of your entities contain an id property then you can define an interface
public interface IEntity
{
int id { get; }
}
implement it on all your classes and change your method to
public T GetEntity<T>(int id) where T : IEntity
{
return db.GetTable<T>().SingleOrDefault(o => o.id == id);
}
Such interface can be useful in all the places where you want to do something for all your entities, ie. Delete by id. You can add more properties to your interface, ie. timestamp.

If you cannot change your entity classes (for implementing a common interface), you could use a predicate function and pass it into the method
public T GetEntity<T>(int id, Func<T, int, bool> predicate) where T : class
{
return db.GetTable<T>().SingleOrDefault(o => predicate(o,id));
}

Another solution, not recommended for performance reasons, but i shall post it anyway.
public T GetEntity<T>(int id) where T : class
{
return db.GetTable<T>().AsEnumerable().SingleOrDefault(o => (int)o.GetType().GetProperty("Id").GetValue(o,null) == id);
}

You need to set a constrain on your T so the compiler can be sure there is a id property. Once way is to use interfaces, ie.
public interface IUnique
{
int id { get; set; }
}
Then you can say
public T GetEntity<T>(int id) where T : class, IUnique
{
return db.GetTable<T>().SingleOrDefault(o => o.id == id);
}

Related

How to use LINQ to query a [key] property using generic types?

Let's say I have a class:
public class Customer
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
And now I want to create a generic Get() method that might query Customer or any one of several other classes that also have a [key] field defined.
public T Get<T>(int id)
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(i => i. ????? = id);
}
I'm not sure how to use Linq to generically specify the [key] field.
Thanks!
Hope this helps:
public interface IBase
{
int Id { get; }
}
public class Customer : IBase
{
public string Name { get; set; }
public int Id { get ; set ; }
}
public T Get<T>(int id) where T : IBase
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(i => i.Id == id);
}
Just implement the interface IBase in all other classes.
For what is worth I think using contracts is a better way to solve this. But in case you or someone else actually need to check for the attribute here's the answer:
public static T Get<T>(int id)
{
string json = DoSomething(); // <-- making it easy for this post
List<T> items = JsonConvert.DeserializeObject<List<T>>(json);
return items.FirstOrDefault(
item => (int)item.GetType()
.GetProperties()
.FirstOrDefault(
p => Attribute.IsDefined(p, typeof(KeyAttribute))
).GetValue(item) == id
);
}
As far a this part of your question:
I'm not sure how to use Linq to generically specify the [key] field.
The attribute is KeyAttribute you can know that by navigating to the definition (pressing F12 if you're using VS or checking the docs in case your editor doesn't support this feature.
Things to consider:
this is using Reflection reasonably heavily, so it will never have the best performance. That being said you can cache the result from GetProperties() somewhere for faster lookups.
It's hardcoding the cast to int but it appears that's what you're after.
If the collection is null it'll throw an exception.

Using Generics or Templates in c# to simplify object retrieval in my application

I have a database that has multiple tables that are objects in my application, Member, Employer, Invoice etc.
I want to create a generic method that will allow me to retrieve a single object from any of the tables. ex. public Object GetRow(ClassType type, ClassKey key, object valueToFind)
so the Member method right now would be
Member member = _manager.Members.Where(m => m.MemberKey == valueToFind).FirstOrDefauilt();
Employer employer = _manager.Employers.Where(e => e.EmployerKey == valueToFind).FirstOrDefault();
Invoice invoice = _manager.Invoices.Where(i => i.InvoiceKey == valueToFind).FirstOrDefault();
How do i write a generic method that would handle all 3 cases?
I am assuming you are using entity framework. In such case you can write a method like:
public T RetrieveFirst<T>( Expression<Func<T, bool>> filter)
{
return _manager.Set<T>().FirstOrDefault(filter);
}
You can then use it as follows:
var member = RetrieveFirst<Member>(m => m. MemberKey = valueToFind );
If you wanted to unify the filtering, the entities would all have to implement an interface and have the same name of the key property.
I think #ajawad987 has written the perfect answer for you :-)
You could implement an interface that all your entities (employee, manager, etc.) inherit and then implement a generic method that works with that interface.
Basically you define a simple interface that defines the common Key property:
public interface IHasKeyProperty
{
int Key { get; set; }
}
public class Manager : IHasKeyProperty
{
public int Key { get; set; }
// Rest of manager code...
}
public class Employee : IHasKeyProperty
{
public int Key { get; set; }
// Rest of employee code...
}
And then you can write a generic query like this:
public TEntity GetByKey<TEntity>(int key)
where TEntity : IHasKeyProperty, class
{
return this._dbContext.Set<TEntity>().FirstOrDefault(x => x.Key == key);
}
I'm assuming you're using Entity Framework Core, hence the _dbContext variable in my snippet above.
Using the method would look like this:
var myEmployee = GetByKey<Employee>(207);
var myManager = GetByKey<Manager>(101);
To extend #ajawad987's answer, if you need to support different key types you could do:
public interface IHasKeyProperty<TId>
{
TId Key { get; set; }
}
public class Manager : IHasKeyProperty<int>
{
public int Key { get; set; }
// Rest of manager code...
}
public class Employee : IHasKeyProperty<Guid>
{
public Guid Key { get; set; }
// Rest of employee code...
}
public TEntity GetByKey<TEntity, TId>(TId key) where TEntity : IHasKeyProperty<TId>
{
return this._dbContext.Set<TEntity>().FirstOrDefault(x => x.Id == key);
}

Generically find any items where item property value is equal using LINQ

I'm trying to implement a generic repository pattern that implements CRUD operations of generic items in an IEnumerable. I'm having issue of generically finding an item that may already be in the IEnumerable.
I need to programmatically pass which properties make up the 'key' or distinct record and then using LINQ do a Enumerable.Any() on the defined properties so it would see if the object already exists in the IEnumerable.
Here is my code so far:
// Generic Method
public void AddItem(TEntity item)
{
var entities = GetAllItems().ToList(); // Method gets cached IEnumerable<TEntity>
if(true) // Generically see if TEntity is already in the list based of defined properties
{
entities.Add(item);
}
}
// Same function but non-generic
private void AddItem(MyObject object)
{
var objects = GetAllItems().ToList(); //Method gets cached IEnumerable<MyObject>
if(!objects.Any(a=> a.ID == MyObject.ID ))
{
objects.Add(object);
_cache.AddReplaceCache(objects);
}
}
NOTE: the keys can be any property or properties on the object MyObject
You can make your entities inherit from a common interface:
public interface IEntity
{
int ID { get; set; }
}
Then you can redefine your method like
public void AddItem<TEntity>(TEntity entity) where TEntity : IEntity
{
// Now you can access entity.ID
}
Now, if you don't always want to compare via ID, then you can add a predicate to your method:
public void AddItem<TEntity>(TEntity entity, Func<TEntity, bool> predicate)
{
var objects = GetAllItems().ToList();
// You might need some logic in the predicate to check for null
if(!objects.Any(a => predicate(a as TEntity))
{
objects.Add(entity);
_cache.AddReplaceCache(objects);
}
}
Then you would use your function as
repository.AddItem(entity, e => e.ID == entity.ID && e.OtherProperty == entity.OtherProperty);
If I understood you correctly your problem is TEntity doesn't have property ID. So make your entities to inherit common interface which have for example the ID column.
public interface IObject
{
int ID {get; set;}
//define all other properties which are shared between your Entities.
}
public class MyObject : IObject
{
public int ID {get; set;}
//other properties.
}
public void AddItem(TEntity item): where TEntity:IObject
{
var entities = GetAllItems().ToList(); //Method gets cached IEnumerable<TEntity>
if(!objects.Any(a=> a.ID == item.ID ))//Generically see if TEntity is already in the list based of defined properties
{
entities.Add(item);
}
}

EntityFramework Get object by ID?

Is it possible with Generics to get an object from my EntityFramework without knowing the type?
I'm thinking of something along the lines of:
public T GetObjectByID<T>(int id)
{
return (from i in myDatabase.T where i.ID == id select i);
}
Is that doable? Could I use Reflection to somehow take T.GetType().Name and use that for the table?
EDIT
Another hangup, is that not all tables available to me use "ID" as their unique column name.
I think the Find() method may be able to do what you're looking for (DbSet.Find Method).
var someEntity = dbSet.Find(keyValue);
You can define interface implemented by all your entities:
public interface IEntity
{
int Id { get; }
}
and method to retrieve your entity:
public T GetObjectById<T>(int id) where T : class, IEntity
{
return context.CreateObjectSet<T>().SingleOrDefault(e => e.Id == id);
}
You can also use similar approach to one provided in the linked question. You just have to use another method to get your entity:
public virtual T GetByKey<T>(int id) where T : class, IEntity
{
string containerName = context.DefaultContainerName;
string setName = context.CreateObjectSet<T>().EntitySet.Name;
// Build entity key
var entityKey = new EntityKey(containerName + "." + setName, "Id", id);
return (TEntity)Context.GetObjectByKey(entityKey);
}
The difference is that first method always query the database even if you have already loaded the instance to the context whereas second approach first checks if the instance is already loaded. The method is not so efficient because it builds these names over and over. Here is more general approach which can work with any key type and name and here is approach working with complex keys.
Neither of this method directly works with inheritance - you must provide base type to make it work.
It's hard to make a completely generic solution because Entities can have composite keys, but this will work for a simple single key case.
The key is to restrict T to be of type System.Data.Objects.DataClasses.EntityObject, which then has as EntityKey property (which represents the primary key).
static T GetById<T>(object id) where T : EntityObject
{
using (var context = new MyEntities())
{
return context.CreateObjectSet<T>()
.SingleOrDefault(t => t.EntityKey.EntityKeyValues[0].Value == id);
}
}
Finally solved the issue with this:
http://pastebin.com/kjXUKBNS
To call the code I use this:
// Get the id of the object we are saving
PropertyInfo prop = GetProperty<TEntity>(entity, entity.EntityKey.EntityKeyValues[0].Key);
string entityID = prop.GetValue(entity, null).ToString();
// Get the current version of this object
var originalEntity = GetEntity<TEntity>(PropertyEquals, entityID);
This makes the assumption that the Primary Key you are searching on is the first one in the list of Primary Keys.
An other way to get entity from id on entitiy framework
public T Get<T>(int id) where T : EntityObject;
{
var ObjectSet = _context.CreateObjectSet<T>();
var PropName = ObjectSet.EntitySet.ElementType.KeyMembers[0].ToString();
return ObjectSet.Where("it." + PropName + "=" + id).FirstOrDefault();
}
Result
SELECT TOP (1)
[Extent1].[CatId] AS [CatId],
[Extent1].[CatName] AS [CatName],
[Extent1].[CatType] AS [CatType],
FROM [dbo].[Categories] AS [Extent1]
WHERE [Extent1].[CatId] = 1
I think this could help
public static TEntity Find<TEntity>(this ObjectSet<TEntity> set, object id) where TEntity : EntityObject
{
using (var context = new MyObjectContext())
{
var entity = set.Context.CreateObjectSet<TEntity>();
string keyName = entity.FirstOrDefault().EntityKey.EntityKeyValues.FirstOrDefault().Key;
return entity.Where("it." + keyName + " = " + id).FirstOrDefault();
}
}
One thing I've done that has worked is to define a "KeyComparator" interface:
public interface IHasKeyComparator<TObject> {
Expression<Func<TObject, bool>> KeyComparator { get; }
}
And then you can implement it on some object even with a multi-valued key:
public sealed class MultiKeyedObject : IHasKeyComparator<MultiKeyedObject> {
public int ID1 { get; set; }
public string ID2 { get; set; }
readonly Expression<Func<MultiKeyedObject, bool>> mKeyComparator;
public Expression<Func<MultiKeyedObject, bool>> KeyComparator {
get { return mKeyComparator; }
}
public MultiKeyedObject() {
mKeyComparator = other => other.ID1 == ID1 && other.ID2 == ID2;
}
}
And can use it generically if you provide the constraint:
public TObject Refresh<TObject>(TObject pObject)
where TObject : IHasKeyComparator<TObject> {
return this.Set<TObject>().Single(pObject.KeyComparator);
}
Doing it this way requires you to have an instance of the object, though, although you can fill an empty one with key values and then use it to pull from the DB.
Here's the method I use to get an Entity object by ID.
public TEntity GetByKey<TEntity>(object keyValue) where TEntity : class
{
EntityKey key = GetEntityKey<TEntity>(keyValue);
object originalItem;
if (ObjectContext.TryGetObjectByKey(key, out originalItem))
{
return (TEntity)originalItem;
}
return default(TEntity);
}
You may find this a more robust solution than some of those provided above. I've used it as part of a generic repository in a lot of projects. Hope it helps you out.

Extension Methods not working for an interface

Inspired by the MVC storefront the latest project I'm working on is using extension methods on IQueryable to filter results.
I have this interface;
IPrimaryKey
{
int ID { get; }
}
and I have this extension method
public static IPrimaryKey GetByID(this IQueryable<IPrimaryKey> source, int id)
{
return source(obj => obj.ID == id);
}
Let's say I have a class, SimpleObj which implements IPrimaryKey. When I have an IQueryable of SimpleObj the GetByID method doesn't exist, unless I explicitally cast as an IQueryable of IPrimaryKey, which is less than ideal.
Am I missing something here?
It works, when done right. cfeduke's solution works. However, you don't have to make the IPrimaryKey interface generic, in fact, you don't have to change your original definition at all:
public static IPrimaryKey GetByID<T>(this IQueryable<T> source, int id) where T : IPrimaryKey
{
return source(obj => obj.ID == id);
}
Edit: Konrad's solution is better because its far simpler. The below solution works but is only required in situations similar to ObjectDataSource where a method of a class is retrieved through reflection without walking up the inheritance hierarchy. Obviously that's not happening here.
This is possible, I've had to implement a similar pattern when I designed a custom entity framework solution for working with ObjectDataSource:
public interface IPrimaryKey<T> where T : IPrimaryKey<T>
{
int Id { get; }
}
public static class IPrimaryKeyTExtension
{
public static IPrimaryKey<T> GetById<T>(this IQueryable<T> source, int id) where T : IPrimaryKey<T>
{
return source.Where(pk => pk.Id == id).SingleOrDefault();
}
}
public class Person : IPrimaryKey<Person>
{
public int Id { get; set; }
}
Snippet of use:
var people = new List<Person>
{
new Person { Id = 1 },
new Person { Id = 2 },
new Person { Id = 3 }
};
var personOne = people.AsQueryable().GetById(1);
This cannot work due to the fact that generics don't have the ability to follow inheritance patterns. ie. IQueryable<SimpleObj> is not in the inheritance tree of IQueryable<IPrimaryKey>

Categories