Get a value of an anonymous object in an anonymous method - c#

I'm trying to write a general method like:
protected async Task<ResultModel<TU>> GetEntityByIdAsync<TU, TKey>(TKey id) where TU : class
{
try
{
var result = await _db.Set<TU>().FirstOrDefaultAsync(x =>
x.GetType().GetProperty("Id").GetValue(???).ToString() == id.ToString());
return result.ToResultModel();
}
catch (Exception ex)
{
_logger.Error($"Error In GetEntityByIdAsync {typeof(TU).Name}. Error: {ex}");
throw;
}
}
but I cannot figure it out what should I put in GetValue(???).
any help?

While you can get it working as you are trying to do, what you will find is that Entity Framework Core is not able to parse the reflection code, meaning it will run the FirstOrDefaultAsync in memory. So if you have a table with 1000 rows, all of those rows will be extracted from the database and filtered there. There's a few solutions:
Use the DbSet.Find method, this looks like it will do exactly what you are trying to achieve. For example:
var entity = await _db.Set<TU>().FindAsync(id)
Make your entities implement a common interface, for example:
public interface IEntity
{
int Id { get; }
}
Meaning your entities will look something like this:
public class SomeEntity : IEntity
{
public int Id { get; set; }
}
And finally your method now looks a lot simpler:
protected async Task<ResultModel<TU>> GetEntityByIdAsync<TU, TKey>(TKey id)
where TU : IEntity
{
return await _db.Set<TU>.FirstOrDefaultAsync(x => x.Id == id);
}
Build up an expression manually. This is a lot more involved and I'm not going to show how to do it as it's almost certainly not needed in this situation.

Related

c# pass type parameter as interface | disable LazyLoading

