How to combine Find() and AsNoTracking()? - c#

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();

Related

How can I implement multiple Include in Entity Framework?

I use Entity framework 6. I have a Transaction object with several navigation properties. It is easy to implement eager loading using multiple Include.
var aa = db.Transactions.Include(p => p.Account).Include(p => p.Instrument);
How can I implement the same if the fields to be included are parameters?
var aa = db.Transactions.IncludeMore(delegatesToBeIncluded);
If delegatesToBeIncluded is null then there is nothing to be included.
https://stackoverflow.com/a/38823723/5852947 This is similar what I want but it uses string instead of delegates.
https://stackoverflow.com/a/35889204/5852947 This is also interesting.
How to pass lambda 'include' with multiple levels in Entity Framework Core? This focuses on multiple level (I have one level)
https://stackoverflow.com/a/52156692/5852947 This is promising also.
Which direction should I go?
Revision 1: Why I need this?
Based on the elements of aa new objects will be created. I realized that at each object creation EF reads the DB (lazy loading is used). It is just 50 ms, but it is repeated n times.
This function is implemented in a template class, so Transactions is also a parameter.
Revision 2: In the full code there is filtering (pagination to be exact), and ToList() at then end. The tricky part that it is implemented in a template function. dbTableSelector is a delegate: readonly Func<MainDbContext, DbSet<TDbTable>> dbTableSelector;
var myList = dbTableSelector(db).Where(WhereCondition).
Skip(numberOfSkippedRows).Take(PageSize).OrderBy(OrderByCondition).ToList();
After that I transform each element of myList to another type of object. This is where lazy loading is activated one by one for each element. That is why I try to use Include. If dbTableSelector(db) returns Transactions I have to Include different elements when it returns let us say Instruments. So IncludeMore should have a List parameter which defines the fields to be included.
Here is the solution. It is based on this.
public static class IQueryableExtensions
{
public static IQueryable<T> IncludeMultiple<T, TProperty>(this IQueryable<T> query,
Expression<Func<T, TProperty>>[] includeDelegates) where T : class
{
foreach (var includeDelegate in includeDelegates)
query = query.Include(includeDelegate);
return query;
}
}
This is the calling:
var pathsA = new Expression<Func<ViewTransaction, object>>[2] { p => p.Account, p => p.Instrument };
var pathsB = new Expression<Func<ViewTransaction, object>>[1] { p => p.Account};
var pathsC = Array.Empty<Expression<Func<ViewTransaction, object>>>();
var a = db.ViewTransactions.IncludeMultiple(pathsA).Single(e => e.Id == 100);

How can I do it ? I don't want to add same record in database with Entity Framework

What methods can I use Find, FirstOrDefault, Any ....
public virtual void Add(T entity)
{
using (MovieAppContext _context = new MovieAppContext())
{
var record = _context.Set<T>().Find(entity); // does not work I got e
if (record == null)
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
}
}
Given your scope here, DbSet<T>.Find<T>(params object[]) is probably not the method you should be using.
The following solution uses IQueryable<T>.Any<T>(Expression<Func<T, bool>>), a Linq Extension Method that accepts a Predicate\Function.
Code Sample using it
using (MovieAppContext _context = new MovieAppContext())
{
bool queryResult = _context.Set<T>().Any((e) => e.Name.Equals(entity.Name, StringComparison.OrdinalIgnoreCase));
if (queryResult)
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
}
Here, we are defining an anonymous function delegate, Func<T, bool>, that is in turn being used to define a Linq Expression, Expression<Func<T, bool>>.
Note
I defined this function predicate expression to evaluate on the entity names (a made-up property of this type; that in this example portrays a unique constraint.)
Also, an alternative to using anonymous functions declaration for this expression is to declare it, then pass it.
That looks like the following:
...
Expression<Func<T, bool>> WhereEntityNameMatches = (e) => e.Name.Equals(entity.Name, StringComparison.OrdinalIgnoreCase));
bool queryResult = _context.Set<T>().Any(WhereEntityNameMatches);
...
Which may help out with reusability and scope considerations.
For consideration, An alternative here would be to not attempt the query lookup and check at all.
Instead, you would do a try-catch for DbUpdateException in the attempt to save changes after\following this Add operation.
try
{
_context.Set<T>().Add(entity);
_context.SaveChanges();
}
catch (DbUpdateException)
{
var isRaisedByUniqueIndexOrConstraint = new[] { "2601", "2627" }.Contains(ex.GetBaseException().Data["HelpLink.EvtID"]) ?? false;
if (!isRaisedByUniqueIndexOrConstraint)
throw;
// Could suppress and\or handle attempted duplicate record adding
}
This relies on those index\constraints existing, so you would first define a Unique Index on the column(s) (as well as your entity type using Data Annotations: IndexAttribute).
Note
Part of this answer\solution purposes getting the Error Number from ADO.NET SqlClient Base Exception to further scope and qualify the cause of exception. The Error Numbers, "2601", "2627", are hard-coded in this example.

Performance with Entity Framework

