Extract strongly-typed data context instance from arbitrary query - entity-framework

Background
We have a class library which has a grid (inherits from WPF DataGrid) with refresh functionality. The grid has a IQueryable Query property, which enables the refresh. Each grid's query is defined not in the class library, but in the referencing end-project:
var dg = new RefreshableDataGrid();
dg.Query = () => new ProjectDbContext().Persons;
Each grid also has a textbox for text filtering. When text is entered in the filter, an expression is generated which checks if any string property or string-convertible property (using SqlFunctions.StringConvert) contains the filter string. The expression is then appended to the original query as an argument to Where, and thus only the records containing matching strings are returned.
//within the class library
//pseudo-code -- this is actually done via reflection, because at compile time the
//actual type of the grid is not known, and there is no generic placeholder
this.ItemsSource = this.Query.Where(filterExpression)
In some cases, the filter logic is defined in end-projects, on the entity type. For example:
public interface IFilterable {
public Expression<Func<String, Boolean>> TextSearchExpression();
}
public class Email {
public int ID {get;set;}
public int PersonID {get;set;}
public string Address {get;set;}
}
public class Person : IFilterable
public int ID {get;set;}
public string LastName {get;set;}
public string FirstName {get;set;}
public Expression<Func<String, Boolean>> TextSearchExpression() {
Dim ctx = new ProjectDbContext();
return phrase => LastName.Contains(phrase) || FirstName.Contains(phrase) ||
ctx.Emails.Where(x => x.PersonID = ID && x.Address.Contains(prase).Any();
}
}
This expression tree uses an instance of the project-specific context, which is a different instance from that of the original query. Queries cannot use components from multiple contexts (at least not in Entity Framework). I can rewrite the expression tree to use a specific instance, but I need to extract the original instance from the query.
It seems obvious that the query holds some reference to the context instance, otherwise the query would not be able to return results.
I do not want to pass the context instance to the class library.
Hence:
Given a query, how can I get the strongly-typed DbContext instance used to create the query?
In other words, what goes in the body of this method:
DbContext GetDbContext<TSource>(IQueryable<TSource> qry) {
// ???
}

