How do I rewrite query expressions to replace enumerations with ints? - c#

Inspired by a desire to be able to use enumerations in EF queries, I'm considering adding an ExpressionVisitor to my repositories that will take incoming criteria/specifications criteria and rewrite them to use the corresponding persisted int property.
I'm consistently using the following Value-suffix pattern in my (code-first) entities:
public class User : IEntity
{
public long ID { get; set; }
internal int MemberStatusValue { get; set; }
public MemberStatus MemberStatus
{
get { return (MemberStatus) MemberStatusValue; }
set { MemberStatusValue = (int) value; }
}
}
And map this to the database using the following:
internal class UserMapping : AbstractMappingProvider<User>
{
public override void DefineModel( DbModelBuilder modelBuilder )
{
// adds ToTable and other general mappings
base.DefineModel( modelBuilder );
Map.Property( e => e.MemberStatusValue ).HasColumnName( "MemberStatus" );
}
}
In my repositories I have the following method:
public IQueryable<T> Query( Expression<Func<T, bool>> filter, params string[] children )
{
if( children == null || children.Length == 0 )
{
return Objects.Where( filter );
}
DbQuery<T> query = children.Aggregate<string, DbQuery<T>>( Objects, ( current, child ) => current.Include( child ) );
return filter != null ? query.Where( filter ) : query;
}
I'd like to add a method call inside this method to rewrite the filter expression, replacing all references to the MemberStatus property with references to MemberStatusValue.
I suppose it will be a solution involving something like seen in this SO post, but I'm not sure exactly how to get from idea to implementation.
If you can give any advice on the potential performance impact of adding this feature, that would also be appreciated.

I'm not sure whether this is quite what you're after, but I've found it simpler to handle enums in a similar but slightly different way. To wit, I have two properties, as you do, but my int property is public and is what the database persists; I then have another public "wrapper" property that gets/sets the int property value via casts from/to the desired enumerated type, which is what's actually used by the rest of the application.
As a result, I don't have to mess around with the model; EF understands and persists the int property just fine while the rest of the application gets nice interactions with the enum type. The only thing I don't like about my approach is that I have to write my LINQ statements with a bunch of casts on any enum value I'm trying to query to turn it into an int to match against the field that's actually persisted. It's a small price, however, and I'd like to suggest it to you because it appears to me that you're using a string to generate your query which gives up all the type safety, Intellisense, etc. that LINQ provides.
Finally, if you're interested in a walkthrough of how to use the new enum features in EF 5 (which is available in beta for download now if you'd like to try it out), check this out:
http://msdn.microsoft.com/en-us/hh859576

Related

Query string properties stored as XML