I have another question about performance with EF.
There's one method to get an object from context:
tDocumentTyp DocumentTypObject = Context.tDocumentTyps.Where(s => s.DocumentTypID == iTypID).FirstOrDefault();
This method takes ~2979 ms.
Then I wrote a method to get the DBSet via reflection and is executed this way:
tDocumentTyp DocumentTypObject = Context.GetEntries<tDocumentTyp>().Where(s => s.DocumentTypID == iTypID).FirstOrDefault();
My method needs ~222 ms to execute.
So my question now is, why is my method much faster than the original one? Or is there anything wrong with my method?
To make this a bit easier, here is my method for getting DBSet via reflection:
public static IEnumerable<T> GetEntries<T>(this AppContext DataContext,
string PropertyName = null, IEnumerable<string> includes = null) where T : IEntity
{
Type ContextType = typeof(AppContext);
PropertyInfo Entity = null;
if (null == PropertyName)
Entity = ContextType.GetProperty(typeof(T).Name)
?? ContextType.GetProperty(typeof(T).Name + "s");
else
Entity = ContextType.GetProperty(PropertyName);
if (null == Entity)
throw new Exception("Could not find the property. If the property is not equal to the tablesname, you have to parametrize it.");
DbQuery<T> set = ((DbSet<T>)Entity.GetValue(DataContext, null));
if (includes != null)
includes.ForEach(f => set = set.Include(f));
return set;
}
The second example is getting the entire table and applying the Where in memory. You are applying the extension method System.Linq.Enumerable.Where which operates on IEnumerable<T>. Note that this is an in-memory implementation. In the first example you using the extension method System.Linq.Queryable.Where which operates on IQueryable<T>. This is a different method, though they share the same name.
If you inspect closely you will also find that in the first example the method parameter is of type Expression<Func<T, bool>> whilst in the second example it is simply Func<T, bool>. This is a very important difference: the expression can be processed to produce a SQL-query.
So why is the second one faster? Well, that is hard to answer without more information about your data source. But as others have noted in the comments, if the database is not indexed then it may well be quicker to select the entire table and execute the filter in memory than to have the SQL server apply the filtering.

Add object to Entity Framework dynamically using Reflection

In the following code, the type of domainObject varies (but ends with DO, which I trim then to get the corresponding table name). Having the name of the table and its type, I want to update an existing object - its name is the same as the tableName due to the EF - in the database with the new property values from domainObject. Therefore, I have to find the POCO in the table with the same ID first to overwrite this. This is the code so far:
public void Update(object domainObject)
{
Type type = domainObject.GetType();
string tableName = type.Name.Substring(0, type.Name.Length - 2);
PropertyInfo tableProp = typeof(MyDbContext).GetProperty(tableName);
Type tableType = tableProp.PropertyType;
Type pocoType = tableType.GetGenericArguments()[0];
int id = (int)type.GetProperty("ID").GetValue(domainObject);
using (var context = new MyDbContext())
{
object table = tableProp.GetValue(context);
MethodInfo singleMethod = tableType.GetMethod("Single");
}
}
Usually, knowing the actual table and not just its type, I would now get the POCO via
var poco = context.TableName.Single(item => item.ID == id);
There's 2 problems here:
(1) Single is an extension method.
(2) I don't have an idea how to get the lambda expression in form of an object to pass it to the Invoke of Single.
Is there any way to do this at all with Reflection, or do I have to work around this? (For example, I could iterate through the items in table and check manually [which would load everything from the DB into memory and thus should be avoided], or maybe configure the EF to do some kind of 'override' whenever I just Add and object whose ID is already present if this is possible). Even supposing I could work around this, I'd still like to know a definitive answer to this question, since it's pretty interesting for me!
If you want to use reflection and to find given entity by ID then, if ID is primary key this is fairly simple as this is all you have to do:
object entity = context.Set(domainObject.GetType()).Find(id);
If your property is not primary key then you need to do it as follows:
ParameterExpression p = Expression.Parameter(domainObject.GetType());
Expression property = Expression.Property(p, "ID");
Expression c = Expression.Constant(id);
Expression body = Expression.Equal(property, c);
Expression exp = Expression.Lambda(body, new ParameterExpression []{ p });
MethodInfo singleMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "Single" && m.GetParameters().Count() == 2)
.MakeGenericMethod(domainObject.GetType());
DbSet dbSet = context.Set(domainObject.GetType());
object entity = singleMethod.Invoke(null, new object[]{ dbSet, exp });
First with Expression class you build expression that will be passed to Single method (in your case this will be p => p.ID == id). Then you search proper Single method from Queryable class. The last thing is to invoke this method with proper parameters. This way you may do any linq queries with use of Reflection.
You simply need to make a generic method, with a type parameter that represents the type of your entity and use the corresponding DbSet.
public int Update<TEntity>(TEntity domainObject)
{
int id = domainObject.Id; // Needs interface !!
using (var context = new MyDbContext())
{
var objectInDb
= ctx.DbSet<TEntity>.Single(e => e.Id == id); // Needs interface !!
// Use ValueInjecter (not AutoMapper) to copy the properties
objectInDb.InjectFrom(domainObject); // needs ValueInjecter Nuget Package
context.SaveChanges();
}
return userId;
}
As you see in the code comments, your entities need to implement an interface so that you can access the Id property:
public interface IId
{
public int Id { get; set; }
}
And then you need to include the generic method in a generic class that has the corresponding type constraint:
public RepoClass<TEntity>
where TEntity : IId
{
// Define the generic method here
}
In this way you don't have to resort to Reflection.
If you're using some kind of T4 template,or whatever, to create your POCOs, make them partial classes, so that you can declare the interface in a separate file, like this:
public partial MyDomainClass : IId
{
}
In this wya, the interface won't be lost when you update your Db Context objects.
And finally, download an use ValueInjecter, for example using Nuget Package Manager, or running Install-Package ValueInjecter in the Nuget Package Manager console.
When you include using Omu.ValueInjecter; namespace in your code, you'll get an InjectFrom extension method on all objects, that allows to automatically copy all the properties from a source object (by matching their names). Don't use AutoMapper, or you'll have to solve other problems.
Alternatively, you can check that the object exists in the DB (for security) and use the original object, without copying the properties, i.e.
var updatedObject = ctx.Set<TEntity>().Attach(domainObject);
ctx.Entry(updatedObject).State = EntityState.Modified;
ctx.SaveChanges();
I prefer this solution, better than the previous one.

Fetching related object using LINQ and EF

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.

Categories