Entity Framework, Get element from related table based on includes - c#

I recognize that this question has been asked in various forms before, but none of them that I have read quite give me what I need.
Question: Given the model below, and only knowing the client ID, how can I get a List that consists of the Uid of each activity that the client is permitted to perform?
I start with code something like the code below, but don't know where to go from there. I assume that it has something to do with a select statement - but my ignorance of LINQ is overwhelming me :)
public List<string> GetClientAndPermittedActivities ( int clientId )
{
var permittedActivities = ReadAllRaw()
.Where(c => c.Id == clientId)
.Include("ClientType")
.Include("Role")
.Include("PermittedActivities")
.Include("Activities");
}
internal IQueryable<TE> ReadAllRaw ()
{
return base.ReadAll();
}
// READ (ALL)
internal IQueryable<T> ReadAll ()
{
return DbSet;
}
private void SetContext ( DbContext context )
{
this.Context = context;
this.DbSet = Context.Set<T>();
}

You can just 'chain' your includes together with your navigation properties.
public List<string> GetClientAndPermittedActivities(int clientId)
{
var permittedActivities =
ReadAllRaw()
.Include("ClientType.Role.PermittedActivities")
.Where(c => c.Id == clientId)
.ToList();
}
That should get you all the PermittedActivities.
EDIT
If you add
using System.Data.Entity;
to your class, you can use lambdas to create your statements.
public List<string> GetClientAndPermittedActivities(int clientId)
{
var permittedActivities =
ReadAllRaw().Include(x => c.ClientType.Role.PermittedActivities.SelectMany(pa => pa.Activities.Uid))
.Where(c => c.Id == clientId)
.ToList();
}
And with this you receive your requested Activity.Uid.

Your method can looks like this:
public List<string> GetClientAndPermittedActivities(int clientId)
{
return ReadAllRaw()
.Where(c => c.Id == clientId)
.SelectMany(
ct => ct.ClientType
.Role
.PermittedActivities,
(s, c) => c.Uid
)
.ToList();
}

