Fetching related object using LINQ and EF - c#

I have two objects that are linked by a foreign key relationship and I use DataModel to map the objects:
Event:1------*:Asset
I wrote a query that fetches all assets for a given [eventPublicId]
List<Asset> assetList =
ReliableExecution.RetryWithExpression<Event, List<Asset>>
(u => u.FirstOrDefault(x => x.PublicId == eventPublicId).Assets.ToList()).ToList();
My problem is that I had to call ToList() twice and this looks awkward. Also I had to use FirstOrDefault, but when I tried to use [Where] or anything else, it didn't compile.
Is there any other better way how this code can be rewritten?
This is RetryWithExpression signature for reference:
public static TValue RetryWithExpression<T, TValue>(Func<ObjectSet<T>, TValue> func, Int32 retryInfiniteLoopGuard = 0)
where T : class

You specify that the func parameter should return a List<Asset>, so the navigation property event.Assets does not fit the bill: It is an EntityCollection<Asset>, which is not implicitly convertible to the delegate return type. The explicit conversion ToList() creates the specified type.
Technically, to get rid of the ToList, you should use
ReliableExecution.RetryWithExpression<Event, EntityCollection<Asset>> ...
but I don't know if that meets your functional requirements.

Related

How to get an overloaded == operator to work with LINQ and EF Core?

so basically, I have a project which uses EF Core. In order to shorten my lambdas when comparing if two objects (class Protocol) are equal, I've overridden my Equals method and overloaded the == and != operators. However, LINQ doesn't seem to care about it, and still uses reference for determining equality. Thanks
As I've said before, I've overridden the Equals method and overloaded the == and != operators. With no luck. I've also tried implementing the IEquatable interface. Also no luck.
I am using:
EF Core 2.2.4
//the protocol class
[Key]
public int ProtocolId {get;set;}
public string Repnr {get;set;}
public string Service {get;set;}
public override bool Equals(object obj)
{
if (obj is Protocol other)
{
return this.Equals(other);
}
return false;
}
public override int GetHashCode()
{
return $"{Repnr}-{Service}".GetHashCode();
}
public bool Equals(Protocol other)
{
return this?.Repnr == other?.Repnr && this?.Service == other?.Service;
}
public static bool operator ==(Protocol lhs, Protocol rhs)
{
return lhs.Equals(rhs);
}
public static bool operator !=(Protocol lhs, Protocol rhs)
{
return !lhs.Equals(rhs);
}
//the problem
using (var db = new DbContext())
{
var item1 = new Protocol() { Repnr = "1666", Service = "180" };
db.Protocols.Add(item1 );
db.SaveChanges();
var item2 = new Protocol() { Repnr = "1666", Service = "180" };
var result1 = db.Protocols.FirstOrDefault(a => a == item2);
var result2 = db.Protocols.FirstOrDefault(a => a.Equals(item2));
//both result1 and result2 are null
}
I would expect both result1 and result2 to be item1. However, they're both null. I know I could just do a.Repnr == b.Repnr && a.Service == b.Service, but that just isn't as clean. Thanks
To understand why the incorrect equality comparer is used, you have to be aware about the difference between IEnumerable<...> and IQueryable<...>.
IEnumerable
An object that implements IEnumerable<...>, is an object that represents a sequence of similar objects. It holds everything to fetch the first item of the sequence, and once you've got an item of the sequence you can get the next item, as long as there is a next item.
You start enumerating either explicitly by calling GetEnumerator() and repeatedly call MoveNext(). More common is to start enumerating implicitly by using foreach, or LINQ terminating statements like ToList(), ToDictionary(), FirstOrDefault(), Count() or Any(). This group of LINQ methods internally uses either foreach, or GetEnumerator() and MoveNext() / Current.
IQueryable
An object that implements IQueryable<...> also represents an enumerable sequence. The difference however, is that this sequence usually is not held by your process, but by a different process, like a database management system.
The IQueryable does not (necessarily) hold everything to enumerate. Instead it holds an Expression and a Provider. The Expression is a generic description about what must be queried. The Provider knows which process will execute the query (usually a database management system) and how to communicate with this process (usually something SQL-like).
An IQueryable<..> also implements IEnumerable<..>, so you can start enumerating the sequence as if it was a standard IEnumerable. Once you start enumerating an IQueryable<...> by calling (internally) GetEnumerator(), the Expression is sent to the Provider, who translates the Expression into SQL and executes the query. The result is presented as an enumerator, which can be enumerated using MoveNext() / Current.
This means, that if you want to enumerate an IQueryable<...>, the Expression must be translated into a language that the Provider supports. As the compiler does not really know who will execute the query, the compiler can't complain if your Expression holds methods or classes that your Provider doesn't know how to translate to SQL. In such cases you'll get a run-time error.
It is easy to see, that SQL does not know your own defined Equals method. In fact, there are even several standard LINQ functions that are not supported. See Supported and Unsupported LINQ Methods (LINQ to Entities).
So what should I do if I want to use an unsupported function?
One of the things that you could do is move the data to your local process, and then call the unsupported function.
This can be done using ToList, but if you will only use one or a few of the fetched items, this would be a waste of processing power.
One of the slower parts of a database query is the transport of the selected data to your local process. Hence it is wise to limit the data to the data that you actually plan to use.
A smarter solution would be to use AsEnumerable. This will fetch the selected data "per page". It will fetch the first page, and once you've enumerated through the fetched page (using MoveNext), it will fetch the next page.
So if you only use a few of the fetched items, you will have fetched some items that are not used, but at least you won't have fetched all of them.
Example
Suppose you have a local function that takes a Student as input and returns a Boolean
bool HasSpecialAbility(Student student);
Requirement: give me three Students that live in New York City that have the special Ability.
Alas, HasSpecialAbility is a local function, it can't be translated into Sql. You'll have to get the Students to your local process before calling it.
var result = dbContext.Students
// limit the transported data as much as you can:
.Where(student => student.CityCode == "NYC")
// transport to local process per page:
.AsEnumerable()
// now you can call HasSpecialAbility:
.Where(student => HasSpecialAbility(student))
.Take(3)
.ToList();
Ok, you might have fetched a page of 100 Students while you only needed 3, but at least you haven't fetched all 25000 students.