I am using Entity Framework to query a db which is defined by a model: inside this model I have several classes having a #region dynamic values:
[DataContract]
public class Job : AbstractEntity, IJob
{
[DataMember]
public virtual Guid Id { get; set; }
...
#region dynamic values
[DataMember]
public virtual string MetadataValue { get; set; }
[DataMember]
public virtual string ParametersValue { get; set; }
[DataMember]
public virtual string AttributesValue { get; set; }
#endregion
#region links
...
#endregion
}
AttributesValue, MetadataValue and ParametersValue are declared as string but are stored inside the db as XML documents. I am aware that this is not consistent with the model and should be changed, but for some reasons it has been managed this way and I am not allowed to modify it.
I have created a Unit Test in order to better handle the problem, and here is the code:
public class UnitTest1
{
private ModelContext mc;
[TestInitialize]
public void TestInit()
{
IModelContextFactory mfactory = ModelContextFactory.GetFactory();
mc = mfactory.CreateContextWithoutClientId();
}
[TestMethod]
public void TestMethod1()
{
DbSet<Job> jobs = mc.Job;
IQueryable<string> query = jobs
.Where(elem => elem.AttributesValue == "<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>")
.Select(elem => elem.AttributesValue);
List<string> attrs = new List<string>(query);
foreach (string av in attrs)
{
Console.WriteLine(av ?? "null");
}
Assert.AreEqual(1, 1);
}
}
A quick explanation about the TestInit and ModelContext:
ModelContext inherit from DbContext and is an abstract class implemented by SqlModelContext and OracleModelContext (both override OnModelCreating). Depending on the connection string, CreateContextWithoutClientId return a SqlModelContext or an OracleModelContext. Summary: a Factory pattern.
Let's get down to brass tacks: the TestMethod1.
The problem here is in the Where method and the error returned is, as expected:
SqlException: The data types nvarchar and xml are incompatible in the equal to operator.
(From now on I will only consider the AttributesValue property)
I thought of some possible solutions, which are:
Creating a new property inside the model (but not mapped to the db) and use it as a "proxy" instead of accessing directly AttributesValue. However only mapped properties can be used in Linq, so I discarded it.
Operating directly on the inner SQL query generated by the IQueryable and using a customized CAST for Oracle and Sql Server db. I'd rather avoid go for this for obvious reasons.
Is there a way to specify a custom Property Getter so that I can cast AttributesValue to string before it is accessed? Or maybe some configuration on the DbModelBuilder?
I'm using standard Entity Framework 6, Code-First approach.
There is no standard xml data type or standard canonical function for converting string to xml or vice versa.
Fortunately EF6 supports the so called Entity SQL Language which supports an useful construct called CAST:
CAST (expression AS data_type)
The cast expression has similar semantics to the Transact-SQL CONVERT expression. The cast expression is used to convert a value of one type into a value of another type.
It can be utilized with the help of the EntityFramework.Functions package and Model defined functions.
Model defined functions allow you to associate Entity SQL expression with user defined function. The requirement is that the function argument must be an entity.
The good thing about Entity SQL operators is that they are database independent (similar to canonical functions), so the final SQL is still generated by the database provider, hence you don't need to write separate implementations for SqlServer and Oracle.
Install the EntityFramework.Functions package through Nuget and add the following class (note: all the code requires using EntityFramework.Functions;):
public static class JobFunctions
{
const string Namespace = "EFTest";
[ModelDefinedFunction(nameof(MetadataValueXml), Namespace, "'' + CAST(Job.MetadataValue AS String)")]
public static string MetadataValueXml(this Job job) => job.MetadataValue;
[ModelDefinedFunction(nameof(ParametersValueXml), Namespace, "'' + CAST(Job.ParametersValue AS String)")]
public static string ParametersValueXml(this Job job) => job.ParametersValue;
[ModelDefinedFunction(nameof(AttributesValueXml), Namespace, "'' + CAST(Job.AttributesValue AS String)")]
public static string AttributesValueXml(this Job job) => job.AttributesValue;
}
Basically we add simple extension method for each xml property. The body of the methods doesn't do something useful - the whole purpose of these methods is not to be called directly, but to be translated to SQL when used inside LINQ to Entities query. The required mapping is provided through ModelDefinedFunctionAttribute and applied via package implemented custom FunctionConvention. The Namespace constant must be equal to typeof(Job).Namespace. Unfortunately due to the requirement that attributes can use only constants, we can't avoid that hardcoded string as well as the entity class / property names inside the Entity SQL string.
One thing that needs more explanation is the usage of '' + CAST. I wish we could use simply CAST, but my tests show that SqlServer is "too smart" (or buggy?) and removes the CAST from expression when used inside WHERE. The trick with appending the empty string prevents that behavior.
Then you need to add these functions to entity model by adding the following line to your db context OnModelCreating override:
modelBuilder.AddFunctions(typeof(JobFunctions));
Now you can use them inside your LINQ to Entities query:
IQueryable<string> query = jobs
.Where(elem => elem.AttributesValueXml() == "<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>")
.Select(elem => elem.AttributesValue);
which translates to something like this in SqlServer:
SELECT
[Extent1].[AttributesValue] AS [AttributesValue]
FROM [dbo].[Jobs] AS [Extent1]
WHERE N'<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>'
= ('' + CAST( [Extent1].[AttributesValue] AS nvarchar(max)))
and in Oracle:
SELECT
"Extent1"."AttributesValue" AS "AttributesValue"
FROM "ORATST"."Jobs" "Extent1"
WHERE ('<coll><item><key>ids:ui:description</key><value>Session Test</value></item><item><key>ids:all:type</key><value>signature</value></item></coll>'
= ((('')||(TO_NCLOB("Extent1"."AttributesValue")))))

Store bool Property as integer with Npgsql and Entity Framework