It seems obvious that the query holds some reference to the context instance, otherwise the query would not be able to return results.
That's true, but it's implementation specific detail and in EF is encapsulated inside internal members/classes/interfaces.
Also taking into account that DbContext is build on top of the ObjectContext, holding a reference to the DbContext is not strongly necessary. Fortunately that's not the case :)
The following uses reflection and implementation details of the latest EF6.1.3 (tested and working if you don't use some 3rd party extensions like LinqKit and similar that replace the query provider):
public static DbContext GetDbContext<TSource>(this IQueryable<TSource> query)
{
const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
var provider = query.Provider;
var internalContextProperty = provider.GetType().GetProperty("InternalContext", flags);
if (internalContextProperty == null) return null;
var internalContext = internalContextProperty.GetValue(provider, null);
if (internalContext == null) return null;
var ownerProperty = internalContext.GetType().GetProperty("Owner", flags);
if (ownerProperty == null) return null;
var dbContext = (DbContext)ownerProperty.GetValue(internalContext, null);
return dbContext;
}

I would recommend passing an instance of MyDataContext into your query function
public class DACPerson
{
public static IQueryable<Person> GetAllAsQueryable(MyDataContext db)
{
return db.People.AsQueryable();
}
}
This allows you to do the following in the calling function:
public List<Person> UpdateListofPeople(string term)
{
using(DataContext db = new DataContext())
{
var people = DACPerson.GetAllAsQueryable(db);
var result = people.Where(x=>x.Username.Contains(term)).
//call other Data Access Component FUnctions here using same DB....
}
}
i.e. you can bring the filtering to the layer above the data access class.
Some people may not like to do this. You may get the advice that its best to keep all entityframeowrk functionality within the same layer and just return the DTO. I like the above approach though. It depends on who will have to maintain each part of your application in the future.
Hope this helps at least

Related

How to query in LINQ & Entity Framework the unmapped property

So I partially followed from an SO answer on how to store a property with array datatype in Entity Framework. What I didn't follow on that answer is setting the string InternalData to be private instead of public as I find it a code smell if it is set to public (not enough reputation to comment there yet).
I also managed to map the private property in entity framework from this blog.
When I perform CR (create, read) from that entity, all goes well. However, when my LINQ query has a where clause using that property with array datatype, it says that "System.NotSupportedException: 'The specified type member is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'".
How to work around on this? Here are the relevant code blocks:
public class ReminderSettings
{
[Key]
public string UserID { get; set; }
[Column("RemindForPaymentStatus")]
private string _remindForPaymentStatusCSV { get; set; }
private Status[] _remindForPaymentStatus;
[NotMapped]
public Status[] RemindForPaymentStatus
{
get
{
return Array.ConvertAll(_remindForPaymentStatusCSV.Split(','), e => (Status)Enum.Parse(typeof(Status), e));
}
set
{
_remindForPaymentStatus = value;
_remindForPaymentStatusCSV = String.Join(",", _remindForPaymentStatus.Select(x => x.ToString()).ToArray());
}
}
public static readonly Expression<Func<ReminderSettings, string>> RemindForPaymentStatusExpression = p => p._remindForPaymentStatusCSV;
}
public enum Status
{
NotPaid = 0,
PartiallyPaid = 1,
FullyPaid = 2,
Overpaid = 3
}
protected override void OnModelCreating(DbModelBuuilder modelBuilder)
{
modelBuilder.Entity<ReminderSettings>().Property(ReminderSettings.RemindForPaymentStatusExpression);
}
//This query will cause the error
public IEnumerable<ReminderSettings> GetReminderSettingsByPaymentStatus(Status[] statusArray)
{
var query = ApplicationDbContext.ReminderSettings.Where(x => x.RemindForPaymentStatus.Intersect(statusArray).Any());
return query.ToList(); //System.NotSupportedException: 'The specified type member 'RemindForPaymentStatus' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.'
}
Entity Framework can not translate LINQ expressions to SQL if they access a property that is annotated as [NotMapped]. (It also can not translate if the property contains custom C# code in its getter/setter).
As a quick (but potentially low performance) workaround, you can execute the part of the query that does not cause problems, then apply additional filtering in-memory.
// execute query on DB server and fetch items into memory
var reminders = dbContext.ReminderSettings.ToList();
// now that we work in-memory, LINQ does not need to translate our custom code to SQL anymore
var filtered = reminders.Where(r => r.RemindForPaymentStatus.Contains(Status.NotPaid));
If this causes performance problems, you have to make the backing field of your NotMapped property public and work directly with it.
var filtered = dbContext.ReminderSettings
.Where(r => r._remindForPaymentStatusCSV.Contains(Status.NotPaid.ToString("D"));
Edit
To handle multiple Status as query parameters, you can attach Where clauses in a loop (which behaves like an AND). This works as long as your Status enum values are distinguishable (i.e. there is no Status "11" if there is also a Status "1").
var query = dbContext.ReminderSettings.Select(r => r);
foreach(var statusParam in queryParams.Status) {
var statusString = statusParam.ToString("D");
query = query.Where(r => r._remindForPaymentStatusCSV.Contains(statusString));
}
var result = query.ToArray();

EF get dbset name in runtime from Type

Purpose:
I need to get the name of the dbset name of the entity
typeof(UserAccount) = "UserAccounts".
But in runtime I need a common type for the loop and therefor do not know example "UserAccount".
Only the "name" from typeof?
I have created an DbContext with some entities.
I have been googling for some time but it do not seem to be working for me because of the Type converting?
Please see my method GetDbSetName in the bottom of this description.
I am pretty new at this EF stuff - so please help med with my issue as described below ;-)
public class MyEntities : DbContext
{
public DbSet<UserAccount> UserAccounts { get; set;}
public DbSet<UserRole> UserRoles { get; set; }
public DbSet<UserAccountRole> UserAccountRoles { get; set; }
}
Defined a list of Type to control the output:
public static List<Type> ModelListSorted()
{
List<Type> modelListSorted = new List<Type>();
modelListSorted.Add(typeof(UserRole));
modelListSorted.Add(typeof(UserAccountRole));
modelListSorted.Add(typeof(UserAccount));
return modelListSorted;
}
The problem is below using Type - If I use "UserAccount" it Works and I get "UserAccounts".
But I do not have the "UserAccount" in runtime as I am in a loop with a serie of types.
I do only have the Type list giving the e
public static loopList()
{
List<Type> modelListSorted = ModelListSorted();
foreach (Type currentType in modelListSorted)
{
string s = DataHelper.GetDbSetName(currentType, db);
}
}
HERE IS THE METHOD GIVING ME THE CHALLANGES ;-)
Meaning not compiling.
saying I am missing a assembly?
I know it is pretty pseudo but can this be done smoothly?
public static string GetDbSetName(Type parmType, MyEntities db)
{
string dbsetname = (db as IObjectContextAdapter).ObjectContext.CreateObjectSet<parmType>().EntitySet.Name;
return dbsetname;
}
The challenge here is that two reflection steps are involved, one to invoke the generic CreateObjectSet method and one to get the EntitySet from the result. Here's a way to do this:
First, the method:
string GetObjectSetName(ObjectContext oc, MethodInfo createObjectSetMethodInfo,
Type objectSetType, Type entityType)
{
var objectSet = createObjectSetMethodInfo.MakeGenericMethod(entityType)
.Invoke(oc, null);
var pi = objectSetType.MakeGenericType(entityType).GetProperty("EntitySet");
var entitySet = pi.GetValue(objectSet) as EntitySet;
return entitySet.Name;
}
As you see, I first get the ObjectSet by invoking the MethodInfo representing the generic method CreateObjectSet<T>(). Then I find the PropertyInfo for the EntitySet property of the generic type ObectSet<T>. Finally, I get this property's value and the name of the obtained EntitySet.
To do this, I first get a MethodInfo for CreateObjectSet<>() (the one without parameters) and the ObjectSet<> type
var createObjectSetMethodInfo =
typeof(ObjectContext).GetMethods()
.Single(i => i.Name == "CreateObjectSet"
&& !i.GetParameters().Any());
var objectSetType = Assembly.GetAssembly(typeof(ObjectContext))
.GetTypes()
.Single(t => t.Name == "ObjectSet`1");
In GetObjectSetName their generic parameters are specified by a concrete entity type, which is done by these "MakeGeneric..." methods.
var oc = (dbContextInstance as IObjectContextAdapter).ObjectContext;
var entityType = typeof(UserRole);
var name = GetObjectSetName(oc, createObjectSetMethodInfo, objectSetType, entityType);
In EF 6 these should be the usings:
using System.Data.Entity.Core.Metadata.Edm
using System.Data.Entity.Core.Objects
using System.Data.Entity.Infrastructure
using System.Linq
using System.Reflection

Using .Find() & .Include() on the same query

I have the following method automatically generated from the scaffold template with repository:-
public Group Find(int id)
{
return context.Groups.Find(id);
}
But since the Groups object has two navigation properties which I need , so I wanted to include the .Include, so I replace the .find with .where :-
public Group Find(int id)
{
return context.Groups.Where(c=>c.GroupID==id)
.Include(a => a.UserGroups)
.Include(a2 => a2.SecurityRoles)
.SingleOrDefault();
}
But my question is how can I apply the .Include with the .find() instead of using .Where()?
I was just thinking about what find actually does. #lazyberezovsky is right include and find cant be used in conjunction with each other. I think this is quite deliberate and here's why:
The Find method on DbSet uses the primary key value to attempt to find
an entity tracked by the context. If the entity is not found in the
context then a query will be sent to the database to find the entity
there. Null is returned if the entity is not found in the context or
in the database.
Find is different from using a query in two significant ways:
A round-trip to the database will only be made if the entity with the given key is not found in the context.
Find will return entities that are in the Added state. That is, Find will return entities that have been added to the context but have
not yet been saved to the database.
(from http://msdn.microsoft.com/en-us/data/jj573936.aspx)
Because find is an optimised method it can avoid needing a trip to the server. This is great if you have the entity already tracked, as EF can return it faster.
However if its not just this entity which we are after (eg we want to include some extra data) there is no way of knowing if this data has already been loaded from the server. While EF could probably make this optimisation in conjunction with a join it would be prone to errors as it is making assumptions about the database state.
I imagine that include and find not being able to be used together is a very deliberate decision to ensure data integrity and unnecessary complexity. It is far cleaner and simpler
when you are wanting to do a join to always go to the database to perform that join.
You can't. Find method defined on DbSet<T> type and it returns entity. You can't call Include on entity, so the only possible option is calling Find after Include. You need DbSet<T> type for that, but Include("UserGroups") will return DbQuery<T>, and Include(g => g.UserGroups) will also return DbQuery<T>:
public static IQueryable<T> Include<T>(this IQueryable<T> source, string path)
where T: class
{
RuntimeFailureMethods.Requires(source != null, null, "source != null");
DbQuery<T> query = source as DbQuery<T>;
if (query != null)
return query.Include(path); // your case
// ...
}
DbQuery<T> is not a child of DbSet<T> thus method Find is not available. Also keep in mind, that Find first looks for entity in local objects. How would it include some referenced entities, if they don't loaded yet?
You can try to do this:
public static class DbContextExtention
{
public static TEntity FirstOfDefaultIdEquals<TEntity, TKey>(
this IQueryable<TEntity> source, TKey otherKeyValue)
where TEntity : class
{
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, "ID");
var equal = Expression.Equal(property, Expression.Constant(otherKeyValue));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
return source.FirstOrDefault(lambda);
}
public static TEntity FirstOfDefaultIdEquals<TEntity>(
this ObservableCollection<TEntity> source, TEntity enity)
where TEntity : class
{
var value = (int)enity.GetType().GetProperty("ID").GetValue(enity, null);
var parameter = Expression.Parameter(typeof(TEntity), "x");
var property = Expression.Property(parameter, "ID");
var equal = Expression.Equal(property, Expression.Constant(value));
var lambda = Expression.Lambda<Func<TEntity, bool>>(equal, parameter);
var queryableList = new List<TEntity>(source).AsQueryable();
return queryableList.FirstOrDefault(lambda);
}
}
GetById:
public virtual TEntity GetByIdInclude(TId id, params Expression<Func<TEntity, object>>[] includes)
{
var entry = Include(includes).FirstOfDefaultIdEquals(id);
return entry;
}
Method include EntityFramework Core (look here(EF6 and EF Core)):
protected IQueryable<TEntity> Include(params Expression<Func<TEntity, object>>[] includes)
{
IIncludableQueryable<TEntity, object> query = null;
if (includes.Length > 0)
{
query = DbSet.Include(includes[0]);
}
for (int queryIndex = 1; queryIndex < includes.Length; ++queryIndex)
{
query = query.Include(includes[queryIndex]);
}
return query == null ? DbSet : (IQueryable<TEntity>)query;
}

MVC4 and Entity Framework Inheritance and Include

I have some simple objects
public class DataClass
{
public int id;
public string Data;
}
public class Job()
{
public int id;
}
public class NewJob : Job
{
public DateTime StartDate;
public DataClass data;
}
I have then defined them in my dBContext()
public DbSet<Job> Jobs { get; set; }
public DbSet<DataClass> DataClass { get; set; }
Now if I use the following code
NewJob job = (NewJob) db.Jobs.Find(id);
This works fine but returns "data" as null
I know I define the class with the virtual keyword and it works and populates the "data" object.
public class NewJob : Job
{
public DateTime StartDate;
public virtual DataClass data;
}
But in my case I "normally" do not want the "data" object to be populated. So I need to load it on demand.
If I try something like
NewJob job = (NewJob)db.Jobs.Include("data").First();
I get an exception
A specified Include path is not valid. The EntityType 'Models.Job' does not declare a navigation property with the name 'data'.
I guess this is because it is looking at "job" and not "NewJob" when it is trying to do the include.
I also do not like the include with a string - no design time checking.
It looks like you are trying to convert data object to your domain object via type casting which is a very bad idea. What you want to do is grab your data object, instantiate your domain object, and map your data values to the domain object using some type of helper class. A very helpful tool I have been using is Automapper. Its a tool that will allow you to map one object to another. It also allows the use of regular expression to help with the mappings if the naming conventions between the 2 objects are different.
If you're using Entity Framework Code First and want to create instances of derived classes/entities you should do the following:
using (var db = new MyDbContext())
{
var newJob = db.Jobs.Create<NewJob>();
newJob.data.Data = "some data for a new job"; // this is string Data from DataClass
db.Jobs.Add(newJob);
db.SaveChanges();
}
After a lot of searching I found the following which can help.
If you include the System.Data.Entity namespace in your using clause then you can use the extension method .Include() after OfType<>() which is not normally available.
Slightly different code sample
using System.Data.Entity;
NewJob job = (NewJob)db.Jobs.OfType<NewJob>().Include(m => m.data).Where(x => x.id == id).FirstOrDefault();
This seems to be working for me in the example I used.

Issue with passing objects to domain method

I have WCF Ria Services App with DTO objects. I defined relations between my DTO objects:
[Include]
[Association("FK_Items_OrderID", "ID", "OrderID")]
public List<Item> Items { get; set; }
So I can see collection of Items on the client and operate with it. Then I want to save changes and call this method:
[Invoke]
public void SaveOrderChanges(Order order)
{
_dataManager.SaveOrderChanges(order);
}
It is domain service method. But I see than collection of related Items in Order object here is already NULL, but when I call this method on the client and pass order to this method collection is filled.
Does anybody know what to do? Thanks.
That's happen because the client side serializer don't include assocations. Wcf ria services has a different way to handle this kind of scenarious, but this involves the use of DomainContext and the standard Get/Insert/Update/Delete methods.
My advice is to change the sign of your save method to something like
[Invoke]
public void SaveOrderChanges(string serializedOrder)
{
Order order = MyCustomSerializer.Deserialize<Order>(serializedOrder);
_dataManager.SaveOrderChanges(order);
}
Thi`s is an utilities class that I use to serialize and deserialize objects, however it should be tricked to include the Items collection.
public static class Serialization
{
public static string Serialize<T>(T obj)
{
//Create a stream to serialize the object to.
var ms = new MemoryStream();
// Serializer the User object to the stream.
var ser = new DataContractSerializer(typeof (T));
ser.WriteObject(ms, obj);
byte[] array = ms.ToArray();
ms.Close();
return Encoding.UTF8.GetString(array, 0, array.Length);
}
public static T Deserialize<T>(string obj) where T : class
{
if (obj == null)
return null;
var serializer = new DataContractSerializer(typeof (T));
var stream = new MemoryStream(Encoding.UTF8.GetBytes(obj));
var result = serializer.ReadObject(stream) as T;
return result;
}
}
Of course, you need to do the opposite client-side and serialize your order and items.
This is the easiest way to go that I know, however you could also take a look here.
Eventually, try to refactor your code in the way WCF Ria is meant to go (Context based)
Hope this helps,
Marco