How to pass a predicate as parameter c#

How can I pass a predicate into a method but also have it work if no predicate is passed? I thought maybe something like this, but it doesn't seem to be correct.
private bool NoFilter() { return true; }
private List<thing> GetItems(Predicate<thing> filter = new Predicate<thing>(NoFilter))
{
return rawList.Where(filter).ToList();
}
private List<thing> GetItems(Func<thing, bool> filter = null)
{
return rawList.Where(filter ?? (s => true)).ToList();
}
In this expression s => true is the fallback filter which is evaluated if the argument filter is null. It just takes each entry of the list (as s) and returns true.
There are two parts to this.
First, you need to adjust the NoFilter() function to be compatible with Predicate<T>. Notice the latter is generic, but the former is not. Make NoFilter() look like this:
private bool NoFilter<T>(T item) { return true; }
I know you never use the generic type argument, but it's necessary to make this compatible with your predicate signature.
For fun, you could also define NoFilter this way:
private Predicate<T> NoFilter = x => true;
Now the second part: we can look at using the new generic method as the default argument for GetItems(). The trick here is you can only use constants. While NoFilter() will never change, from the compiler's view that's not quite the same things a a formal constant. In fact, there is only one possible constant you can use for this: null. That means your method signature must look like this:
private List<thing> GetItems(Predicate<thing> filter = null)
Then you can check for null at the beginning of your function and replace it with NoFilter:
private List<thing> GetItems(Predicate<thing> filter = null)
{
if (filter == null) filter = NoFilter;
return rawList.Where(filter).ToList();
}
And if you also do want to explicitly pass this to the method when calling it, that would look like this:
var result = GetItems(NoFilter);
That should fully answer the original question, but I don't want to stop here. Let's look deeper.
Since you need the if condition anyway now, at this point I would tend to remove the NoFilter<T>() method entirely, and just do this:
private IEnumerable<thing> GetItems(Predicate<thing> filter = null)
{
if (filter == null) return rawList;
return rawList.Where(filter);
}
Note that I also changed the return type and removed the ToList() call at the end. If you find yourself calling ToList() at the end of a function just to match a List<T> return type, it's almost always much better to change the method signature to return IEnumerable<T> instead. If you really need a list (and usually, you don't), you can always call ToList() after calling the function.
This change makes your method more useful, by giving you a more abstract type that will be more compatible with other interfaces, and it potentially sets you up for a significant performance bump, both in terms of lowered memory use and in terms of lazy evaluation.
One final addition here is, if you do pare down to just IEnumerable, we can see now this method does not really provide much value at all beyond the base rawItems field. You might look at converting to a property, like this:
public IEnumerable<T> Items {get {return rawList;}}
This still allows the consumer of your type use a predicate (or not) if they want via the existing .Where() method, while also continuing to hide the underlying raw data (you can't directly just call .Add() etc on this).

How to combine Find() and AsNoTracking()?

How to combine Find() with AsNoTracking() when making queries to an EF context to prevent the returned object from being tracked. This is what I can't do
_context.Set<Entity>().AsNoTracking().Find(id);
How can I do that? I am using EF version 6.
Note: I do not want to use SingleOrDefault(), or Where. I just can't because the parameter Id is generic and it's a struct and I can not apply operator == for generics in that case.
So instead of using AsNoTracking() what you can do is Find() and then detach it from the context. I believe that this gives you the same result as AsNoTracking() besides the additional overhead of getting the entity tracked. See EntityState for more information.
var entity = Context.Set<T>().Find(id);
Context.Entry(entity).State = EntityState.Detached;
return entity;
Edit: This has some potential issues, if the context hasn't loaded some relationships, then those navigation properties will not work and you will be confused and frustrated why everything is returning null! See https://stackoverflow.com/a/10343174/2558743 for more info. For now on those repositories I'm overriding the FindNoTracking() methods in my repositories that I need that in.
<context>.<Entity>.AsNoTracking().Where(s => s.Id == id);
Find() does not make sense with AsNoTracking() because Find is supposed to be able to return tracked entities without going to database.. your only option with AsNoTracking is either Where or First or Single...
The accepted answer has the issue that if the item you are trying to find is already being tracked, it will return that item then mark it as untracked (which may mess up other parts of the code).
Akos is on the right track with his suggestion to build the expression yourself, but the example only works for entities that have a single primary key (which covers most cases).
This extension method works in EF Core and effectively matches the signature for the DbSet<T>.Find(object []). But it is an extension method for DbContext instead of DbSet because it needs access to the Entity's metadata from the DbContext.
public static T FindNoTracking<T>(this DbContext source, params object[] keyValues)
where T : class
{
DbSet<T> set = source.Set<T>();
if (keyValues == null || !keyValues.Any())
{
throw new Exception("No Keys Provided.");
}
PropertyInfo[] keyProps = GetKeyProperties<T>(source);
if (keyProps.Count() != keyValues.Count())
{
throw new Exception("Incorrect Number of Keys Provided.");
}
ParameterExpression prm = Expression.Parameter(typeof(T));
Expression body = null;
for (int i = 0; i < keyProps.Count(); i++)
{
PropertyInfo pi = keyProps[i];
object value = keyValues[i];
Expression propertyEx = Expression.Property(prm, pi);
Expression valueEx = Expression.Constant(value);
Expression condition = Expression.Equal(propertyEx, valueEx);
body = body == null ? condition : Expression.AndAlso(body, condition);
}
var filter = Expression.Lambda<Func<T, bool>>(body, prm);
return set.AsNoTracking().SingleOrDefault(filter);
}
public static PropertyInfo[] GetKeyProperties<T>(this DbContext source)
{
return source.Model.FindEntityType(typeof(T)).FindPrimaryKey().Properties.Select(p => p.PropertyInfo).ToArray();
}
you can then use the method directly on the DbContext. For example, if your entity has a composite key consisting of two strings:
context.FindNoTracking<MyEntity>("Key Value 1", "Key Value 2");
If you really want the Extension method to be on DbSet instead of the DbContext, you can do so but you'll need to get the context from the set in order to gain access to the metadata about the entity. Currently there isn't a good way to do this. There are some hacky ways to do this, but they involve using reflection to access private fields of framework classes, so I'd advise against it.
Alternatively...
If you have a way of figure out what the Key properties are without using the DbContext/Metadata, you can make it an extension for DbSet instead. For example, if all of your Key properties are marked with the [Key] attribute, you can use this code:
public static T FindNoTracking<T>(this DbSet<T> source, params object[] keyValues)
where T : class
{
//Pretty much the same...
}
public static PropertyInfo[] GetKeyProperties<T>()
{
return typeof(T).GetProperties()
.Where(pi => pi.GetCustomAttribute<KeyAttribute>() != null).ToArray();
}
This would also work in both Entity Framework and EF Core.
Back in 2015, an official request was made to include the functionality, i.e. combine Find() and AsNoTracking(). The issue was immediately closed after giving this argument:
AsNoTracking doesn't really make sense for Find since one of the key features of find is that it will return the already tracked version of the entity without hitting the database if it is already in memory. If you want to load an entity by key without tracking it then use Single.
Hence, you could replace:
_context.Set<Entity>().AsNoTracking().Find(id); // Invalid
with something like this:
_context.Set<Entity>().AsNoTracking().Single(e => e.Id == id);
Well, I guess if you really want to do this, you can try creating your expression yourself. I assume you have a base entity class that's generic and that's where the generic key property comes from. I named that class KeyedEntityBase<TKey>, TKey is the type of the key (if you don't have such a class, that's fine, the only thing that I used that for is the generic constraint). Then you can create an extension method like this to build the expression yourself:
public static class Extensions
{
public static IQueryable<TEntity> WhereIdEquals<TEntity, TKey>(
this IQueryable<TEntity> source,
Expression<Func<TEntity, TKey>> keyExpression,
TKey otherKeyValue)
where TEntity : KeyedEntityBase<TKey>
{
var memberExpression = (MemberExpression)keyExpression.Body;
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, memberExpression.Member.Name);
var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
return source.Where(lambda);
}
}
And then, you can use it like this (for an integer key type):
context.Set<MyEntity>.AsNoTracking().WhereIdEquals(m=>m.Id, 9).ToList();