I'am using Entity Framework 6.1 with the Npgsql 2.2.5 driver.
Entity
public class MyEntity
{
public bool Deprecated { get; set; }
}
Mapping
public class MyEntityMap : EntityTypeConfiguration<MyEntity>
{
public MyEntityMap ()
{
Property(t => t.Deprecated)
.HasColumnName("status")
.HasColumnType("Integer")
.IsOptional();
}
}
When I try to read something from the Database, I get an exception, that is not directly related to something with mapping:
InvalidOperationException "Sequence doesn't contain any matching element" (translated from german, don't know the exact english text)
Is it possible to store a boolean property as an integer? I did a Workaround with introducing a new property Status of type int, which is mapped to the status column. Then I added the NotMappedattribute to Deprecated, made it return Status != 0 int its getter and setting Status to 1 or 0. It is working, but now I can't use Deprecated in linq queries.
I'd simply change the datatype of the column, but there is a legacy system using this database as well. Introducing a new column and keep both in sync with some database triggers would be a solution, but my model has some of these issues. So I'd like to have a more generic solution.
Is there a better way?
Yeah... same problem here.
I don't think there's a clean way to do it unless you modify the source for the npgsql EF provider.
public static class DbValue
{
public const int FALSE = 0; //or something like this
}
public class MyEntity
{
[Column("Deprecated")]
public integer DeprecatedStatus { get; set; }
[NotMapped]
public bool DeprecatedBool
{
get { this.DeprecatedStatus != 0 }
set { this.DeprecatedStatus = (value ? 1 : 0) }
}
}
//Then in Linq
db.MyEntities.Where(e => e.DeprecatedStatus == DbValue.FALSE);
//and
db.MyEntities.Where(e => e.DeprecatedStatus != DbValue.FALSE);
Oh, hey I just thought of another idea. You could write Expression objects in the code and pass them into your Linq (since IQueryable<> uses expressions)
so like this:
public class MyEntity
{
public static Expression<Func<MyEntity, bool>> IsDeprecated = (myEntity) => myEntity.Deprecated != 0;
public static Expression<Func<MyEntity, bool>> IsNotDeprecated = (myEntity) => myEntity.Deprecated == 0;
public integer Deprecated{ get; set; }
}
//Then in Linq
db.MyEntities.Where(MyEntity.IsDeprecated);
//and
db.MyEntities.Where(MyEntity.IsNotDeprecated);
The reason for using Expressions instead of Func stuff can be a little confusing for novices, but the pattern is certainly easy to follow if you're comfortable with lambda expressions. I've done this kind of thing before and it works. What doesn't work is trying to dynamically create your Expression objects at runtime because something goes awry in the EF code. (only compiler geeks would think to do that anyway)
So the disadvantage here is every time you have an expression in your LINQ that you want to use the Deprecated property, you have to create another static expression object.

Need to perform actions on EF objects with common fields, not sure if/how I need to use Interfaces

I'm using Entity Framework. I have a few database tables that store different statistics:
Stats1 (Stats1ID, Mean)
Stats2 (Stats2ID, Mean)
Stats3 (Stats3ID, Mean)
I have multiple methods which I want to consolidate into a single method. The only difference between these methods are the parameters:
public static bool IsValid(Stats1 stat, decimal value) { // }
public static bool IsValid(Stats2 stat, decimal value) { // }
// etc
The methods all use the common field of these different Stat objects - 'Mean'. How do I replace the first parameter with some generic object that I can use to access the Mean field of whichever type is passed in? Not sure if this is relevant but I use "database first" and generate the model like that
edit: appreciate the answers, will test things soon
All Stats classes can implement an interface, say IStat, containing the Mean property. It's enough to extend a generated partial class by another partial class:
partial class Stats1 : IStat { }
EF doesn't mind, as long as you don't use the interface for navigation property types (but you won't with database first).
Then you can define a generic method with a generic type constraint (where):
public static bool IsValid<T>(T stat, decimal value)
where T : IStat
{
// Example of what you could do here:
return stat.Mean > value);
}
Usage:
var valid = IsValid(stat1, 1);
EDIT - Didn't notice you're using Database First. I use this approach in code first, and it probably don't apply to you. But I leave it here in case anyone reads the question later.
You can define an interface that denoted the common field, like:
public interface IStatEntity
{
public int Mean { get; set; }
}
and implement the interface on all of the entities. Implementing an interface does not interfere with EF's mappings and doesn't mean anything to EF.
I use the same approach for having properties such as CreationTime and LastModificationTime and then setting them centrally in my DbContext.
You could use reflection to do what you want.
public static bool IsValid<TStats>(TStats stats, decimal value)
{
if (Equals(stats, null))
return false;
// Get the 'Mean' property
var propertyInfo = typeof(TStats).GetProperty("Mean");
if (Equals(propertyInfo, null))
return false;
// Get
var meanValue = propertyInfo.GetValue(stats, null) as decimal?;
// ... do what ever you want with the meanValue
return meanValue.HasValue && meanValue.Value == value;
}

Combining C# code and database code in a Specification

Sometimes you need to define some business rules and the Specification pattern is a useful tool. For example:
public class CanBorrowBooksSpec : ISpecification<Customer>
{
public bool Satisfies(Customer customer)
{
return customer.HasLibraryCard
&& !customer.UnpaidFines.Any();
}
}
However, I often find that I need to 'push' these rules into SQL to improve performance or to cater for things like paged lists of records.
I am then left with having to write code for the rules twice, once in CLR code, and once in SQL (or ORM language).
How do you go about organising code like this?
It seems best if the code was kept together in the same class. That way, if the developer is updating the business rules they have less chance of forgetting to update both sets of code. For example:
public class CanBorrowBooksSpec : ISpecification<Customer>
{
public bool Satisfies(Customer customer)
{
return customer.HasLibraryCard
&& !customer.UnpaidFines.Any();
}
public void AddSql(StringBuilder sql)
{
sql.Append(#"customer.HasLibraryCard
AND NOT EXISTS (SELECT Id FROM CustomerUnpaidFines WHERE CustomerId = customer.Id)");
}
}
However this seems quite ugly to me as we are now mixing concerns together.
Another alternative would be using a Linq-To-YourORM solution, as the LINQ code could either be run against a collection, or it could be translated into SQL. But I have found that such solutions are rarely possible in anything but the most trivial scenarios.
What do you do?
We used Specification pattern with Entity Framework. Here's how we approached it
public interface ISpecification<TEntity>
{
Expression<Func<TEntity, bool>> Predicate { get; }
}
public class CanBorrowBooksSpec : ISpecification<Customer>
{
Expression<Func<Customer, bool>> Predicate
{
get{ return customer => customer.HasLibraryCard
&& !customer.UnpaidFines.Any()}
}
}
Then you can use it against LINQ-to-Entities like
db.Customers.Where(canBorrowBooksSpec.Predicate);
In LINQ-to-Objects like
customerCollection.Where(canBorrowBooksSpec.Predicate.Compile());

cast Table<T> to something

I have a datacontext, and it has Authors table.
public partial Author:IProductTag{}
I want to cast Table<Authors> object to Table<IProductTag>, but that appears to be impossible. I am trying to do that because I want my method to be able to work with different tables which come as input parameters. To be more specific, I need to execute OrderBy and Select methods of the table. I have few other tables, entities of which implement IProductTag . Also, I tried to write a function like:
public static void MyF<t>(){
Table<t> t0 = (Table<t>)DataContext.GetMyTableUsingReflection();
}
But it fails at compile-time. And if I cast the table to something like ITable or IQueriable, then the OrderBy and Select functions simply don't work. So how do you deal with it?
I suspect you want to make your method generic - so instead of
void DoSomethingWithTable(Table<IProductTag> table)
you should have
void DoSomethingWithTable<T>(Table<T> table) where T : class, IProductTag
That should work fine, assuming you only need to read entities (and apply query operators etc). If that doesn't work for you, please give more details about what your method needs to do.
(You say that your attempt to use reflection failed, but you haven't said in what way it failed. Could you give more details?)
I have no idea what a ProductTag is so I've used different types to show my solution to this problem. Yes there doesn't seem to be a way to get a Table<T>, but you can get IQueryable<T> which works just as well (at least for my situation).
I have a simple analytics database, where each website has its own table containing both generic and specific items. I wanted to use an interface for the shared data.
public interface ISession
{
public DateTime CreateDt {get; set; }
public string HostAddress {get; set; }
public int SessionDuration {get; set; }
}
public static IQueryable<ISession> GetQueryableTable(MyDataContext db, string site)
{
Type itemType;
switch (item)
{
case "stackoverflow.com":
itemType = typeof(Analytics_StackOverflow);
break;
case "serverfault.com":
itemType = typeof(Analytics_ServerFault);
break;
default: throw Exception();
}
return db.GetTable(itemType).Cast<ISession>();
}
You can then do a query like this :
var table = GetQueryableTable(db, "stackoverflow.com");
var mySessions = table.Where(s => s.HostAddress == MY_IP);
To create a new row you can use reflection :
var rowType = typeof(Analytics_ServerFault);
var newRow = (ISession) rowType.GetConstructor(new Type[0]).Invoke(new object[0]);
(I have a function to get GetRowType - which is not shown here).
Then to insert into the table I have a separate helper function:
public static void Insert(MyDataContext db, ISession item)
{
// GetTable is defined by Linq2Sql
db.GetTable(GetRowType(domain)).InsertOnSubmit(item);
}

Categories