FirstOrDefault returns null on foreign key - c#

Lets say we have the following model :
public class ReadingOrder
{
public virtual int Id { get; set; }
public virtual Order Order { get; set; }
}
Mapping:
Table("db_ReadingOrder");
Id(o => o.Id).Column("Id").GeneratedBy.Identity();
References(o => o.Order, "OrderId");
I want to get the ReadingOrder which has the orderId equal with 1 (eg).
But when I try a FirstOrDefault, the query returns null :
var readingO = _repositoryFactory.GetRepository<ReadingOrder>().FirstOrDefault(xz => xz.Order.Id == 1);
If I get all of them and after apply a FirstOrDefault works, but its stupid:
var readingOrderList1 = _repositoryFactory.GetRepository<ReadingOrder>()
.GetAll().FirstOrDefault(xz => xz.Order.Id == 1);
The method from repository has the following format:
public T FirstOrDefault(Expression<Func<T, bool>> predicate)
{
return _session.Query<T>().FirstOrDefault(predicate);
}
easy stuff, but not working. If I go for a normal property, like Id, all works as expected.
Also, if I get the generated query from log and put it in sqlite, it runs successfully and the reading order is returned. Is there a bug in NHibernate? Is a mapping problem? Or is it a problem with SQLite?

Ok, finally I found the problem : the name of the foreign key column "OrderId".
Nhibernate looks for "Hibernate.Order" in this case, I don`d know why, but after I changed the name of the column, the item is now retrieved from db. Thank you all for your answers! I gave the bounty to user Syed Farjad Zia Zaidi, because he helped me to isolate the problem. It was clear that was an Nhibernate issue, so thank you again.

There are two ways I see you can make this work either change your mapping as:
Table("db_ReadingOrder");
Id(o => o.Id).Column("Id").GeneratedBy.Identity();
References(o => o.Order);
and then query:
var readingO = _repositoryFactory.GetRepository<ReadingOrder>().FirstOrDefault(xz => xz.Order.Id == 1);
Otherwise you can also change your mapping as:
Table("db_ReadingOrder");
Id(o => o.Id).Column("Id").GeneratedBy.Identity();
References(o => o.Order).Column("OrderId");
and then query:
var readingO = _repositoryFactory.GetRepository<ReadingOrder>().FirstOrDefault(xz => xz.Order.OrderId == 1);

some times that's related to dynamic global filters, like a soft delete method by a IsDeleted prop so u need to fetching ignored items and using some methods like this .IgnoreQueryFilters() in ef.

The same scenario works fine with the following code at the repository.
public virtual IEnumerable<T> Get(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = _context.Set<T>();
if (filter != null)
{
query = query.Where(filter);
}
if (includeProperties != null)
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
return query.ToList();
}
}

Modify your class like this:
public class ReadingOrder
{
public virtual int Id { get; set; }
public virtual Order Order { get; set; }
public virtual int OrderId { get; set; } // Explicit and direct foreign Key
}
and then retrieve your query using OrderId, instead of Order.Id:
var readingO = _repositoryFactory.GetRepository<ReadingOrder>()
.FirstOrDefault(xz => xz.OrderId == 1);

Related

EF Include(Expression)

I am trying to build generic method which would look simething like this:
public static IQueryable<TModel> IncludeByUserCondition<TModel, TIncludable>(this IQueryable<TModel> query, Func<TModel, IQueryable<TIncludable>> includes, List<int> userIDs)
where TModel : class
where TIncludable: class
{
Expression<Func<TModel, object>> result = x => includes(x);
if(typeof(ASqlBase).IsAssignableFrom(typeof(TIncludable)))
{
result = x =>
includes(x)
.Select(prop => prop as ASqlBase)
.Where(prop =>
prop.DeleteDate == null
)
.Where(prop =>
userIDs != null && userIDs.Count > 0 ? userIDs.Contains(prop.IdentityUnitID) : true
)
.Select(prop => prop as TIncludable);
}
query = query.Include(result);
return query;
}
This method would allow me to centrally check if user can read navigation property's value and, if so, include it in result. My applications read rights are conceived in hierarchical way: logged user can read his records and records of all users he had added to the system. Because of that, I cannot determine all visible records in compile-time and, thus, cannot use different database contexts for different groups of users. Also, since this is only one of many ways for filtering data, unfortunately I cannot make use of Global Filters.
I am trying to call the above method like this:
qry = qry.IncludeByUserCondition<AllocatedFund, AllocatedFundDetailPaymentMade>(p => p.AllocatedFundDetailPaymentsMade.AsQueryable(), allowedUserIDs);
However, when I try to invoke it in run-time, I get the following exception:
The expression 'Invoke(__includes_0, x).Select(prop => (prop As ASqlBase)).Where(prop => ((prop.DeleteDate == null))).Where(prop => True).Select(prop => (prop As AllocatedFundDetailPaymentMade))' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'. To target navigations declared on derived types, use casting ('t => ((Derived)t).MyProperty') or the 'as' operator ('t => (t as Derived).MyProperty'). Collection navigation access can be filtered by composing Where, OrderBy(Descending), ThenBy(Descending), Skip or Take operations.
On another hand, when I try to run query manually, everything works fine:
qry = qry.Include(p =>
p.AllocatedFundDetailPaymentsMade
.AsQueryable()
.Where(prop =>
prop.DeleteDate == null
)
.Where(prop =>
userIDs != null && userIDs.Count > 0 ? userIDs.Contains(prop.IdentityUnitID) : true
)
Since I have more than just one navigation property to include in this manner (and I have to perform query in similar manner for all 30+ other models I use in my application), I wouldn't want to manually write those where clauses in every query.
Does anybody know the solution for this problem? Any help would be kindly appreciated.
EDIT:
ASqlBase is just base, abstract class from which some other models interit (although not all of them - ie. User model does not inherit from ASqlBase).
ASqlBase looks like this:
public abstract class ASqlBase
{
[Key]
public int ID { get; set; }
[Required]
public int UserID { get; set; }
[ForeignKey("UserID")]
public virtual User User { get; set; }
public DateTime? DeleteDate { get; set; }
}
I plan to use that function to get data and then display it in report. I'm giving example for Person, then the method call would look something like this:
var qry = dbContext.Person.IncludeByUserCondition<Person, Athlete>(p => p.Athletes.AsQueryable(), athleteAllowedUserIDs);
qry = qry.IncludeByUserCondition<Person, Employee>(p => p.Employees.AsQueryable(), employeeAllowedUserIDs);
qry = qry.IncludeByUserCondition<Person, Student>(p => p.Students.AsQueryable(), studentAllowedUserIDs);
Person model looks something like this:
public class Person : ASqlBase
{
...
public virtual ICollection<Athlete> Athletes { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
public virtual ICollection<Student> Students { get; set; }
}
All of the above models: Athlete, Employee and Student inherit from ASqlBase
EDIT 2:
Sorry for bad method naming, method should be called IncludeByUserCondition, not IncludeMultiple (as it was named before).
A little bit simplified usage. You do not need to call AsQueryable() and explicitly specify generic parameters:
var qry = dbContext.Person.IncludeByUserCondition(p => p.Athletes, athleteAllowedUserIDs);
qry = qry.IncludeByUserCondition(p => p.Employees, employeeAllowedUserIDs);
qry = qry.IncludeByUserCondition(p => p.Students, studentAllowedUserIDs);
And realization:
public static class IncludeExtensions
{
public static IQueryable<TModel> IncludeByUserCondition<TModel, TRelated>(this IQueryable<TModel> query,
Expression<Func<TModel, IEnumerable<TRelated>>> collectionProp, List<int> userIDs)
where TModel : class
where TRelated : ASqlBase
{
var relatedParam = Expression.Parameter(typeof(TRelated), "r");
// r.DeleteDate == null
var filterPredicate = (Expression)Expression.Equal(
Expression.PropertyOrField(relatedParam, nameof(ASqlBase.DeleteDate)),
Expression.Constant(null, typeof(DateTime?)));
if (userIDs?.Count > 0)
{
// r.DeleteDate == null && userIDs.Contains(r.UserID)
filterPredicate = Expression.AndAlso(filterPredicate,
Expression.Call(typeof(Enumerable), nameof(Enumerable.Contains), new[] { typeof(int) },
Expression.Constant(userIDs),
Expression.PropertyOrField(relatedParam, nameof(ASqlBase.UserID))));
}
// r => r.DeleteDate == null && userIDs.Contains(r.UserID)
var filterLambda = Expression.Lambda(filterPredicate, relatedParam);
// p => p.Navigation.Where(r => r.DeleteDate == null && userIDs.Contains(r.UserID))
var transformedProp = Expression.Lambda(Expression.Call(typeof(Enumerable), nameof(Enumerable.Where),
new[] { typeof(TRelated) }, collectionProp.Body, filterLambda), collectionProp.Parameters);
// query.Include(p => p.Navigation.Where(r => r.DeleteDate == null && userIDs.Contains(r.UserID)))
var includeExpression = Expression.Call(typeof(EntityFrameworkQueryableExtensions),
nameof(EntityFrameworkQueryableExtensions.Include),
new[] { typeof(TModel), typeof(IEnumerable<TRelated>) },
query.Expression,
Expression.Quote(transformedProp));
// instantiate new IQueryable<TModel>
var resultQuery = query.Provider.CreateQuery<TModel>(includeExpression);
return resultQuery;
}
}

Many to Many using LinqKit with EF Core with Generic class

I'm currently converting an EF project to EF Core but I don't know how or whether it is possible to:
a) Use LinqKit on Many-To-Many with EF Core
b) Use a) with a Generic class
I'm currently using the following class:
protected virtual IQueryable<TEntity> GetQueryable(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = null,
bool isCollection = false,
int? skip = null,
int? take = null)
{
IQueryable<TEntity> query = this.Context.Set<TEntity>();
if (filter != null)
{
query = query.AsExpandable().Where(filter);
}
if (includeProperties != null)
{
foreach (var includeProperty in includeProperties.Split
(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
}
if (orderBy != null)
{
query = orderBy(query);
}
if (skip.HasValue)
{
query = query.Skip(skip.Value);
}
if (take.HasValue)
{
query = query.Take(take.Value);
}
return query;
}
In EF 6, I would set the type of my generic class to a main Entity I wanted to deal with and if it had a many to many, I would simply set the IncludeProperties to the child Entity.
For example:
var test = GetQueryable<Company>(null, "Users");
This would returns all companies and return all the users associated with each relevant company.
LinqKit would then come in to add additional filters, orders, etc...
But I cannot figure out how to use this with a Many to Many relation in EF Core.
I've seen an example where you do something similar to this:
this.Context.Companies.Include("CompanyUsers")
.ThenInclude("Users")
.ToListAsynch()
And the above looks like what I need but I need to use it to be generic and I need to apply filters with LinqKit or some other way but I need to set filters, sorting, paging, etc... dynamically.
Any ideas?
Thanks
UPDATE-1:
My many-to-many relation class is defined as follows:
public class CompanyUsers
{
public Guid UserId { get; set; }
public User User { get; set; }
public Guid CompanyId { get; set; }
public Company Company { get; set; }
}
The reason I'm adding this is that while my table of users is called "Users" the navigation property is called User and as suggested by Dennis, I should specify the Many-To-Many class (CompanyUsers) followed by the navigation property (User) so rather than being:
var test = GetQueryable<Company>(null, "CompanyUsers.Users");
it should be defined as instead:
var test = GetQueryable<Company>(null, "CompanyUsers.User");
Note the s was removed to match the navigation property rather than the table.
The simplest solution would be to use the string overload of the Include.*
var test = GetQueryable<Company>(null, "CompanyUsers.User");
*I don't recommend using magic strings. You should consider writing an method that returns this based on the models properties.

Why does my EF appear to return duplicate rows from my SQL View which is working?

I've looked the question up but nothing I have found is working for me. I created a view in SQL which works when you run it in the Management Studio. When the view is accessed from my MVC Application, EF is returning identical rows instead of rows with different data.
Table: Cars
[Id]
[Registration]
[Make]
[Model]
Table: Bookings
[Id]
[BookingStartDate]
[BookingEndDate]
[CarId]
View: CarBookings
SELECT [C].[Id],
[C].[Registration],
[C].[Make],
[C].[Model],
[B].[BookingStartDate],
[B].[BookingEndDate]
FROM [Cars] AS C INNER JOIN [Bookings] AS B ON C.Id = B.CarId
If I run the query in SSMS I get all the expected results, for example:
Car 1, Booked 12/03/2018
Car 1, Booked 19/09/2018
When I access the same view from my MVC Application I get:
Car 1, Booked 12/03/2018
Car 1, Booked 12/03/2018
Putting a breakpoint onto the controller shows that the results are the same so it's not the presentation layer that's causing it. No filters are applied and there are no conditions at all.
I'm using KendoUI and returning my results to a Grid.
Here is my controller code for getting the data:
HomeController.cs
public ActionResult GetBookings([DataSourceRequest] DataSourceRequest request)
{
var bookings = unitOfWork.BookingsRepository.Get();
var result = bookings.ToDataSourceResult(request);
return Json(result, JsonRequestBehavior.AllowGet);
}
My application uses a generic repository. I'm not sure if it's causing the problem but it's worth mentioning. Here is the GET method from my repository.
DAL/GenericRepository.cs
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
DAL/Context.cs
public DbSet<Bookings> Bookings { get; set; }
DAL/UnitOfWork.cs
private GenericRepository<Bookings> bookingsRepository;
public GenericRepository<Bookings> bookingsRepository
{
get
{
if (this.bookingsRepository == null)
{
this.bookingsRepository = new GenericRepository<Bookings>(context);
}
return bookingsRepository;
}
}
Entity Class
This is the class that represents the view and accesses it using the [Table] annotation.
namespace MyProject.Models
{
[Table("CarBookings")]
public class Bookings
{
//Car
[Key]
public int Id { get; set; }
public string Registration { get; set; }
public string Make { get; set; }
public string Model { get; set; }
//Booking
public DateTime BookingStartDate { get; set; }
public DateTime BookingEndDateYearOfBuild { get; set; }
}
}
When I searched for answers to this, I read that the view doesn't have an ID so EF tries to logically order records by unique values and this can sometimes cause problems (source: https://www.itworld.com/article/2833108/development/linq-in--net-returning-duplicate-rows-from-a-working-sql-view--solved-.html).
I adjusted my views select code as per the above article but it didn't work for me; I still saw duplicates:
SELECT ROW_NUMBER() OVER (ORDER BY Car.Id) AS NID,
Car.Id,
Booking.BookingStartDate
... etc...
FROM Cars AS Car INNER JOIN
Booking AS Booking ON Car.Id = Booking.Car_Id
I did some more digging and aside from the above-mentioned [Key] for views, other threads I found pointed at .AsNoTracking() as a potential solution. I investigated this a little more and attempted to implement this on my solution.
Here is one of those comments relating to my problem:
AsNoTracking() allows the "unique key per record" requirement in EF to be bypassed (not mentioned explicitly by other answers).
This is extremely helpful when reading a View that does not support a unique key because perhaps some fields are nullable or the nature of the view is not logically indexable.
For these cases the "key" can be set to any non-nullable column but then AsNoTracking() must be used with every query else records (duplicate by key) will be skipped
Source: What difference does .AsNoTracking() make?
Within my GenericRepository.cs I set this value on the Get method and the results on my grid are now accurate without any duplication going on.
Here is the code I changed:
GenericRepository.cs
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> query = dbSet.AsNoTracking();
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
This change as solved my problem. Hopefully, there will no unwanted effects from this later down the line :) thanks to everyone who took the time to reply.
As you've mentioned before views can get messed if there's no primary key.
Typically you need to add a [Key] attribute to at least one of the properties on your view - the original recommendation back in EF 1 with the edmx designer was to just set ALL the properties on the view to be primary keys but this may be overkill. But if adding it to one property doesn't work try adding it to all of them or a subset of them so that each entity has a unique key combo e.g.
public partial class CarsBookings
{
[Key]
public int Id { get; set; }
[Key]
public string Registration { get; set; }
}

Access children(list) related property in Expression tree

I created a repository for my entity Master. In the repository, I have a Get method to get my entity by Id using Entity Core.
The method receives:
public TEntity Get(object id, params Expression<Func<TEntity, object>>[] includedRelatedEntities)
{
return GetById(IncludeEntities(DBContext.Set<TEntity>().AsQueryable(), includedRelatedEntities), id);
}
Then, when I use it in my code, I just pass to the method the id of the entity I´m looking for and and expression tree of the related entities that I need to include in the query (Expression<Func<TEntity, object>>)
An example of use is the following one:
var master = MasterRepository.Get(1, x => x.BranchOffice.Location);
In that case I´m looking for the Master with Id = 1 and I want it to include the BranchOffice related entity and the Location related to that BranchOffice.
From one to many relationships, it works fine, but for related lists, I dont know how to resolve it using an expression.
For example, if I want to include the Product entity of the list of Detail named Details related to my Master, I dont know how to express it in the expression tree.
var master = MasterRepository.Get(1, x => x.Details.Product);
Details is a list, so I cant access product as it is in the example above.
How can I express that in a Expression<Func<TEntity, object>>?
EDIT:
I´ve already tried:
var master = MasterRepository.Get(1, x => x.Details.Select(y=> y.Product));
But I´m getting the following exception:
The property expression 'x => {from Detail y in [x].Details select
[y].Product}' is not valid. The expression should represent a property
access: 't => t.MyProperty'. For more information on including related
data, see go.microsoft.com/fwlink/?LinkID=746393.'
I don't know can you change or replace IncludeEntities implementations, so maybe answer would not be helpful for you. Well, x => x.Details.Product will looks like this DbContext.Set<SomeType>().Include(x => x.Details).ThenInclude(o => o.Product) in the EF.Core.
So if you want to include multiple levels I can suggest you to build a query at runtime that will contains Include and ThenInclude. So, this query will be built from input expression looks like this x => x.Details.Select(y => y.Product). It's method that build this query:
/// <summary>
/// Takes include looks like 'x => x.Collections.Select(o => o.List.Select(p => p.Date))'
/// </summary>
public static IQueryable<T> GetQueryWithIncludes<T>(IQueryable<T> query, Expression<Func<T, object>> arg)
{
// Tiny optimization
ParameterInfo[] parameters;
var includeInfo = typeof(EntityFrameworkQueryableExtensions).GetMethods().Where(info => info.Name == "Include" &&
(parameters = info.GetParameters()).Length == 2 &&
typeof(Expression).IsAssignableFrom(parameters[1].ParameterType)).Single();
// Retrieve then include that take first param as 'IIncludableQueryable<TEntity, ICollection<TPreviousProperty>>'
var thenIncludeInfo = typeof(EntityFrameworkQueryableExtensions).GetMethods().Where(info => info.Name == "ThenInclude").ToList()[1];
// Retrieve then include that take first param as 'IIncludableQueryable<TEntity, IEnumerable<TPreviousProperty>>'
var lastThenIncludeInfo = typeof(EntityFrameworkQueryableExtensions).GetMethods().Where(info => info.Name == "ThenInclude").ToList()[0];
// Retrieve all selection from input expression
var lambda = arg as LambdaExpression;
var method = arg.Body as MethodCallExpression;
var result = new List<Expression>();
while (method != null)
{
result.Add(Expression.Lambda(method.Arguments[0], lambda.Parameters[0]));
lambda = method.Arguments[1] as LambdaExpression;
method = lambda.Body as MethodCallExpression;
}
result.Add(lambda);
// Add Include and ThenInclude to IQueryable
for (int i = 0; i < result.Count; ++i)
{
var lambdaExp = result[i] as LambdaExpression;
query = i == 0
? includeInfo.MakeGenericMethod(lambdaExp.Parameters[0].Type, lambdaExp.ReturnType).Invoke(null, new object[] { query, lambdaExp }) as IQueryable<T>
: i == result.Count - 1
? lastThenIncludeInfo.MakeGenericMethod((result[0] as LambdaExpression).Parameters[0].Type, lambdaExp.Parameters[0].Type, lambdaExp.ReturnType).Invoke(null, new object[] { query, lambdaExp }) as IQueryable<T>
: thenIncludeInfo.MakeGenericMethod((result[0] as LambdaExpression).Parameters[0].Type, lambdaExp.Parameters[0].Type, lambdaExp.ReturnType).Invoke(null, new object[] { query, lambdaExp }) as IQueryable<T>;
}
return query;
}
By the way, method takes a one expression, but it can be lightly modified, so it will takes array of expression or you can directly invoke the method from a loop for all of expressions.
Code below is just usage. I wrote a tree small classes for testing:
public class Test
{
public int Id { get; set; }
public DateTime TestDate { get; set; }
public ICollection<Level> Levels { get; set; }
}
public class Level
{
public int Id { get; set; }
public ICollection<LevelDetail> LevelDetails { get; set; }
}
public class LevelDetail
{
public int Id { get; set; }
public DateTime LevelDate { get; set; }
}
...
// These results are the same and have the same expression trees
var resultByInclude = context.Tests
.Include(o => o.Levels)
.ThenInclude(p => p.LevelDetails).ToList();
var resultBySelect = GetQueryWithIncludes(context.Tests,
o => o.Levels.Select(p => p.LevelDetails)).ToList();
I hope it will helps you.

How to get around "Internal .NET Framework Data Provider error 1025."?

I am using the Entity Framework 4.3, POCO, database first and I am getting the following error:
Internal .NET Framework Data Provider error 1025.
QUESTION: I think that my query expresses my intent but I seem to be hitting this error, so I am wondering if anyone knows how I could structure my query differently to get around this error?
Here is the scenario...
I have a SQL server 2008 database that has 2 tables - A and B:
A
AId (int - not null - identity - primary key)
AName (nvarchar(10) - not null)
B
BId (int - not null - identity - primary key)
SomeName (nvarchar(10) - not null)
AId (int - not null - foreign key connecting to AId in the table A)
I then define the context like so:
public class DatabaseContext : DbContext
{
public DatabaseContext(string name)
: base(name)
{
Configuration.AutoDetectChangesEnabled = false;
As = Set<A>();
Bs = Set<B>();
}
public DbSet<A> As { get; private set; }
public DbSet<B> Bs { get; private set; }
}
And the entity classes like so:
public class A
{
public int AId { get; set; }
public string AName { get; set; }
public virtual ICollection<B> Bs { get; private set; }
public void AddB(B b)
{
if (b == null)
{
throw new ArgumentNullException("b");
}
if (Bs == null)
{
Bs = new List<B>();
}
if (!Bs.Contains(b))
{
Bs.Add(b);
}
b.A = this;
}
}
public class B
{
public int BId { get; set; }
public A A { get; set; }
public string SomeName { get; set; }
}
Now for the query...
What I want is all of the As where every "B SomeName" is in the list of names supplied so I do this:
var names = new[] {"Name1", "Name2"};
var ctx = new DatabaseContext("EFPlayingEntities");
var res = ctx.As.Where(a => a.Bs.Select(b => b.SomeName).All(names.Contains));
// Here I evaluate the query and I get:
// Internal .NET Framework Data Provider error 1025.
Console.WriteLine(res.Count());
To be clear about what I mean, if the table data looks like this:
AId,AName
1,A1
2,A2
3,A3
4,A4
BId,SomeName,AId
1,Name1,1
2,Name2,1
3,Name1,2
4,Name1,3
5,Name3,3
6,Name1,4
7,Name2,4
I would expect to get back A1, A2 and A4 (so that count call above would return 3).
The reason why this happens is subtle.
Queryable.All need to be called with an Expression. Passing in just the method 'reference' creates a delegate, and subsequently, Enumerable.All becomes the candidate instead of the intended Queryable.All.
This is why your solution you posted as an answer works correctly.
EDIT
so if you write the statement as this, it will work without exception:
var res = ctx.As.Where(
a => a.Bs.Select(b => b.SomeName).All(b => names.Contains(b)));
I have worked out a solution to this, in case anyone is interested. Doing the following is equivalent and does not result in the exception in the question:
var res = ctx
.Bs
.GroupBy(b => b.A)
.Where(g => g.All(b => names.Contains(b.SomeName)))
.Select(g => g.Key);
I do not know if this is the best way though!?
The semantics of your query look good to me; clearly, getting an internal provider error is not the intended behaviour! I would have expected some more explicit mesage about EF not being able to translate your query into a store operation, if that's in fact what the problem is.
Another way to do what you want would be:
var names = new[] {"Name1", "Name2"};
var nameCount = names.Length;
var ctx = new DatabaseContext("EFPlayingEntities");
var result = ctx.As
.Where(a => a.Bs
.Select(b => b.SomeName)
.Intersect(names)
.Count() == a.Bs.Count());
(get every A such that intersecting its Bs' names with the fixed list gives all the Bs)
although I haven't tried this to see if EF can translate this successfully.
Another way:
var names = new[] {"Name1", "Name2"};
var ctx = new DatabaseContext("EFPlayingEntities");
var result = ctx.As
.Where(a => !a.Bs.Select(b => b.SomeName).Except(names).Any());
(get every A such that the list of its Bs' names is reduced to nothing by taking out the fixed list)
also untried.

Categories