I was reading about query interceptors. I was disappointed because thats more like a filter instead of an interceptor. In other words you can eather include records or not include them. You are not able to modify records for instance.
If I want to create a query interceptor for my entity Users I could then do something like:
[QueryInterceptor("Users")] // apply to table users
public Expression<Func<User, bool>> UsersOnRead()
{
return cust => cust.IsDeleted == false;
}
What if I instead create the operation: NOTE IS VERY IMPORTANT TO HAVE THE OPERATION NAME JUST LIKE THE ENTITY NAME OTHERWISE IT WILL NOT WORK
[WebGet]
public IEnumerable<User> Users()
{
return this.CurrentDataSource.Users.Where(x=>x.IsDeleted==false);
}
Placing this method instead of the query interceptor makes my service behave exactly the same. Plus I have more power! Is taking this approach a better solution?
I played around a little more with this and one of the issues is navigation properties won't be filtered. Let's say you have an entity called SalesPeople that has a link into IEnumberable of Customers
If you do
[QueryInterceptor("Customers")] // only show active customers
public Expression<Func<Customers, bool>> ActiveCustomers()
{
return cust => cust.IsDeleted == false;
}
when you query your OData feed like WCFDataService.svc/SalesPeople?$expand=Customers the results set for Customers will still have the filter applied.
But this
[WebGet]
public IQueryable<Customers> Customers()
{
return this.CurrentDataSource.Customers.Where(x=>x.IsDeleted==false);
}
When running OData query like WCFDataService.svc/Customers you will have the filtered list on active customers, but when running this WCFDataService.svc/SalesPeople?$expand=Customers the results set for the Customers will include deleted customers.
Related
I am trying to use an Async method with ThenInclude filter and Where.
Its throwing this error. Think has it has something to do with this line:
LkDocumentTypeProduct = new Collection<LkDocumentTypeProduct>(dept.LkDocumentTypeProduct.Where(c => c.LkDocumentTypeId == lkDocumentTypeId).ToList())
and also skipping these lines below in my App Service: How would I make this ThenInclude Filter Async and fix the skipping of lines?
//ITS SKIPPING THESE THREE LINES BELOW
IEnumerable<Product> ProductModel = mapper.Map<IEnumerable<Product>>(Products);
var ProductDto = mapper.Map<IEnumerable<Product>, IEnumerable<ProductDto>>(ProductModel);
return ProductDto;
Error:
ArgumentException: Expression of type 'System.Collections.Generic.IAsyncEnumerable`1[Data.Entities.LkDocumentTypeProduct]' cannot be used for parameter of type
'System.Collections.Generic.IEnumerable`1[Data.Entities.LkDocumentTypeProduct]' of method 'System.Collections.Generic.List`1[Data.Entities.LkDocumentTypeProduct] ToList[LkDocumentTypeProduct]
(System.Collections.Generic.IEnumerable`1[Data.Entities.LkDocumentTypeProduct])'
Parameter name: arg0
Repository:
public async Task<IEnumerable<Product>> GetProductsByDocumentType(int lkDocumentTypeId)
{
var ProductsByDocumentType = All
.Include(dept => dept.LkDocumentTypeProduct)
.Select(dept => new Product
{
ProductId = dept.ProductId,
ProductName = dept.ProductName,
ProductCode = dept.ProductCode,
LkDocumentTypeProduct = new Collection<LkDocumentTypeProduct>(dept.LkDocumentTypeProduct.Where(c => c.LkDocumentTypeId == lkDocumentTypeId).ToList())
}).Where(dept=>dept.LkDocumentTypeProduct.Any());
return await ProductsByDocumentType.ToListAsync();
}
AppService:
public async Task<IEnumerable<ProductDto>> GetProductsByDocumentTypeId(int id)
{
var Products = await ProductRepository.GetProductsByDocumentType(id);
//ITS SKIPPING THESE THREE LINES BELOW !
IEnumerable<Product> ProductModel = mapper.Map<IEnumerable<Product>>(Products);
var ProductDto = mapper.Map<IEnumerable<Product>, IEnumerable<ProductDto>>(ProductModel);
return ProductDto;
}
Controller:
[HttpGet("[Action]/{id}")]
[ProducesResponseType(typeof(ProductDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<IEnumerable<ProductDto>>> GetByDocumentType(int id)
{
IEnumerable<ProductDto> Product = await ProductAppService.GetProductsByDocumentTypeId(id);
if (Product == null)
{
return NotFound();
}
How to add where clause to ThenInclude
It's not "skipping" lines, it's erroring on this line:
IEnumerable ProductModel = mapper.Map>(Products);
Automapper is a helper, not a magician. :)
From what I'm guessing you had a method that was returning Product entities that you've been told to change over to a DTO. Automapper can help you, in an Async way:
Firstly, ignore the fact that you're working with async collections. Automapper maps objects to one another really well, not collections, it works within collections.
In your case given your repository is returning IEnumerable<Product>, to map this to an IEnumerable<ProductDto> use this in your Service:
public async Task<IEnumerable<ProductDto>> GetProductsByDocumentTypeId(int id)
{
var products = await ProductRepository.GetProductsByDocumentType(id);
var dtos = await products.Select(x => Mapper.Map<ProductDto>(x)).ToListAsync();
return dtos;
}
That should get you working, but isn't ideal. The reason is that the repository is returning back a collection of Entities which means that we'd be materializing all fields in each of those entities returned, whether we need them or not. This also opens the door if Automapper "touches" any related entities that haven't been eager loaded as this will trigger lazy-load calls to the database. You may cater for this with Include, but as code matures these lazy load hits can sneak in or you have eager load costs for fields you no longer need.
Automapper offers a brilliant method to address this, ProjectTo but it requires code to leverage EF's IQueryable implementation rather than returning IEnumerable.
For instance if we change the repository method to:
public IQueryable<Product> GetProductsByDocumentType(int lkDocumentTypeId)
{
var query = _dbContext.Products
.Where(p => p.LkDocumentTypeProduct.Any(c => c.LkDocumentTypeId == lkDocumentTypeId));
return query;
}
Then in the service:
public async Task<IEnumerable<ProductDto>> GetProductsByDocumentTypeId(int id)
{
var dtos = await ProductRepository.GetProductsByDocumentType(id)
.ProjectTo<ProductDto>().ToListAsync();
return dtos;
}
What this does is change the repository to return an IQueryable, basically return a promise of retrieving a known set of entities that our consumer can query against. I would be a bit wary of the "All" property as to what that property returns. I've seen a few cases where methods like that do something like return _context.Products.ToList(); which is a real performance / resource trap.
We feed that Queryable to Automapper's provided extension method ProjectTo which then queries just the columns needed to satisfy the ProductDto. The advantages of this approach are considerable. We don't need to explicitly Include related tables or worry about tripping lazy loaded references, and the built query only pulls back the fields our DTO cares about.
I have that Action:
[EnableQuery]
public IHttpActionResult Get()
{
var ordWeb = orderCtx.ORDER.AsQueryable();
var ordWebDTO =ordWeb.ProjectTo<ORDER>(mapper.ConfigurationProvider);
return Ok(ordWebDTO.toList);
}
This an action inside a controller.
orderWebDTO is a result of a mapping with some fields coming from different tables of a Database.
In that case Odata query coming from Url should be processed AFTER "return" call.
when I use Odata Query in the URL (ex. localhost/Controller?%24top=30) EntityFramework load all data from database WITHOUT filter them (in the example: last 30 records).
It's very expensive: I have more than 35k records, and it load all of them and AFTER get last 30...
How can resolve it?
UPDATE 09.13.18
I have that kind of mapping with one value calculated while mapping work.
var c = new MapperConfiguration(
cfg => cfg.CreateMap<ORDER, ORDER_WEB>()
.ForMember(....)
.ReverseMap()
);
mapper = c.CreateMapper();
In the ORDER_WEB model I have that:
public class ORDER_WEB
{
...
...
public string ValueFromEntityFrameworkModel {get; set;}
public string Set_ORDER
{
get
{
ORDER_TYPE tipo = new ORDER_TYPE();
return tipo.GetData(ValueFromEntityFrameworkModel);
}
set { }
}
without toList() It cannot work...
For this reason OData work on ALL records and AFTER assign the values mapping including Set_ORDER.
The point is that : is it possible to do an OData query (with attributes/parameters) with few records and AFTER assign values mapping?
I hope to be clear...
There are errors in your code sample, but if this accurately reflects what you are doing in your actual code sample, then
ordWebDTO.ToList()
Will go to the database and retrieve all 35k records AND THEN apply the OData filters you were looking to apply. Compare that to:
[EnableQuery]
public IQueryable<ORDER> Get()
{
var ordWeb = orderCtx.ORDER.AsQueryable();
var ordWebDTOs =ordWeb.ProjectTo<ORDER>(mapper.ConfigurationProvider);
return ordWebDTOs;
}
This will return an IQueryable against which the OData filters will be applied so that when the list is materialized, it is an efficient query to the database.
I'm trying get all records from a table that have a specific foreign key but I'm struggling to get linq to return anything useful.
Post Model
public class Post
{
//... irrelevant properties
[ForeignKey("Category")]
public int CategoryId;
public virtual Category Category { get; set; }
}
my dbo.Posts table
What I've tried
I've tried several variations of the following:
//id = 7
using (UnitOfWork uwork = new UnitOfWork())
{
var post = uwork.PostRepository.GetAll().Where(c => c.CategoryId == id);
}
This only returns "Non-Public members", which doesn't contain anything useful.
Question
How could I modify my linq query to return all posts that have a specific Foreign Key id?
updates
here's my repository
It seems that you are basically looking at DbQuery<T> object, which is an implementation of IQueryable<T>. Basically LINQ did not make a query yet, because no one asked it for data. So instead it collects all info about the query in an object, to execute it later when needed.
To force it to give you the actual data, simply do ToList, or iterate over posts, or anything:
var post = uwork.PostRepository.GetAll().Where(c => c.CategoryId == id).ToList();
Just make sure to do so before you expose the db context object.
I am working on a program to manage my personal finances. I have an SQLite database that is storing all of the data, and I am able to load/save accounts, bills, payments, etc.
What I am looking to do, is load the account name of the associated account based on the PayeeId of each Payment. I know how to achieve this using SQL, but my data is set up using repositories. For example, I load the Payments by calling
var payments = await _paymentsRepository.LoadAllAsync();
And the LoadAllAsync() method is in RepositoryBase class, and looks like so:
public async Task<IEnumerable<TTable>> LoadAllAsync()
{
var query = _sqliteService.Conn.Table<TTable>();
var array = (await query.ToListAsync()).ToArray();
return array;
}
and is declared in the IPaymentsRepository interface like so:
Task<IEnumerable<Payment>> LoadAllAsync();
Each Payment object has a PayeeId property that links to the Payee for that Payment. The Payment itself doesn't store any of the other information about the Payee, but I would like to be able to load the PayeeName property to display with the Payment information. Is there a simple way to do this, or will I have to create a separate ViewModel to store the "hybrid" data containing both the Payment information as well as the Payee information?
EDIT
I know I can accomplish this using an extra class, for example PaymentInfo or something, and store both the Payment and Payee data, then access it like this: PaymentInfo.Payment.PaymentAmount or PaymentInfo.Payee.PayeeName, but I would have to load them in two separate queries. While this is certainly possible, I am hoping for a solution that can be accomplished in one query, which is why I am looking at using a JOIN. If I need to, I will just use LINQ, but my question is whether or not this is possible using the repository setup that I currently have.
EDIT 2
Here is the repository code. I have tried to only include the relevant pieces. Each table has its own repository. Here is the signature of the PaymentsRepository:
public class PaymentsRepository : RepositoryBase<Payment, int>, IPaymentsRepository
{
}
The RepositoryBase<> looks like this:
public abstract class RepositoryBase<TTable, TKey> : IRepository<TTable, TKey>
where TTable : IKeyedTable<TKey>, new()
{
protected readonly ISqliteService SqliteService;
protected RepositoryBase(ISqliteService sqlLiteService)
{
SqliteService = sqlLiteService;
}
public async Task<IEnumerable<TTable>> LoadAllAsync()
{
var query = SqliteService.Conn.Table<TTable>();
var array = (await query.ToListAsync()).ToArray();
return array;
}
......
}
The IRepository interface:
interface IRepository<TTable, in TKey>
where TTable : IKeyedTable<TKey>, new()
{
Task<TTable> LoadByIdAsync(TKey id);
Task<IEnumerable<TTable>> LoadAllAsync();
Task InsertAsync(TTable item);
Task UpdateAsync(TTable item);
Task DeleteAsync(TTable item);
AsyncTableQuery<TTable> Query();
}
And the ISqliteService:
public interface ISqliteService
{
SQLiteAsyncConnection Conn { get; }
Task<object> ClearLocalDb();
void Reconnect();
}
Everything is ultimately queried against that SQLiteAsyncConnection property, using the built-in SQLite methods. For example, in the LoadAllAsync() function, var query = _sqliteService.Conn.Table<TTable>(); uses this:
public AsyncTableQuery<T> Table<T> ()
where T : new ()
{
//
// This isn't async as the underlying connection doesn't go out to the database
// until the query is performed. The Async methods are on the query iteself.
//
var conn = GetConnection ();
return new AsyncTableQuery<T> (conn.Table<T> ());
}
which is located in SQLiteAsync.cs
I wasn't able to figure out a way to directly query two different tables using LINQ, but I got things working with a "hybrid" class. I just created a PaymentInfo class that has a Payment property and a Payee property, which point to the relevant data. I added a method to my PaymentsRepository that looks like this:
public async Task<IEnumerable<PaymentInfo>> LoadAllPaymentInfoAsync()
{
var payments = await SqliteService.Conn.Table<Payment>().ToListAsync();
var payees = await SqliteService.Conn.Table<Payee>().ToListAsync();
var query = from p1 in payments
join p2 in payees on p1.PayeeId equals p2.Id
select new PaymentInfo() {Payment = p1, Payee = p2};
return query;
}
I am sure that this is not necessarily the best way to accomplish this, but I thought I would share it here in case anyone comes across this page looking to do what I did.
I think you can get IQueryable<Payment> and IQueryable<Payee>, join them in LINQ, and then call .ToArray() on the result.
It will build up the query and perform it only when you actually access the data (in this case, on the ToArray() call). I believe this should generate a single query.
I've got two entity classes User and Connection, and User has a collection of Connection.
class User
{
public string username {get; set;}
public ICollection<Connection> Connections {get; set;}
}
class Connection
{
public string ConnectionId {get; set;}
public string RoomName {get; set;}
}
I've got this SQL Query which retrieves a list of connections for a user
// this code can be moved to the database as a stored procedure or view
var sql = #"SELECT Users.UserName, Users.UserPix,
Connections.ConnectionId, Connections.RoomName, Connections.DateCreated
FROM Users CROSS JOIN Connections
WHERE (Users.UserName = #p0) AND (Connections.RoomName = #p1)";
return _context.Users.SqlQuery(sql, username, roomName).FirstOrDefault();
It returns a user object with an empty connection list, rather than populating the connections with data returned from the database.
I've tried replacing cross-join with inner-join but still the same result
I don't know how else to modify the sql query so that the actual data is returned. How can I do this or is there something I'm missing?
Since you aren't doing anything special here (there aren't even any projections) you can just return the entities directly from the context, including the navigation properties you want with them:
e.g. given these entities:
class User
{
public string username { get; set; }
public ICollection<Connection> Connections { get; set; }
}
class Connection
{
public string ConnectionId { get; set; }
public string RoomName { get; set; }
}
A one-to-many relationship should exist in your database between User -> Connection
In order to get all connections for a particular user, or all connections for all users, or any combination of queries you can think of with filters/aggregates etc on users/connections whilst preserving the relationship you can just use .Include() from QueryableExtensions in System.Data.Entity
Signature is as so if you are interested:
public static IQueryable<T> Include<T, TProperty>(this IQueryable<T> source, Expression<Func<T, TProperty>> path) where T : class
Which is a good thing about EF, you can eager load the child entities on demand without having to mess with the entity design or add new entity classes which don't contain the navigation properties
So basically it boils down to this:
using(YourDbContext context = new YourDbContext())
{
var query = context.Users.Include(user => user.Connections);
// Do stuff with query
}
Don't forget about deferred execution - if you don't know what deferred execution is, and how a query is built up in EF it's worth looking it up - the general rule of thumb is; the less queries that hit the database and the less crunching you do in C# (for database style operations like aggregates) the quicker your app is going to perform - so make sure you don't enumerate the results until you've fully built the query or you are going to be doing all the number crunching on the .NET side using LINQ to objects, not in SQL where it should be!
So calling .ToList() or enumerating the above query will get you the results
EF will translate anything you have done to the correct SQL dialect using the provider you have chosen (likely System.Data.SqlClient). You don't need to write any SQL...
Some other examples of queries you might do:
// Get me users called Fred including their connection details
context.Users.Include(x => x.Connections).Where(u => u.Username == "Fred")
// Get me users that are currently connected to "Game Room"
context.Users.Include(x => x.Connections).Where(u => u.Connections.Any(c => c.RoomName == "Game Room")
None of this requires you to write any SQL - put a SQL trace on when running these queries to see what EF does, and usually it will write queries better than you ever could :) (only sometimes it doesn't and it's usually when you are doing something silly)
Edit
Ok I see you are trying to filter both the user and the connections that come back over the wire, in that case you either need to explicitly load the navigation property as a separate query or use projection to do the filtering
e.g.
Explicit Loading
var user = context.Users.First(x => x.UserName == username);
context.Entry(user).Collection(x => x.Connections).Query().Where(x => x.RoomName == roomName).Load();
That does result in two queries
Projection
var usersConnections = context.Users
.Where(u => u.UserName == userName)
.Select(u => new
{
User = u,
Connections = u.Connections.Where(c => c.RoomName == roomName)
});
This results in an anonymous type which contains a User property and a Connections property. You can always project into a known type too if you need to send this over some sort of domain boundary
This will be a single query against the datasource
To do this I first retrieve/check if the user exist, then I retrieve their connections and add it to the list of connection for that user
public User GetUserAndConnections(string username, string roomname)
{
var user = _context.Users.Find(username);
if (user != null)
{
var connections =
_context.Users.Where(u => u.UserName == username)
.SelectMany(x => x.Connections.Where(p => p.RoomName == roomName))
.ToList();
user.AddExistingConnections(connections);
}
return user;
}