Map Func<Type1, bool> to Func<Type2, bool>

I'm using Entity Framework behind a set of repositories. The repositories use DTOs instead of the entity objects generated by the Entity Framework. I'm using AutoMapper to convert them back and forth.
I'm having trouble figuring out how to do the following:
Method definition:
public IEnumerable<DTO.User> Get(Func<DTO.User, bool> whereClause)
Example:
var users = userRepository.Get(usersWhere => usersWhere.PostCount > someNumber);
And I'd like to be able to pass that Func<T, bool> on to the Entity Framework like so:
var users = entityFramework.Users.Where(whereClause);
Obviously, that won't build, because the .Where() method is expecting an Entities.User, not a DTO.User.
Is there a way to convert a Func<DTO.User, bool> into a Func<Entities.User, bool>?
You can use the Select() method to perform the conversion either through a cast, a call to a constructor or other. You could then use your function in the Where() filter since the collection is of the correct type.
var users = entityFramework.Users
.Select(user => (DTO.User)user) //perform the conversion
.Where(whereClause);
An alternative approach would be to create a new function with the correct types to perform the conversion from within. Then you may use the new where clause.
Func<Entities.User, bool> newWhereClause = user => whereClause((DTO.User)user); //convert here

Compare type of IEnumerable<T>

I have dictionary that gives me back a method according to the value passed in. Defined as so:
Dictionary<Type, IXmlWriterConverter>
I have now added a new function that which has the Key/type set to IEnumerable, so far so good.
But when I execute my unit test with a List containing two DataTables but the dictionary can not find the key e.g. my type conversion differs.
Why is that so? And what would be the right attempt to solve my problem?
Edit: Sorry here is the requested code ;-)
Function that generates the testvalues:
public IEnumerable<DataTable> CreateTestDataTableList()
{
var resultDataTable = new List<DataTable>();
resultDataTable.Add(CreateTestTable("testTable1", 2));
resultDataTable.Add(CreateTestTable("testTable2", 3));
return resultDataTable;
}
Function called by the unit test:
public void Write(XmlWriter xmlWriter, object value)
{
...
converter = FindConverter(value.GetType());
}
Function checking the dictionary:
public IXmlWriterConverter FindConverter(Type type)
{
if(Converters.ContainsKey(type))
{
return Converters[type];
}
return null;
}
2.Edit:
Code that adds the values to the Dictionary:
public void Add(IXmlWriterConverter xmlWriterConverter)
{
if(Converters.ContainsKey(xmlWriterConverter.InputType))
{
Remove(xmlWriterConverter);
}
Converters.Add(xmlWriterConverter.InputType, xmlWriterConverter);
}
The InputType is a readonly (get) property of the converter. I checked the type added to the dictionary and that was registered as IEnumerable, however when I checked on the typeof when passing in my list the type was List and not IEnumerable. I was told that this happens because I pass in the values as object.
This is a really code-stinky solution to me, and it cuts down on the efficiency, but you can also iterate through the GetInterfaces() method on Type, like this:
List<DataTable> l = new List<DataTable>();
var t = l.GetType();
var ints = t.GetInterfaces();
Then you could do a lookup on the type, and, if that doesn't work do a lookup on it's interfaces.
However, this feels like a terrible hack, which usually indicates that some more design work needs to be done. Is it not possible to put the List type in the dictionary? Is there no better way of doing this lookup?
Also, a note on doing dictionary lookups: It's more efficient to use the TryGetValue method, like this:
public IXmlWriterConverter FindConverter(Type type)
{
IXmlWriterConverter converter;
if( Converters.TryGetValue(type, out converter) )
{
return converter;
}
return null;
}
When you do it this way, it only does one lookup on the dictionary, whereas if you use ContainsKey it has to do two lookups.
Its a bit of hack but the only thing that gets into my mind when looking at your code, is to add an other generic write method:
public void Write<TValue>(XmlWriter writer, TValue value) {
// ...
}
This allows to identify the right type for IEnumerable and leaves the other Write method to not break any existing code.
Probably you are trying to retrieve List type and not IEnumerable (inheritance is not going to work in this context)
Please paste the code that does the lookup if you want more details and more certain answer :)
You should also post the code of where you are retrieving the test values. But based on what you've given, are you possibly using the non-generic IEnumerable to retrieve the values instead of the generic version that you're using to generate them?
The InputType is a readonly (get)
property of the converter. I checked
the type added to the dictionary and
that was registered as IEnumerable,
however when I checked on the typeof
when passing in my list the type was
List and not IEnumerable. I was told
that this happens because I pass in
the values as object.
Like they say on MythBusters, well, there's your problem! Even though List is-a IEnumerable the Type objects that represent each are definitely not the same. In programmer jargon, typeof(List) != typeof(IEnumerable). That is why the lookup is not working.

Categories