Should be something like
from c in Client
where c.ClientId = clientId
from p in c.ClientType.Role.PermittedActivities
select p.Activity.Uid
As you see, you just start at Client and then follow the associations: n - 1 is represented by a dot . and 1 - n by a new from statement.
This is called query syntax, or comprehensive syntax. Most of the times this produces more succinct code than fluent syntax (Client.Where(c => ...), although there are things you can only do with the latter.

Related

Is there any way I can Optimise my linq query to reduce database calls?

So I have an scenario where I have an Opportunity table that has customer reference and customer Id.
And Customer table that has reference to ProjectManager's table.
Now, I have opportunityId and using that id I need to get Project Manager's information.
Below is my working code,
public async Task<Object> GetProjectManagerIdAgainstCustomer(int? opportunityId)
{
var customerId = await _context.Opportunities
.Where(Opportunity => Opportunity.Id == opportunityId)
.Include(Opp => Opp.Customer)
.Select(Opp => Opp.CustomerId)
.FirstOrDefaultAsync();
var ProjectManager = await _context.Customers
.Where(Customer => Customer.Id == customerId)
.Include(Customer => Customer.ProjectManager)
.Select(x => new {
email = x.ProjectManager.ProjectManagerEmail,
fullname = x.ProjectManager.ProjectManagerName,
})
.FirstOrDefaultAsync();
return (ProjectManager);
}
Now, one of my issues is that these two queries make two trips to the database, which takes me to the main aim of the question, how to optimise it so that all of this is done within one trip and if you guys can find any other issues with this code, that would be great?
Additionaly having any related documentation would help alot.
Secondly, is there any way I could have gotten the ProjectManager object within the attached Customer object, for eg. by using include?
It seems like you are looking for a join operation.
Try the one documented here:
https://learn.microsoft.com/en-us/dotnet/csharp/linq/perform-inner-joins
Using this you can create a join query that will be executed on your database and you will only receive the final output.
I hope you have navigation property Opportunities for Customer.
public async Task<Object> GetProjectManagerIdAgainstCustomer(int? opportunityId)
{
var query =
from c in _context.Customers
from o in c.Opportunities
where o.Id == opportunityId
select new
{
email = c.ProjectManager.ProjectManagerEmail,
fullname = c.ProjectManager.ProjectManagerName,
}
return await query.FirstOrDefaultAsync();
}
Method chain syntax
public async Task<Object> GetProjectManagerIdAgainstCustomer(int? opportunityId)
{
var query = _context.Customers
.SelectMany(c => c.Opportunities.Where(o => o.Id == opportunityId, (c, o) => c)
.Select(c => new
{
email = c.ProjectManager.ProjectManagerEmail,
fullname = c.ProjectManager.ProjectManagerName,
});
return await query.FirstOrDefaultAsync();
}

ASP.NET / LINQ / EF: Async on custom distinct comparer class

I'm using .NET Core with EF Core.
I use a custom comparer class like this to achieve a distinct result on my query:
var filterResult = (from c in MyDBContext.Table1
join mt in MyDBContext.Table2
on c.ID equals mt.ID
select new MyModel
{
ID = c.ID,
Description = c.Description
}
)
.ToList()
.Distinct(new MyComparer())
.Take(takeThis);
This is what the comparer class looks like:
public class MyComparer : IEqualityComparer<MyModel>
{
public bool Equals(MyModel x, MyModel y)
{
return x.ID == y.ID;
}
public int GetHashCode(MyModel obj)
{
return obj == null ? 0 : obj.ID;
}
}
This works, but I want to improve it's performance by trying to run distinct() before ToList() to avoid grabbing the entire table (and also implement async).
This is my goal:
var filterResult = await (from c in MyDBContext.Table1
join mt in MyDBContext.Table2
on c.ID equals mt.ID
select new MyModel
{
ID = c.ID,
Description = c.Description
}
)
.Distinct(new MyComparer())
.Take(takeThis)
.ToListAsync();
Using this, I get an NotSupportedException exception. So I tried to do the following:
.Distinct(new MyComparer())
.Take(takeThis)
.ToList();
But that also gives me a NotSupportedException exception.
How do I change my code to avoid having to run ToList() before distinct?
Running Distinct before query materialization (.ToList()) in the context of EF query will result in EF tring to translate the code to SQL Query, but EF is not that powerfull to translate any c# method to SQL query, in the following case the Comparer logic.
As alternative to this query i will suggest you to use the following query:
.GroupBy(x => x.ID)
.Select(g => g.OrderBy(x => x.ID).FirstOrDefault())
.OrderBy(x => x.ID)
.Take(() => ....)
.ToListAsync()
This will create groups for entities with same ID's and then from each group it will select the first one and using order by will keep the result consistent for the query.

LINQ: how to get an intersection of two sets of ints?

There must be a way to compare two sets of results while staying in LINQ. Here's my existing code that uses a HashSet to do the comparison after two separate queries:
public static void AssertDealershipsShareTransactionGatewayCredentialIds(long DealershipLocationId1,
long DealershipLocationId2)
{
using (var sqlDatabase = new SqlDatabaseConnection())
{
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
var doSetsOfCredentialsMatch = new HashSet<int>(DealershipCredentials1).SetEquals(DealershipCredentials2);
Assert.IsTrue(doSetsOfCredentialsMatch,
"The sets of TransactionGatewayCredentialIds belonging to each Dealership did not match");
}
}
Ideas? Thanks.
Easy answer (This will make 1, possibly 2 database calls, both of which only return a boolean):
if (list1.Except(list2).Any() || list2.Except(list1).Any())
{
... They did not match ...
}
Better answer (This will make 1 database call returning a boolean):
var DealershipCredentials1 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId1)
.Select(x => x.TransactionGatewayCredentialId);
var DealershipCredentials2 =
sqlDatabase.Tables.DealershipLocationTransactionGateway
.Where(x => x.DealershipLocationId == DealershipLocationId2)
.Select(x => x.TransactionGatewayCredentialId);
if (DealershipCredentials1.GroupJoin(DealershipCredential2,a=>a,b=>b,(a,b)=>!b.Any())
.Union(
DealershipCredentials2.GroupJoin(DealershipCredential1,a=>a,b=>b,(a,b)=>!b.Any())
).Any(a=>a))
{
... They did not match ...
}
The second method works by unioning a left outer join that returns a boolean indicating if any unmatching records were found with a right outer join that does the same. I haven't tested it, but in theory, it should return a simple boolean from the database.
Another approach, which is essentially the same as the first, but wrapped in a single LINQ, so it will always only make 1 database call:
if (list1.Except(list2).Union(list2.Except(list1)).Any())
{
}
And another approach:
var common=list1.Intersect(list2);
if (list1.Except(common).Union(list2.Except(common)).Any()) {}

Entity Navigation Property IQueryable cannot be translated into a store expression

im using Entity Framework designer first and I need to create custom Model Objects starting from the db objects.
I don't want to use IEnumerable cause it will query too many fields.
The goal is to remove the inner select within this function:
using (var db = new dbEntities())
{
var departments= db.departments
.Include(p => p.employee)
.Where(...)
.Select(p => new CustomDepartmentModel()
{
ID = p.ID,
Employees = p.employee
.Select(q => new CustomEmployeeModel()
{
ID = q.ID,
Name= q.Name
}).ToList()
});
return departments.ToList();
}
by using this function:
public static IQueryable<CustomEmployeeModel> ToModel(this IQueryable<employee> Employee)
{
return Employee.Select(u => new CustomEmployeeModel()
{
ID = u.ID,
Name = u.Name
});
}
But I always get the error: "LINQ to Entities does not recognize the method ToModel".
I did try to use it in these ways without luck:
Employees = p.employee.AsQueryable().ToModel().ToList() //1
Employees = db.Entry(p).Collection(f => f.employee).Query().ToModel().ToList() //2
I think that I need to use something like this:
public static System.Linq.Expressions.Expression<Func<IQueryable<employee>, IQueryable<CustomEmployeeModel>>> ToModel()
{
return p => p.Select(u => new CustomEmployeeModel()
{
ID = u.ID,
Name = u.Name
});
}
but I really can't figure out how to use it.
I think that I need to use something like this: [snip] but I really can't figure out how to use it.
That's exactly what you need, but you also need LINQKit to make the query work. With it, your code would look like this:
var toModel = ToModel();
var departments2 = db.departments
.AsExpandable()
.Include(p => p.employee)
.Where(p => true)
.Select(p => new CustomDepartmentModel()
{
ID = p.ID,
Employees = toModel.Invoke(p.employee).ToList()
});
The problem is that LINQ to Entities is trying to translate your ToModel() method into a SQL query (since that's what LINQ to Entities is supposed to do), and it can't find a way to do it, hence the error that you're seeing.
In order for you to call ToModel() you'll need to have the information already come in from the database, which will then make any LINQ query a LINQ to Objects query, which will be more than able to do what you are asking for. You can do this by calling ToList() before calling ToModel().

Can I use an extension method in Entity Framework SubQuery?

I'm trying to consolidate logic for accessing different tables using Entity Framework. I created an extension method for pulling all registrations from my registration entity where the person is attending:
public static IEnumerable<Registration> Attending(this IEnumerable<Registration> registrations)
{
return registrations.Where(r => r.Status == RegistrationStatus.Paid || r.Status == RegistrationStatus.Assigned || r.Status == RegistrationStatus.Completed);
}
This works great for queries like this:
var attendees = db.Registrations.Attending().ToList();
But it doesn't work when used in a subquery:
ProductTotals = db.Products.Where(p => p.EventID == ev.Id).Select(p => new ProductSummaryViewModel
{
ProductID = p.ProductID,
ProductName = p.Name,
Registrations = p.Registrations.Attending().Count(),
}).ToList();
I get the following error:
LINQ to Entities does not recognize the method
'System.Collections.Generic.IEnumerable1[Registration]
Attending(System.Collections.Generic.IEnumerable1[Registration])'
method, and this method cannot be translated into a store expression.
Is there any way re-use that code in a subquery?
The main thing you're trying to achieve is reusing the predicate that defines the meaning of Attending. You can do that by storing the expression in a readonly variable that is available to whoever needs it in your application, for example in a static class ExpressionConstants.
public static readonly Expression<Func<Registration, bool>> IsAttending =
r => r.Status == RegistrationStatus.Paid
|| r.Status == RegistrationStatus.Assigned
|| r.Status == RegistrationStatus.Completed;
Then you can do
var attendees = db.Registrations.Where(ExpressionConstants.IsAttending).ToList();
And used in the subquery:
ProductTotals = db.Products.Where(p => p.EventID == ev.Id).Select(p => new ProductSummaryViewModel
{
ProductID = p.ProductID,
ProductName = p.Name,
Registrations = p.Registrations.AsQueryable() // AsQueryable()
.Where(ExpressionConstants.IsAttending).Count(),
})
The AsQueryable() is necessary because p.Registrations probably is an ICollection.

Categories

Resources