I'm trying to build a generic repository with Dapper. However, I have some difficulties to implement the CRUD-operations.
Here is some code from the repository:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal IDbConnection Connection
{
get
{
return new SqlConnection(ConfigurationManager.ConnectionStrings["SoundyDB"].ConnectionString);
}
}
public GenericRepository(string tableName)
{
_tableName = tableName;
}
public void Delete(TEntity entity)
{
using (IDbConnection cn = Connection)
{
cn.Open();
cn.Execute("DELETE FROM " + _tableName + " WHERE Id=#ID", new { ID = entity.Id });
}
}
}
As you can see, my delete-method takes a TEntity as parameter which is a parameter of type class.
I call my Delete-method from my UserRepository like this:
public class UserRepository : GenericRepository<User>, IUserRepository
{
private readonly IConnectionFactory _connectionFactory;
public UserRepository(IConnectionFactory connectionFactory) : base("User")
{
_connectionFactory = connectionFactory;
}
public async Task<User> Delete(User model)
{
var result = await Delete(model);
return result;
}
}
The thing is that I can't write entity.Id in my Delete-opration in my generic repository. I get a error. So how can I easily implement CRUD-operations like this?
Here is the error message:
TEntity does not contain a definition of "Id" and no extension method "Id" accepting a argument of type "TEntity" could be found
Define an interface like so.
public interface ITypeWithId {
int Id {get;}
}
And make sure your User type implements that interface.
Now apply it to your class as a generic constraint.
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class, ITypeWithId
If you have types that are stored in the repository but DO Not have an Id property then make your delete type constraint specific to the method and not the class. This will allow you to still use the same repository type even with types that might key on something else like a string or a compound (multi) key.
public void Delete<T>(T entity) where T : class, ITypeWithId
{
using (IDbConnection cn = Connection)
{
cn.Open();
cn.Execute("DELETE FROM " + _tableName + " WHERE Id=#ID", new { ID = entity.Id });
}
}
Please don't do this! Your generic repository adds more confusion than value. It's fragile code (string literals for _tableName, invalid cast errors on the id parameter), and introduces a gaping security hole (sql injection via _tableName). If you've chosen Dapper, it's because you want to be in control of your sql, so it makes no sense to generate the sql you send to Dapper.
you have to define an interface like below
public interface IIdentityEntity
{
public int Id { get; set;}
}
all your entities which want to use the class, must implement the IIdentityEntity.
and the first line should be changed to the following
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class,IIdentityEntity
and what was the problem is that you only described the TEntity as class and class does not have an Id in its description so you have to notify compiler that the Generic type implemented an Interface that holds an Id field inside it
In case it helps, I've just published a library Harbin.DataAccess which implements Generic Repositories (Generic Repository Pattern) using "raw" Dapper, Dapper.FastCRUD, and DapperQueryBuilder:
The Inserts/Updates/Deletes are automatically generated by Dapper FastCRUD (class should be decorated with attributes for keys/autoincrement columns)
Supports FastCRUD bulk update, bulk delete, and async methods.
Repositories can be extended with custom Queries and custom Commands (allows/promotes CQRS separation)
Queries can be defined manually (raw sql) or using Dapper FastCRUD syntax
Dynamic Queries (dynamic number of conditions) can be built using DapperQueryBuilder
There are Read-only Connection Wrappers and Read-only Repositories, so it's easy to use read-replicas (or multiple databases)
Support for ADO.NET transactions
Support for mocking Queries and Commands
Sample Insert/Update/Delete (Generic Repository - this uses Dapper FastCRUD):
var conn = new ReadWriteDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
// Get a IReadWriteRepository<TEntity> which offers some helpers to Query and Write our table:
var repo = conn.GetReadWriteRepository<ContactType>();
var contactType = repo.QueryAll().First();
// Updating a record
contactType.ModifiedDate = DateTime.Now;
repo.Update(contactType);
// Adding a new record
var newContactType = new ContactType() { Name = "NewType", ModifiedDate = DateTime.Now };
repo.Insert(newContactType);
// FastCRUD will automatically update the auto-generated columns back (identity or guid)
// Deleting a record
repo.Delete(newContactType);
[Table("ContactType", Schema = "Person")]
public class ContactType
{
[Key] // if column is part of primary key
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // if column is auto-increment
public int ContactTypeId { get; set; }
public DateTime ModifiedDate { get; set; }
public string Name { get; set; }
}
Sample Dynamic Queries:
var conn = new ReadDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
// Get a IReadRepository<TEntity> which offers some helpers to Query our table:
var repo = conn.GetReadRepository<Person>();
// Custom Query (pure Dapper)
var people = repo.Query("SELECT * FROM Person.Person WHERE PersonType = #personType ", new { personType = "EM" } );
// DapperQueryBuilder allows to dynamically append conditions using string interpolation (but injection-safe)
string type = "EM"; string search = "%Sales%";
var dynamicQuery = repo.QueryBuilder(); // if not specified query is initialized with "SELECT * FROM tablename"
dynamicQuery.Where($"PersonType = {type}");
dynamicQuery.Where($"ModifiedDate >= {DateTime.Now.AddDays(-1)} ");
dynamicQuery.Where($"Name LIKE {search}");
// Result is SELECT * FROM [Person].[Person] WHERE PersonType = #p0 AND ModifiedDate >= #p1 AND Name LIKE #p2
var people = dynamicQuery.Query();
Extending Repositories (adding custom Queries and Commands) using Inheritance:
public class PersonRepository : ReadWriteDbRepository<Person>
{
public PersonRepository(IReadWriteDbConnection db) : base(db)
{
}
public virtual IEnumerable<Person> QueryRecentEmployees()
{
return this.Query("SELECT TOP 10 * FROM [Person].[Person] WHERE [PersonType]='EM' ORDER BY [ModifiedDate] DESC");
}
public virtual void UpdateCustomers()
{
this.Execute("UPDATE [Person].[Person] SET [FirstName]='Rick' WHERE [PersonType]='EM' ");
}
}
public void Sample()
{
// Registers that GetReadWriteRepository<Person>() should return a derived type PersonRepository
ReadWriteDbConnection.RegisterRepositoryType<Person, PersonRepository>();
var conn = new ReadWriteDbConnection(new System.Data.SqlClient.SqlConnection(connectionString));
// we know exactly what subtype to expect, so we can just cast.
var repo = (PersonRepository) conn.GetReadWriteRepository<Person>();
repo.UpdateCustomers();
var recentEmployees = repo.QueryRecentEmployees();
}
Full documentation here.
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 3 days ago.
This post was edited and submitted for review 3 days ago.
Improve this question
How can you call a stored procedure using Entity Framework Core and return the results using a generic class?
In .NET Standard Entity Framework, I'm able to call stored procedures and return generic like so:
public IEnumerable<T> ExecuteStoredProcedure<T>(object[] sqlParameters) where T : IStoredProcedure, new()
{
if (sqlParameters == null) sqlParameters = new object[] { };
return DataContext.Database.SqlQuery<T>((new T()).Query, sqlParameters).ToList();
}
This method is not available in the same fashion in Entity Framework Core anymore...
Assuming you're using Code First and are comfortable with the steps necessary to create the actual stored procedure and have that defined in your database, I will focus my answer on how to call that stored procedure in C# and map the results generically.
First you want to create a model that matches the data you expect to get back from your results.
Here is an example:
public class UserTimesheet : IStoredProcedure
{
public string Query => "[dbo].[GetUserTimesheet] #userId, #month, #year";
public DateTime WorkDate { get; set; }
public Guid ProjectId { get; set; }
public int CategoryId { get; set; }
public string? ProjectName { get; set; }
public decimal? Hours { get; set; }
}
Notice this extends an interface called IStoredProcedure with the Query property. More on that later, but it's there to work by convention.
Here is that interface:
public interface IStoredProcedure
{
string Query { get; }
}
Next you'll want to add a DbSet to your database context.
// Put this in your Database Context
public DbSet<UserTimesheet> UserTimesheets { get; set; } = null!;
Now since this doesn't map to an actual table, you will want to add some code to the OnModelCreating to tell EF how to reference it. Again, I'm working on a convention, in this case I only want to apply this setting to models that implement IStoredProcedure, and we can do that with a little reflection to make life easier.
In this case we're going to say it has no key and treat it like a view. I created an extension method to keep things a little cleaner, you can use it like this:
public static class ModelBuilderExtension
{
public static ModelBuilder ConfigureStoredProcedureDbSets(this ModelBuilder modelBuilder)
{
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (typeof(IStoredProcedure).IsAssignableFrom(entityType.ClrType))
{
modelBuilder.Entity(entityType.ClrType).HasNoKey().ToView(null);
}
}
return modelBuilder;
}
}
// Put this in DatabaseContext class
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ConfigureStoredProcedureDbSets();
}
Next you'll want something to hold your generic code. I use a class called StoredProcedureRepository, but you can call it what you like. Here's the code, including the interface (for Dependency Injection, if you like):
public abstract class Repository
{
protected readonly DatabaseContext _context;
protected Repository(DatabaseContext context)
{
_context = context;
}
}
public interface IStoredProcedureRepository
{
IEnumerable<T> ExecuteStoredProcedure<T>(object[] sqlParameters) where T : class, IStoredProcedure, new();
SqlParameter GetSqlParameter(string name, object value, bool isOutput = false);
}
public class StoredProcedureRepository : Repository, IStoredProcedureRepository
{
#region Properties
private const string SQL_PARAMETER_PREFIX = "#";
#endregion
#region Constructor
public StoredProcedureRepository(DatabaseContext context) : base(context)
{
}
#endregion
#region Shared Public Methods
public IEnumerable<T> ExecuteStoredProcedure<T>(object[] sqlParameters) where T : class, IStoredProcedure, new()
{
return _context.Set<T>().FromSqlRaw<T>((new T()).Query, sqlParameters).ToList();
}
public SqlParameter GetSqlParameter(string name, object value, bool isOutput = false)
{
if (!name.StartsWith(SQL_PARAMETER_PREFIX))
{
name += SQL_PARAMETER_PREFIX;
}
var direction = isOutput ? System.Data.ParameterDirection.Output : System.Data.ParameterDirection.Input;
return new SqlParameter
{
ParameterName = name,
Value = value,
Direction = direction
};
}
#endregion
}
There are a few things to notice here. It's referencing the DbSet generically and using FromRawSql and calling the Query (string) property from the IStoredProcedure implementation of your model. So you'll want to make sure that contains your query to execute the stored procedure, in this example that would be "[dbo].[GetUserTimesheet] #userId, #month, #year"
Now you can call this stored procedure generically.
Here is an example:
var parameters = new object[3];
parameters[0] = GetSqlParameter("#userId", userId);
parameters[1] = GetSqlParameter("#month", month);
parameters[2] = GetSqlParameter("#year", year);
IList<UserTimesheet> queryResults = _storedProcedureRepository.ExecuteStoredProcedure<UserTimesheet>(parameters).ToList();
To add new stored procedures, just create their respective models (being sure to implement IStoredProcedure and define their Query property, then add their DbSet to the database context.
For example:
public class UserProject : IStoredProcedure
{
public string Query => "[dbo].[GetUserProjects] #userId";
public Guid ProjectId { get; set; }
public string ProjectName { get; set; }
}
// add this to the database context
public DbSet<UserProject> UserProjects { get; set; } = null!;
then call it like so:
var parameters = new object[1];
parameters[0] = GetSqlParameter("#userId", userId);
IList<UserProject> queryResults = _storedProcedureRepository.ExecuteStoredProcedure<UserProject>(parameters).ToList();
Maybe this is already solved somewhere but I canĀ“t find a solution...
I work with EF Core and I want to get rid of lazyLoading. I want to write a generic method, which includes different NavigationProperties where needed. I have an Interface which defines crud-methods. I use AutoMapper to map the recieved entities to viewModels. I have different groups of entities. Some can be retrieved without any Navigations, others need specific navigationProperties included.
Actually I want to use one Base-Service-Interface and choose the right implementation during runtime. Or some conditional decision (as described below) in the Service-Implementation.
This is my Service:
public interface IService
{
Task<IEnumerable<TViewModel> GetAsync<TEntity, TViewModel>()
where TEntity : class
where TViewModel : class;
Task PostAsync<TEntity, TViewModel>(TViewModel model)
where TEntity : class
where TViewModel : class;
Task<TViewModel> PatchAsync<TEntity, TViewModel>(int id)
where TEntity : class;
}
This is my Navigation-Interface:
public interface INavigate
{
int NavigationTypeId {get;set;}
NavigationType NavigationType {get;set;}
}
This is my service-implementation:
public class Service
{
public async Task PatchAsync<TEntity, TViewModel>(Guid id,
JsonPatchDocument<TViewModel> patchDoc)
where TEntity : class
where TViewModel : class
{
var query = this.dbContext.Set<TEntity>();
if(typeof(INavigate).IsAssignableFrom(typeof(TEntity)))
{
query = this.dbContext.Set<TEntity>()
.Include(e=>e.NavigationType); // obviously this won't work
}
var entity = await query.FirstAsync();
var viewModel = this.mapper.Map<TViewModel>(entity);
patchDocument.ApplyTo(viewModel);
var updatedEntity = this.mapper.Map(viewModel, entity);
this.dbContext.Entry(entity).CurrentValues.SetValues(updatedEntity);
this.dbContext.SaveChangesAsync();
}
}
So... This isn't a solution. I think there must be some way to solve this problem somehow without generating different services for each Navigation-Interface and specific where-clauses (where TEntity : class, INavigate) - but I don't know where to go from here.
If someone else is looking for a solution I'll post the implementation I ended up with. Thanks to Svyatoslav Danyliv who pointed me to this direction. If someone has a better solution I'd highly appreciate (and accept) an answer.
Here are other questions, that helped me: Include with reflection, ThenInclude and ProjectTo.
I use Automapper and have created a bidirectional Map for every entity/viewModel pair. I have different groups of entities. They either have different navigationProperties which are part of the viewModel or navigationProperties which aren't needed during crud or no navigationProperties at all. I use different interfaces for those navigations (such as INavigate). Some navigations have more than one level to include:
public interface ITopLevelNavigation
{
int TopLevelNavigationId { get; set; }
TopLevelNavigation TopLevelNavigation { get; set; }
}
public class TopLevelNavigation : INavigate
{
public int Id { get; set; }
public int NavigationTypeId { get; set; }
public NavigationType NavigationType { get; set; }
}
And I have entities which have a required relationship:
public interface IRequireDependency
{
int DependencyId { get; set; }
RequiredDependency Dependency { get; set; }
}
I want to assure (for insert and update), that these dependencies exist to avoid an ef-core exception (ForeignKey-Violation) and being able to respond with an accurate and understandable feedback.
Based on the comments for my question, the easiest case is Get:
public Task<IEnumerable<TViewModel> GetAsync<TEntity, TViewModel>()
where TEntity : class
{
return await this.mapper.ProjectTo<TViewModel>(this.dbContext.Set<TEntity>())
.ToListAsync();
}
This works perfectly fine. For generic post I only need to validate required dependencies, everything else can be done with Automapper:
public async Task PostAsync<TEntity, TViewModel>(TViewModel model)
where TEntity : class
where TViewModel : class
{
var entity = this.mapper.Map<TEntity>(model);
if (entity is IRequireDependency dependency)
{
if(!await this.dbContext.RequiredDependency
.AnyAsync(e => e.Id == dependency.DependencyId))
throw new Exception("Invalid DependencyId");
}
await this.dbContext.Set<TEntity>().AddAsync(entity);
await this.dbContext.SaveChangesAsync();
}
To solve the include-issue I have to use the string-overload. I wrote an extension-method:
public static IQueryable<T> IncludeByInterface<T>(this IQueryable<T> queryable)
where T : class
{
if (typeof(INavigate).IsAssignableFrom(typeof(T)))
{
queryable = queryable.Include(nameof(INavigate.NavigationType));
}
if (typeof(ITopLevelNavigation).IsAssignableFrom(typeof(T)))
{
queryable = queryable.Include(nameof(ITopLevelNavigation.TopLevelNavigation));
queryable = queryable.Include($"{nameof(ITopLevelNavigation.TopLevelNavigation)}.
{nameof(ITopLevelNavigation.Navigation.NavigationType)}");
}
return queryable;
}
Now I can patch it like this:
public async Task Patch<TEntity, TViewModel>(int id, JsonPatchDocument<TViewModel> doc)
where TEntity : class
where TViewModel : class
{
var entity = await this.dbContext.Set<TEntity>().AsQueryable()
.IncludeByInterface().FirstAsync();
var viewModel = this.mapper.Map<TViewModel>(entity);
patchDocument.ApplyTo(viewModel);
var updatedEntity = this.mapper.Map(viewModel, entity);
this.dbContext.Entry(entity).CurrentValues.SetValues(updatedEntity);
await this.dbContext.SaveChangesAsync();
}
I hope this helps if someone is facing similar issues. And again: If you think this can be done better - please let me know.
I am trying to delete an entity of Employee from the database which contains different tables like Employee, Project, Skills using a generic repository pattern.
namespace Information.Repository
{
public class IRepositoy<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly ApplicationDbContext _dbContext;
public IRepositoy(ApplicationDbContext dbContext)
{
_dbContext = dbContext;
}
public void Remove(int id)
{
TEntity element = _dbContext.Set<TEntity>().Find(id);
_dbContext.Set<TEntity>().Remove(element);
}
}
}
When the above Remove method is called it makes two database call
One for getting the entity.
Second for deleting it.
I have found the query like the below one which executes with single SQL query
when the entity type(Employee or Project or Skill) is known
public void Remove(int id)
{
Employee employee = new Employee { EmployeeId = id };
_dbContext.Entry(employee).State = EntityState.Deleted;
}
can anyone please suggest me how to delete an entity without fetching it using a generic repository pattern similar to the above example.
Using raw SQL
Entity Framework doesn't allow you to delete an object that you haven't loaded (or attached). This also extends to conditional deletes (e.g. deleting all users named John) as it requires you to load the users before deleting them.
You can get around this by executing raw SQL. It's not ideal as you tend to use EF so you don't have to write SQL, but the lack of a decent delete behavior (without loading) makes this an acceptable solution.
Something along the lines of:
using (var context = new FooContext())
{
var command = "DELETE * FROM dbo.Foos WHERE Id = 1";
context
.Database
.ExecuteSqlCommand(command);
}
Where relevant, don't forget about SQL injection protection. However, it's usually a non-issue for simple deletes as the FK is usually a GUID or int, which doesn't expose you to injection attacks.
Making it generic
The example you posted works as well, but you're probably not using it because it can't easily be made generic-friendly.
What I tend to do in all my EF projects is to have an (abstract) base class for all my entities, something along the lines of:
public class BaseEntity
{
public int Id { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
public DateTime? UpdatedOn { get; set; }
public string UpdatedBy { get; set; }
}
An interface would also work, I just prefer a base class here.
The audit fields are not part of this answer but they do showcase the benefits of having a base class.
When all your entities inherit from the same base class, you can put a generic type constraint on your repositories which ensures that the generic type has an Id property:
public class IRepositoy<TEntity> : IRepository<TEntity> where TEntity : BaseEntity
At which point you can generically implement your proposed solution:
public void Remove(TEntity obj)
{
dbContext.Entry(obj).State = EntityState.Deleted;
}
You can also specify a parameterless constructor type constraint:
where TEntity : BaseEntity, new()
which enables you to instantiate your generic type as well:
public void Remove(int id)
{
TEntity obj = new TEntity() { Id = id };
dbContext.Entry(obj).State = EntityState.Deleted;
}
Note
There is a generic raw SQL solution as well, but I've omitted it as it is more complex because it requires you to retrieve the table name based on the entity type.
The raw SQL variant is only valuable in cases where you want to execute conditional deletes (e.g. removing all entities whose id is an even number).
However, since most conditional deletes are entity-specific, this means that you generally don't need to make them generic, which makes the raw SQL approach more viable as you only have to implement it in a specific repository and not the generic one.
You still have to fetch it. Entity Framework caches your dbSets so it's usually pretty quick. Use the same context like so:
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
Where dbSet =
context.Set<TEntity>();
The current limitation of Entity Framework is, in order to update or delete an entity you have to first retrieve it into memory. However there are few alternatives to delete a specific record.
You can try ExecuteSqlCommandto delete a specific record
_dbContext.Database.ExecuteSqlCommand("Delete Employee where EmployeeId = {0}", id );
or try using EntityFramework.Extended Library to delete a specific record
_dbContext.Settings.Where(s=> s.EmployeeId == id).Delete();
In our WebApi project we use EF CodeFirst approach. Also we use 2 types of databases: SQL Server and MySQL. All tables have the field ID, but in SQL Server database this field has int data type, in MySQL database this field is char(36) and contains GUID.
To solve the problem I created a custom value type like IdType and changed all model classes to use that type insted int:
public class Document
{
public IdType ID { get; set; }
public string DocumentNm { get; set; }
...
}
Then I configured the DbContext (e.g for SQL Server)
modelBuilder.Properties<IdType>().Configure(c => c.HasColumnType("int"));
...and changed repository:
public interface IRepository<T> where T : IEntity
{
IQueryable<T> GetAll();
T GetById(IdType id);
...
}
After that, when I try to go to e.g. http://localhost:7081/api/Document, it gives me an error:
Multiple actions were found that match the request: \r\nGet on type
WebUI.Controllers.API.DocumentController\r\nGetById on type
WebUI.Controllers.API.DocumentController
I use default settings of routing. Here is [HttpGet] methods from DocumentController:
public HttpResponseMessage Get() { ... }
public HttpResponseMessage GetById(IdType id) { ... }
How can I solve the problem? Could this be the cause of incorrect implementation of IdType?
P.S. I created IdType for int values as described here. if I have to add more informations, please let me know.
UPDATE
DocumentController:
public HttpResponseMessage GetById(IdType id)
{
var entity = repository.GetById(id);
if (entity == null)
{
return ErrorMsg(HttpStatusCode.NotFound, string.Format("No {0} with ID = {1}", GenericTypeName, id););
}
return Request.CreateResponse(HttpStatusCode.OK, entity);
}
My repository:
public virtual T GetById(IdType id)
{
return GetAll().FirstOrDefault(x => x.ID == id);
}
public virtual IQueryable<T> GetAll()
{
return entities = context.Set<T>();
}
It seems that it not implemented yet in current version of Entity Framework
And as mentioned in task on GitHub
we're currently planning to work on lighting this feature up after our
initial RTM of EF7.
I am trying to create a completely dynamic way to query entity framework using entity sql, where the type T in ObjectQuery (the table name, or entity name) is not known at compile time, and is passed into a method as a string,
public class EntityQuery
{
public ObjectContext Context { get; private set; }
public string TableName { get; private set; }
public EntityQuery(ObjectContext context, string sourceObject)
{
MemberInfo[] mInfo = context.GetType().GetMembers();
TableName = sourceObject;
Context = context;
if (!mInfo.Any<MemberInfo>(MemberInfo => MemberInfo.Name == TableName))
throw new EntityOperationException("Entity '" + TableName + "' does not exist in the object context.");
}
public ObjectQuery<dynamic> InitiateQuery(List<EntityFilter> filters)
{
string predicate = DynamicESQLBuilder.GetESQL(filters, Context.DefaultContainerName, TableName);
string format = String.Format("[{0}]", predicate);
ObjectQuery<dynamic> query = new ObjectQuery<dynamic>(predicate, Context, MergeOption.NoTracking);
return query;
}
}
The class DynamicESQLBuilder creates an entity sql command string based on the context name, table name, and entity filters passed in. The 'EntityFilter' class essentially encapsulates a single 'where' clause,
public class EntityFilter
{
public ExpressionType OpType { get; private set; }
public string PropertyName { get; private set; }
public object Value { get; private set; }
}
When I try to use it like this,
MyDBModel db = new MyDBModel();
List<EntityFilter> filters = new List<EntityFilter>()
{
new EntityFilter("Name", ExpressionType.Equal, "Sean")
};
EntityQuery query = new EntityQuery(db, "Person");
var results = query.InitiateQuery(filters);
I get an error stating:
'Name' is not a member of type 'MyDBModel.Person' in the currently loaded schemas. Near simple identifier, line 1, column 74.
I am open to an entirely different way of doing this. But basically what I am trying to achieve is similar to what WCF OData services do behind the scenes -- I can query my model with just strings passed in - the name of the entity, the operation, the value, etc. No need for a strongly typed 'T' in my query.
If you have Entity SQL as a string and you don't mind doing things the old-school ADO.NET way, you can always use EntityConnection, EntityCommand, and EntityDataReader directly.