Hello so I am trying to make an some a bit more dynamic, that said i would like to be able to pass in an expression that will include the entities that I am trying to include. when i am trying to do this i keep getting an error that says:
The Include path expression must refer to a navigation property defined on the type. Use dotted paths for reference navigation properties and the Select operator for collection navigation properties.
I have googled this error and saw that they were doing what i can get to work:
context.Contacts.Include(contact=>contact.PhoneNumber)
what I am trying to do is this:
Func<IEntity, IEntity> func = (contact) => ((Contact)contact).PhoneNumber;
Expression<Func<IEntity, IEntity>> expression = (contact)=> func(contact);
context.Contacts.Include(expression);
can someone please expain what I am doing wrong and why?
public interface IEntity
{
int Id { get; set; }
}
public class Contact:IEntity
{
public int Id {get;set;}
public string FirstName { get; set; }
public string LastName { get; set; }
public int PhoneNumberId { get; set; }
public PhoneNumber PhoneNumber { get; set; }
}
public class PhoneNumber:IEntity
{
public int Id { get; set; }
public string Number { get; set; }
}
UPDATE:
I have a repository class that looks at the type of class and uses reflection to get the correct DbSet and returns a IQueryable.
public IQueryable Get(IEntity t)
{
var setMethod = typeof(DbContext)
.GetMethod(nameof(DbContext.Set))
.MakeGenericMethod(t.getType());
var query = (IQueryable)setMethod.Invoke(db, null);
var results = query...
}
I have a table control that goes and gets the correct data using the typeRepository. So I am trying to be able to include entities to that table.
When you don't have a generic type argument, but simple Type parameter, you'd better use the non generic DbContext and IQueryable services provided by EF.
First, you don't need reflection - DbContext provides non generic Set method with Type argument:
public virtual DbSet Set(Type entityType)
As for Include, you can simply use the non generic Include extension method with string argument:
public static IQueryable Include(this IQueryable source, string path)
So the method in question can be implemented like this:
IQueryable query = db.Set(t.GetType());
if (t is Contact)
query = query.Include(nameof(Contact.PhoneNumber));
Not the best OOP practices, but works for the chosen design.
I believe your issue relates to the delegate type you are assigning to your Func. IE: Func<IEntity, IEntity> func
In which case IEntity does not have a navigation property defined of type IEntity
Try assigning the concrete type for your implementation
Func<Contact, PhoneNumber> func
Related
I made some changes to add a field to a table, and now I'm getting an exception on casting.
I've narrowed down the code as much as I can and still get the error.
My changes have been completely removed and I'm still getting the issue.
One question is, if it really is of type ClientService as it claims in the exception, how can it have an issue with casting to IClientID? There are nothing but primitive types involved that I can see.
In this code
try {
using (CFDb db = CreateDbContext<CFDb>(context)) {
ServiceFilter serviceFilter = filters.Where(f => f.ClassName == "ServiceFilter").Select(f => f).FirstOrDefault() as ServiceFilter;
IQueryable<ClientService> query = BuildServiceFilter(serviceFilter, db);
query = BuildClientGovernmentAssistanceFilter(query, (ClientGovernmentAssistanceFilter)null, db);
ClientService[] result = query.ToArray();
}
} catch (Exception ex) {
Logger.LogException("GetServicesReportData", ex, System.Diagnostics.TraceEventType.Error);
return null;
}
query.ToArray() throws
System.NotSupportedException: Unable to cast the type 'Shared.ClientService' to type 'Shared.IClientID'. LINQ to Entities only supports casting EDM primitive or enumeration types.
BuildClientGovernmentAssistanceFilter looks like this
private IQueryable<T> BuildClientGovernmentAssistanceFilter<T>(IQueryable<T> query, ClientGovernmentAssistanceFilter filter, CFPINDb db) where T : IClientID {
query = from clientService in query
join clientGovernmentAssistance in db.ClientGovernmentAssistance on clientService.ClientID equals clientGovernmentAssistance.ClientID
select clientService;
return query;
}
If I comment out the join, or the call to the routine, the error goes away.
I don't think it is an issue with the joined table per se because I can substitute any other table that supports IClientID and get the same error.
Note that T here is IClientID and is called as ClientService.
I can get BuildServiceFilter down to this:
private IQueryable<ClientService> BuildServiceFilter(ServiceFilter serviceFilter, CFPINDb db) {
query = from clientService in db.ClientServices
select clientService;
return query;
}
Here are the definitions:
public interface IClientID {
int ClientID { get; set; }
}
[DataContract]
public class ClientService : IClientID {
[DataMember]
public int ClientID { get; set; }
[DataMember]
public int ServiceID { get; set; }
[DataMember]
public DateTime ServiceDate { get; set; }
}
ClientService is slightly modified:
public DbSet<ClientService> ClientServices { get; set; }
modelBuilder.Entity<ClientService>().HasKey(k => new { k.ClientID, k.ServiceID, k.ServiceDate });
modelBuilder.Entity<ClientService>().Property(p => p.ClientID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
modelBuilder.Entity<ClientService>().Property(p => p.ClientID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
[DataContract]
public class ClientGovernmentAssistance: IClientID {
[DataMember, Key]
public int ClientGovernmentAssistanceID { get; set; }
[DataMember]
public int ClientID { get; set; }
}
It seems like it is something with the join, not with the table elements themselves, but I can't figure what.
If I pull the code out and inline the functions, it gives a different error about casting an anonymous type that makes even less sense.
The solution, as pointed out by Clay, was to add 'class' to all the IQueryable<T> function declarations.
private IQueryable<T> ... where T : class, IClientID
I didn't think this would work since it looked like all the cases where I saw the 'class' solution were going the other direction, trying to cast an interface to a class, whereas my error was casting a class to an interface. Looks like it works both ways.
I currently have a data object for the sake of argument I'll call it foo...
public class Foo
{
public int IndexedKey { get; set;}
public string NonIndexedData {get; set;}
}
I have a generic repository that I want to query this object with, however due to permissions to the database I'm not allowed to do a full table scan. I've therefore been tasked with creating safe query objects.
public class FooQuery
{
public int IndexedKey
}
The generic repository allows an arbitrary predicate and it currently has an implementaation similar to the following...
public class FooRepo : IGenericRepo<Foo>
{
private ICollection<Foo> _allFooRecords; //Imagine this is populated
public ICollection<Foo> GetWhere(Expression<Func<Foo, bool>> criteria)
{
return _allFooRecords.Where(criteria.Compile())
}
}
I want to be able to do the following...
public class FooRepo : IGenericRepo<Foo, FooQuery>
{
private ICollection<Foo> _allFooRecords; //Imagine this is populated
public ICollection<Foo> GetWhere(Expression<Func<FooQuery, bool>> criteria)
{
return _allFooRecords.Where(criteria.Compile())
}
}
The above won't compile. I know that FooQuery's properties definitely contain the right fields that match what is the indexed property of the Foo class, but I can no longer user the criteria.Compile because it will return a function that is incompatible with searching a Foo collection. Is there a way to make this work with the above signature, and what would I need to change about my implementation to get this to work correctly.
Many Thanks
your requirement is: Automatically translate view model expression Expression<Func<FooQuery, bool>> to real model expression Expression<Func<Foo, bool>>. FooQuery is actually your ViewModel, but Foo is real Model.
Automapper could do the magic.
//wrap the translate in base class, so you don't have to do it in each repo
public class BaseRepo<TEntity, TViewMode> : IGenericRepo<TEntity, TViewMode>
{
....
public IWhatever<TEntity> Where(Expression<Func<TViewMode, bool>> vmExpression)
{
var expression = Mapper.Map<Expression<Func<TEntity, bool>>>(vmExpression);
return whateverDataOrQuery.Where(expression);
}
}
Full official document is here:
Expression Translation: https://github.com/AutoMapper/AutoMapper/blob/master/docs/Expression-Translation-(UseAsDataSource).md
Base type:
public class TreeRecord
{
public long id;
public long? parent_id;
}
Record type:
public class TestRecord : TreeRecord
{
public string name;
}
Using Dapper to inset will fail, because it can't find the values. I suspect that is because some of the fields are from the base type.
public long Add(T record, bool returnIndex = false)
{
var query =
$"INSERT INTO {TableName} ({string.Join(", ", _fields)}, parent_id) " +
$"VALUES (#{string.Join(", #", _fields)}, #parent_id);";
// Seems like Dapper won't flatten this type hierachy.
_connection.Execute(query, record);
}
I'm working in a generic container, where T inherits from TR and "extends" the fields. Because of this, I can't simply provide a flattened new ... construct for the value parameters for Dapper. _fields are non-base fields in T, in this case just name from TestRecord.
The exception thrown is:
System.InvalidOperationException: Must add values for the following parameters: #name, #parent_id
even though both are set (p_id to null, name to Books) (inside Dapper's Execute):
Is it a flattening issue? IMO, because all fields on a TestRecord, including the base fields, can be accessed by var.field, Dapper should be able to use it.
The behavior you're seeing here is because ultimately Dapper is looking for properties and not fields. Underneath the covers, it's calling typeof(RecordType).GetProperties(); and enumerating them for the dynamic parameter generation.
If you change your types to:
public class TreeRecord
{
public long id { get; set; }
public long? parent_id { get; set; }
}
public class TestRecord : TreeRecord
{
public string name { get; set; }
}
...then you should see it working, on base or inherited types.
here is a piece of code
public class Order
{
//Primary Key
public long OrderId { get; set; }
//Many to One Relationship
[ParentObject("PersonId")]
public Person Buyer { get; set; }
//One to Many Relationship
[ChildObject("OrderId")]
public List<OrderDetail> OrderDetails { get; set; }
public decimal TotalAmount
{
get
{
if (OrderDetails == null)
return 0;
return OrderDetails.Sum(o => o.LineItemCost);
}
}
public string Notes { get; set; }
}
I am trying to examine the Order object. Now what I want to do is that: get all the properties and try to find out the class type for instance
public List<OrderDetail> OrderDetails { get; set; }
I want to get the type "OrderDetail" that is another class. When I try to get it using PropertyInfo.PropertyType I get "List1" (the Generic type), PropertyInfo.GetType() gives some System.Reflection.RuntimeType, PropertyInfo.DeclaringType gives "Order" (the class that contains the property). I would be obliged if anyone can propose a solution. Thanks in advance.
You can use the GetGenericArguments() method and do:
typeof(Order).GetProperty("OrderDetails").PropertyType.GetGenericArguments()[0]
The type returned by PropertyInfo.PropertyType would be typeof(List<OrderDetail>)
using this you can get the type arguments using Type.GetGenericArguments()
Type propertyType = property.PropertyType;
if(propertyType.IsGeneric && propertyType.GetGenericTypeDefinition() == typeof(List<>))
{
Type argType = propertyType.GetGenericArguments()[0];
// argType will give you your OrderDetail
}
Are you looking for how to know about generic arguments?
Let's say you've an instance of Order in some variable like "someOrder":
someOrder.OrderDetails.GetType().GetGenericArguments();
Since List has a single generic parameter, Type.GetGenericArguments method will return an array of an item: Order type.
It's solving your problem, isn't it?
It seems you didn't know that OrderDetails type is List`1 (a generic list with a single generic parameter). PropertyInfo.PropertyType exposes that because the type of such property is a generic list. You want to know the generic type of that generic list.
I'm trying to pass a object type to a method. I'm doing this for my CRUDRepository that's inherited by others repositories, but i can't figure out how to know with type i'm handling.
For example:
public PageOf<Entity> GetPageOfEntity(int pageNumber, int pageSize)
{
// Here i need to work with the entity type to make calls to database
}
The Entity is a object that's inherited by other entities (user, products, etc) and this method is returning a paged result. I'm doing this to avoid creating a GetPageOf method for each entity that i've.
What's the proper way to pass to the method the object type that i want to deal in paged results?
Edit:
Here's a example of the Entity
public abstract class Entity
{
public virtual long Id { get; set; }
public virtual DateTime Created { get; set; }
}
And the user object:
public class User : Entity
{
public string FirstName { get; set; }
}
As i'm trying to write a single class to handle some crud operations, i need to know in the method what object i'm using (but i'm trying to now create one method for each object type)
Thanks
Make the method generic:
public PageOf<TEntity> GetPageOfEntity<TEntity>(int pageNumber, int pageSize)
where TEntity : Entity
{
Type entityType = typeof(TEntity);
...
}
I am not sure, I understand your question correctly, but can't you just use a generic parameter?
public PageOf<Entity> GetPageOfEntity<TEntity>(int pageNumber, int pageSize)
where TEntity : Entity
{
// Here i need to work with the entity type to make calls to database
}