Maybe this is already solved somewhere but I canĀ“t find a solution...
I work with EF Core and I want to get rid of lazyLoading. I want to write a generic method, which includes different NavigationProperties where needed. I have an Interface which defines crud-methods. I use AutoMapper to map the recieved entities to viewModels. I have different groups of entities. Some can be retrieved without any Navigations, others need specific navigationProperties included.
Actually I want to use one Base-Service-Interface and choose the right implementation during runtime. Or some conditional decision (as described below) in the Service-Implementation.
This is my Service:
public interface IService
{
Task<IEnumerable<TViewModel> GetAsync<TEntity, TViewModel>()
where TEntity : class
where TViewModel : class;
Task PostAsync<TEntity, TViewModel>(TViewModel model)
where TEntity : class
where TViewModel : class;
Task<TViewModel> PatchAsync<TEntity, TViewModel>(int id)
where TEntity : class;
}
This is my Navigation-Interface:
public interface INavigate
{
int NavigationTypeId {get;set;}
NavigationType NavigationType {get;set;}
}
This is my service-implementation:
public class Service
{
public async Task PatchAsync<TEntity, TViewModel>(Guid id,
JsonPatchDocument<TViewModel> patchDoc)
where TEntity : class
where TViewModel : class
{
var query = this.dbContext.Set<TEntity>();
if(typeof(INavigate).IsAssignableFrom(typeof(TEntity)))
{
query = this.dbContext.Set<TEntity>()
.Include(e=>e.NavigationType); // obviously this won't work
}
var entity = await query.FirstAsync();
var viewModel = this.mapper.Map<TViewModel>(entity);
patchDocument.ApplyTo(viewModel);
var updatedEntity = this.mapper.Map(viewModel, entity);
this.dbContext.Entry(entity).CurrentValues.SetValues(updatedEntity);
this.dbContext.SaveChangesAsync();
}
}
So... This isn't a solution. I think there must be some way to solve this problem somehow without generating different services for each Navigation-Interface and specific where-clauses (where TEntity : class, INavigate) - but I don't know where to go from here.
If someone else is looking for a solution I'll post the implementation I ended up with. Thanks to Svyatoslav Danyliv who pointed me to this direction. If someone has a better solution I'd highly appreciate (and accept) an answer.
Here are other questions, that helped me: Include with reflection, ThenInclude and ProjectTo.
I use Automapper and have created a bidirectional Map for every entity/viewModel pair. I have different groups of entities. They either have different navigationProperties which are part of the viewModel or navigationProperties which aren't needed during crud or no navigationProperties at all. I use different interfaces for those navigations (such as INavigate). Some navigations have more than one level to include:
public interface ITopLevelNavigation
{
int TopLevelNavigationId { get; set; }
TopLevelNavigation TopLevelNavigation { get; set; }
}
public class TopLevelNavigation : INavigate
{
public int Id { get; set; }
public int NavigationTypeId { get; set; }
public NavigationType NavigationType { get; set; }
}
And I have entities which have a required relationship:
public interface IRequireDependency
{
int DependencyId { get; set; }
RequiredDependency Dependency { get; set; }
}
I want to assure (for insert and update), that these dependencies exist to avoid an ef-core exception (ForeignKey-Violation) and being able to respond with an accurate and understandable feedback.
Based on the comments for my question, the easiest case is Get:
public Task<IEnumerable<TViewModel> GetAsync<TEntity, TViewModel>()
where TEntity : class
{
return await this.mapper.ProjectTo<TViewModel>(this.dbContext.Set<TEntity>())
.ToListAsync();
}
This works perfectly fine. For generic post I only need to validate required dependencies, everything else can be done with Automapper:
public async Task PostAsync<TEntity, TViewModel>(TViewModel model)
where TEntity : class
where TViewModel : class
{
var entity = this.mapper.Map<TEntity>(model);
if (entity is IRequireDependency dependency)
{
if(!await this.dbContext.RequiredDependency
.AnyAsync(e => e.Id == dependency.DependencyId))
throw new Exception("Invalid DependencyId");
}
await this.dbContext.Set<TEntity>().AddAsync(entity);
await this.dbContext.SaveChangesAsync();
}
To solve the include-issue I have to use the string-overload. I wrote an extension-method:
public static IQueryable<T> IncludeByInterface<T>(this IQueryable<T> queryable)
where T : class
{
if (typeof(INavigate).IsAssignableFrom(typeof(T)))
{
queryable = queryable.Include(nameof(INavigate.NavigationType));
}
if (typeof(ITopLevelNavigation).IsAssignableFrom(typeof(T)))
{
queryable = queryable.Include(nameof(ITopLevelNavigation.TopLevelNavigation));
queryable = queryable.Include($"{nameof(ITopLevelNavigation.TopLevelNavigation)}.
{nameof(ITopLevelNavigation.Navigation.NavigationType)}");
}
return queryable;
}
Now I can patch it like this:
public async Task Patch<TEntity, TViewModel>(int id, JsonPatchDocument<TViewModel> doc)
where TEntity : class
where TViewModel : class
{
var entity = await this.dbContext.Set<TEntity>().AsQueryable()
.IncludeByInterface().FirstAsync();
var viewModel = this.mapper.Map<TViewModel>(entity);
patchDocument.ApplyTo(viewModel);
var updatedEntity = this.mapper.Map(viewModel, entity);
this.dbContext.Entry(entity).CurrentValues.SetValues(updatedEntity);
await this.dbContext.SaveChangesAsync();
}
I hope this helps if someone is facing similar issues. And again: If you think this can be done better - please let me know.

WebApi 2.0 OData, obtain list of SQL tables used in the query

I have a fairly standard WebApi 2.0 OData 4.0 webservice using EF5, and code first approach. This service works and I can query entities and related entities through foreign keys.
The service is read-only and the controllers only have a Get and Get-by-key implemented.
public class MyTableController : MyDbController
{
[EnableQuery]
public IQueryable<MyTable> Get()
{
return db.MyTable;
}
[EnableQuery]
public SingleResult<MyTable> Get([FromODataUri] int key)
{
IQueryable<MyTable> result = db.MyTable.Where(p => p.pk == key);
return SingleResult.Create(result);
}
}
In both Get() implementations, I would like to have access to the list of tables that are being used in the OData and resulting SQL query. MyTable is obviously one of them, but how do I obtain the others (among others, the ones used in (nested) $expand)? I can try to parse the URL myself, but that's doesn't seem like a very good way to go about it.
Create a class CustomizeAttribute inherit from EnableQueryAttribute
Override this method : public virtual IQueryable ApplyQuery
then you get the queryOptions in this method, you can go to the SelectExpandQueryOption and find the ExpandItem, then you get all the table.
public override IQueryable ApplyQuery(IQueryable queryable, ODataQueryOptions queryOptions)
{
if (queryOptions.SelectExpand != null)
{
foreach (var selectItem in queryOptions.SelectExpand.SelectExpandClause.SelectedItems)
{
var expandedItem = selectItem as ExpandedNavigationSelectItem;
if (expandedItem != null)
{
// get the entitySetName, tableName
string entitySetName = expandedItem.NavigationSource.Name;
// can go recursive with expandItem.SelectExpandClause in case we have $epxand=A($expand=B)
}
}
}
return base.ApplyQuery(queryable, queryOptions);
}
Use this attribute on Controller method
[CustomizeAttribute]
public IQueryable<MyTable> Get()
{
return db.MyTable;
}

SingleResult Web Api remove Queryable

I've been trying to access my object <User> by using SingleResult.Create().
The issue here is that my API is returning a wrapped object containing [1] element.
{"Queryable":[{"FirstName":"John","LastName":"Doe","UserName":"JohnDoe","Id":1}]}
When using SingleResult<T>, I've seen that is possible to return 1 single element of a type like this:
{"FirstName":"John","LastName":"Doe","UserName":"JohnDoe","Id":1}
I would like to remove {"Queryable":[]} from my SingleResult<T>
Please Help :D
The problem is that your SingleResult.Create() is creating a wrapper object for your entity, where what you SEEM to want is the entity itself. Either use another method internal to your project that doesn't wrap your object (no idea what that would be), or change the signature of your method and return the entity directly.
[HttpGet]
public virtual TEntity GetById(int id)
{
try
{
var data = GetDataById(id);
return data;
}
catch (Exception e)
{
throw e;
}
}
public virtual TEntity GetbyId(int id)
{
var data = _ctx.Set<TEntity>().Where(e => e.Id == id);
var entity = data.FirstOrDefault();
return entity;
}

Custom value type, EF Code First and routing

In our WebApi project we use EF CodeFirst approach. Also we use 2 types of databases: SQL Server and MySQL. All tables have the field ID, but in SQL Server database this field has int data type, in MySQL database this field is char(36) and contains GUID.
To solve the problem I created a custom value type like IdType and changed all model classes to use that type insted int:
public class Document
{
public IdType ID { get; set; }
public string DocumentNm { get; set; }
...
}
Then I configured the DbContext (e.g for SQL Server)
modelBuilder.Properties<IdType>().Configure(c => c.HasColumnType("int"));
...and changed repository:
public interface IRepository<T> where T : IEntity
{
IQueryable<T> GetAll();
T GetById(IdType id);
...
}
After that, when I try to go to e.g. http://localhost:7081/api/Document, it gives me an error:
Multiple actions were found that match the request: \r\nGet on type
WebUI.Controllers.API.DocumentController\r\nGetById on type
WebUI.Controllers.API.DocumentController
I use default settings of routing. Here is [HttpGet] methods from DocumentController:
public HttpResponseMessage Get() { ... }
public HttpResponseMessage GetById(IdType id) { ... }
How can I solve the problem? Could this be the cause of incorrect implementation of IdType?
P.S. I created IdType for int values as described here. if I have to add more informations, please let me know.
UPDATE
DocumentController:
public HttpResponseMessage GetById(IdType id)
{
var entity = repository.GetById(id);
if (entity == null)
{
return ErrorMsg(HttpStatusCode.NotFound, string.Format("No {0} with ID = {1}", GenericTypeName, id););
}
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
My repository:
public virtual T GetById(IdType id)
{
return GetAll().FirstOrDefault(x => x.ID == id);
}
public virtual IQueryable<T> GetAll()
{
return entities = context.Set<T>();
}
It seems that it not implemented yet in current version of Entity Framework
And as mentioned in task on GitHub
we're currently planning to work on lighting this feature up after our
initial RTM of EF7.

Repository pattern and combined/joined entities as optimised SQL

I'm working on building a repository system on top of a system that is a bit harder to work on than usual (ref. a previous question by me).
Anyway.
My data model is fairly simple at this point: I have several countries, and each country has 0 or more airports. Here's my base Repository:
public abstract class Repository<T> : IRepository<T> where T : Entity, new()
{
protected SimpleSQLManager SQLManager = DatabaseManager.Instance.SQLManager;
public virtual IQueryable<T> GetAll()
{
IQueryable<T> all = SQLManager.Table<T>().AsQueryable();
return all;
}
public virtual IQueryable<T> GetAll(Expression<Func<T, bool>> predicate)
{
IQueryable<T> all = SQLManager.Table<T>().Where(predicate).AsQueryable();
return all;
}
public T GetById(string tableName, int id)
{
return SQLManager.Query<T>( "SELECT * FROM " + tableName + " WHERE Id = ?", id )[0];
}
}
Please ignore the ugly GetById() implementation; I'm running this on Unity3D's (Mono's) .NET libraries, and there's seemingly a bug in there which makes it impossible at the moment to do it properly. Either way, that's not the problem. :)
Now, a normal EntityRepository looks like this (CountryRepository in this case):
public class CountryRepository : Repository<Country>
{
public override IQueryable<Country> GetAll()
{
return base.GetAll().OrderBy( c => c.Name );
}
public Country GetById(int id)
{
return base.GetById( "Country", id );
}
}
The Country entity looks like this:
public class Country : Entity
{
public IQueryable<Airport> Airports()
{
return RepositoryFactory.AirportRepository.GetByCountry( this );
}
}
Then, in my application I can do something like this:
foreach ( Country c in RepositoryFactory.CountryRepository.GetAll() )
{
foreach ( Airport a in c.Airports() )
{
// ...
}
}
...and this works just fine; I'm happy with how everything is abstracted away etc. etc. :)
The problem is that the above code creates one database SELECT per country, which is highly ineffective. This is where I'm not sure where to go forward. I know how to do this with plain old SQL, but I want to go the Linq (or otherwise "non-SQL") way.
Can someone point me in the right/correct direction?
Thanks!
I didn't see anything in SimpleSQL's documentation that looked like it would make sql lite generate a join--something like entity framework's Include method.
That said, you could just bring in all airports and countries into memory with 2 queries and hook them to each other manually, like so:
var airports = RepositoryFactory.AirportRepository.GetAll().ToList();
var countries = RepositoryFactory.CountryRepository.GetAll().ToList();
countries.ForEach(c => c.Airports = airports.Where(a => a.CountryId == c.Id));
Note that you'll need to add a property to your country class:
public IEnumerable<Airport> Airports {get;set;}
I don't like it, but that might be the only way given your environment. You could further abstract the join/mapping logic with generics, but that's the basic idea.

Categories