Entity framework Core Raw SQLQueries with custom model - asp.net-core

Using Entity Framework 6, I was able to use execute a Raw SQL Query and use a custom model which was not defined in the DBContext in order to store the output of the query. A simple example is the following:
List<MyModel> data = context.Database.SqlQuery<MyModel>("SELECT Orders.OrderID, Customers.CustomerName FROM Orders INNER JOIN Customers ON Orders.CustomerID=Customers.CustomerID;").ToList();
I execute one SQL command and I expect a list of custom models.
I try to do something similar with Entity Framework Core and the closest example that I found will force me to define a property from DBContext. This will not allow me to use a custom model to fill the data that SQL server will return.
var books = context.Books.FromSql("SELECT * FROM Books").ToList();
This query informs Entity Framework Core that the query will return a list of books. Is there a way to implement something like this in Entity Framework Core?

From .NET Core 2.1:
Add modelBuilder.Query<YourModel>() to OnModelCreating(ModelBuilder modelBuilder)
Use context.Query<YourModel>().FromSql(rawSql) to get data

Here's how I was able to get this working (for completeness):
MyModel.cs:
public class MyModel
{
// The columns your SQL will return
public double? A { get; set; }
public double? B { get; set; }
}
Add class that just inherits from your original EF context class (i called mine DbContextBase):
public class DbContext : DbContextBase
{
public virtual DbSet<MyModel> MyModels { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Necessary, since our model isnt a EF model
modelBuilder.Entity<MyModel>(entity =>
{
entity.HasNoKey();
});
}
}
Use that class (instead of your original EF context class):
// Use your new db subclass
using (var db = new DbContext())
{
var models = await db.MyModels.FromSqlRaw(...).ToListAsync(); // E.g.: "SELECT * FROM apple A JOIN banana B ON A.col = B.col"
}
Notes:
If you need to, just use FromSqlInterpolated instead of
FromSqlRaw
The "db context" subclass allows you to update EF models without affecting your "polyfill" code
Works with SQL Server stored procs that return only 1 result set

The question was about .NET Core 2. Now I have a solution and I am going to write it here so that someone else could use it in case he/she needs it.
First of all we add the following method in dbContext class
public List<T> ExecSQL<T>(string query)
{
using (var command = Database.GetDbConnection().CreateCommand())
{
command.CommandText = query;
command.CommandType = CommandType.Text;
Database.OpenConnection();
List<T> list = new List<T>();
using (var result = command.ExecuteReader())
{
T obj = default(T);
while (result.Read())
{
obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties())
{
if (!object.Equals(result[prop.Name], DBNull.Value))
{
prop.SetValue(obj, result[prop.Name], null);
}
}
list.Add(obj);
}
}
Database.CloseConnection();
return list;
}
}
Now we can have the following code.
List<Customer> Customers = _context.ExecSQL<Customer>("SELECT ......");

follow these steps:
Create your model
Probably it could be better if you can reduce it to a model as generic as possible but it's not a must:
public class MyCustomModel
{
public string Text { get; set; }
public int Count { get; set; }
}
Add it to your own DbContext
Create DbSet for your custom model
public virtual DbSet<MyCustomModel> MyCustomModelName { get; set; }
Keep in mind to specify your custom model has no key
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
...
modelBuilder.Entity<MyCustomModel>().HasNoKey();
}
Use it from your dbContext instance
async public Task<List<MyCustomModel>> GetMyCustomData()
{
var rv = new List<MyCustomModel>();
using (var dataContext = new DbContext())
{
var sql = #"
select textField as 'Text', count(1) as 'Count'
from MyTable";
rv = await dataContext.Set<MyCustomModel>().FromSqlRaw(sql).ToListAsync();
}
return rv;
}

Related

Ignore properties in data model while keeping them in EF Core migrations and Database Tabel

I have properties in my Model like
public class Test{
public int Id{ get; set; }
public string Title { get; set; }
public DateTime? CreatedDate { get; set; }
public DateTime? ModifiedDate { get; set; }
public DateTime? DeletedDate { get; set; }
}
Here, I am using this Test class for creating Table in Database using Migration,
Now the table is created successfully but the problem is when i want do any operation using stored procedure which is like " Select Title from Test where Id=1" ,When i run the this i am facing error like this
"The required column 'CreatedDate' was not present in the results of a
'FromSql' operation"
I have used
NotMapped Attribute it works fine but when i add another migration the NotMapped properties gets Dropped from the database after updating the database
Also use Shadow properties and Ignore properties like
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Test>().Property<DateTime?>("CreatedDate");
modelBuilder.Entity<Test>().Property<DateTime?>("ModifiedDate");
modelBuilder.Entity<Test>().Property<DateTime?>("DeletedDate");
}
Also try this,
protected override void OnModelCreating(ModelBuilder modelBuilder) {
modelBuilder.Entity<Test>().Ignore(x => x.DeletedDate);
modelBuilder.Entity<Test>().Ignore(x => x.IsDeleted);
modelBuilder.Entity<Test>().Ignore(x => x.ModifiedDate); }
But the issue remains the same ,
So the issue is i want to ignore the CreateDate, ModifiedDated, DeletedDated property while performing DB operation and also not want to drop these columns from Database when i add and update new migration.
"The required column 'CreatedDate' was not present in the results of a
'FromSql' operation"
The first thing you need to know is that the root problem of this error is not your CreatedDate field, but the type you return after executing FromSql.
When you execute FromSql, the return type is Test, and the Test type contains all fields(Id,Title,CreatedDate...), but your stored procedure only selects the Title field,therefore, the received type does not match, and this error occurs.
You can solve this problem from two methods.
The first method is to change the stored procedure to return data consistent with the Test type.
Select * from Test where Id=1
The other method changes from the perspective of receiving types.
You can customize the FromSql method to make the returned type dynamic.
public static class CustomFromSqlTest
{
public static IEnumerable<dynamic> FromSql(this DbContext dbContext, string Sql, Dictionary<string, object> Parameters)
{
using (var cmd = dbContext.Database.GetDbConnection().CreateCommand())
{
cmd.CommandText = Sql;
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
foreach (KeyValuePair<string, object> param in Parameters)
{
DbParameter dbParameter = cmd.CreateParameter();
dbParameter.ParameterName = param.Key;
dbParameter.Value = param.Value;
cmd.Parameters.Add(dbParameter);
}
//var retObject = new List<dynamic>();
using (var dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
var dataRow = GetDataRow(dataReader);
yield return dataRow;
}
}
}
}
private static dynamic GetDataRow(DbDataReader dataReader)
{
var dataRow = new ExpandoObject() as IDictionary<string, object>;
for (var fieldCount = 0; fieldCount < dataReader.FieldCount; fieldCount++)
dataRow.Add(dataReader.GetName(fieldCount), dataReader[fieldCount]);
return dataRow;
}
}
Use it:
var result = _context.FromSql("spName #Id", new Dictionary<string, object> { { "#Id", 1 } }).ToList();

EFPlus BulkInsert - How to get DB-generated IDs

Using MSSQL with IDENTITY column for IDs,
how can I get entity IDs synchronized with table IDs after calling BulkInsert?
context.BulkInsert(entities);
Neither of both achieves the requested result:
context.BulkSynchronize(entities);
context.BulkMerge(entities);
Assume we have one entity
var newSomething = new Something { Id = 0 };
and the corresponding TSQL table column definition
ID int IDENTITY(1,1)
Entity Framework automatically sets Id after calling SaveChanges()
context.SomethingSet.Add(newSomething);
context.SaveChanges();
Assert.IsTrue(newSomething.Id != 0)
See also How can I get Id of inserted entity in Entity framework?
How does EFPlus provide a way of getting the Id of inserted entities?
Disclaimer: I'm the owner of the project Entity Framework Extensions
The Entity Framework Extensions library should by default already return the ids for inserted entities.
For example, the following code should already work and return ids when using with BulkInsert.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Windows.Forms;
namespace Z.EntityFramework.Extensions.Lab
{
public partial class Form_Request_Ids : Form
{
public Form_Request_DateNull()
{
InitializeComponent();
// CLEAR
using (var ctx = new CurrentContext())
{
ctx.EntitySimples.RemoveRange(ctx.EntitySimples);
ctx.SaveChanges();
}
// TEST
using (var ctx = new CurrentContext())
{
var list = new List<EntitySimple>();
list.Add(new EntitySimple() { Id = 0, IntColumn = 1, CreatedDate = DateTime.Now });
ctx.BulkInsert(list);
}
}
public class CurrentContext : DbContext
{
public CurrentContext()
: base("CodeFirstEntities")
{
}
public DbSet<EntitySimple> EntitySimples { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Types().Configure(x => x.ToTable(GetType().DeclaringType != null ? GetType().DeclaringType.FullName.Replace(".", "_") + "_" + x.ClrType.Name : ""));
base.OnModelCreating(modelBuilder);
}
}
public class EntitySimple
{
public int Id { get; set; }
public int IntColumn { get; set; }
public DateTime CreatedDate { get; set; }
}
}
}
If you still have the issue, try to contact us directly with an example info#zzzprojects.com or post your example here.

Best Practice with MVC4 and EF5 to apply changes

I have a CustomerOrder-view where I would like to change an existing CustomerOrder.
I have a viewmodel that very simpliefied looks something like this:
public class CustomerOrderViewModel
{
public int ID { get; set; }
public Customer Customer { get; set; }
public DateTime Date { get; set; }
public IEnumerable<OrderRow> OrderRows { get; set; }
}
public class OrderRow
{
public int id { get; set; }
public int price { get; set; }
}
public class Customer
{
public string Name { get; set; }
}
I also have a database with mapping tables / fields.
In my GET Action Method I load the Order with the help of Automapper like this:
var customerOrder = using (var ctx = new My_Entities()) {
return ctx.CustomerOrders.
Include("Orderrows").
Include("Customer").
Single(o => o.CustomerOrderID == id);
}
var model= AutoMapper.Mapper.DynamicMap<DataAccessLayer.CustomerOrder, CustomerOrderViewModel>(customerOrder);
In the View I use Knockout to bind to a viewmodel there, where the user can update the CustomerOrder. That includes editing Customer information and adding new orderrows etc.
Then in the post back a map the ViewModel back to the ObjectContext CustomerOrder:
var customerOrderToBeSaved =
AutoMapper.Mapper.DynamicMap<CustomerOrderViewModel, CustomerOrder>(
customerOrderViewModel);
try
{
using (var ctx = new MyEntities())
{
ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.CustomerOrders.ApplyCurrentValues(customerOrderToBeSaved);
...
ctx.SaveChanges();
}
}
I get the error message: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
OK, that I can understand. But how should I go about this? Can I get the existing object and apply Changes to that one, because that is really what I'd like. I've tried to look up the old one and detach it but I haven't got it to wrok.Perhaps I'm doing this in a completely wrong way. Please advice.
You should not attach customerOrderToBeSaved, see MSDN about the argument of ApplyCurrentValues.
The detached object that has property updates to apply to the original object.
So you've got to load the entity from the database into the context and then ApplyCurrentValues with the detached object that has the new values.
You don't have to load the row from the database to update it.
You can do something like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
ctx.Entry( entity ).State = EntityState.Modified;
ctx.SaveChanges();
This will tell EF to issue an UPDATE SQL statement that overwrites all the columns in the record.
You can select which columns you want to update like this:
var entity = ctx.CustomerOrders.Attach(customerOrderToBeSaved);
var entry = ctx.Entry( entity );
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
entry.Property( o => o.<ColumnPropertyToUpdate> ).IsModified = true;
...
ctx.SaveChanges();
If you do this, EF will only include the columns you've marked as modified in the UPDATE statement.

Does WCF OData Service Support Projection?

I'm using WCF OData service as my application Data Provider.OData service expose a entity that I don't want to get whole entity,I create LINQ query to get projection from this Entity.
But i have error in OData Service.This is my code:
from n in NewsInfos
select new NewsInfos
{
n.NewsId,
n.NewsTitle,
n.NewsLead,
n.NewsDate
};
This is entire code:
[System.ServiceModel.ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class NewsDataService : DataService<NewsODataModel>
{
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.DataServiceBehavior.AcceptProjectionRequests = true;
}
}
Yes, WCF Data Services and OData support projection. Projection is codified in the URL with the $select system query option, e.g.: http://services.odata.org/Experimental/OData/OData.svc/Products?$select=Name&$format=json. The LINQ Provider in the client bits enable this similarly to what you've shown in your example. Here is one such example:
using System;
using System.Data.Services.Client;
using System.Linq;
namespace Scratch
{
public class Program
{
public static void Main()
{
var context = new DataServiceContext(new Uri("http://services.odata.org/OData/OData.svc/"));
var categories = context.CreateQuery<Category>("Categories").Select(c => new { c.Name });
Console.WriteLine("context.Categories.Where(...): {0}", categories);
foreach (var category in categories)
{
Console.WriteLine(category.Name);
}
}
}
public class Category
{
public int ID { get; set; }
public string Name { get; set; }
}
}
One thing to consider with projection is that the magic in our client-side bits frequently requires you to use anonymous objects (hence the new { c.Name }).
Your error may be unrelated; if you're still getting the error after reading this can you update your service to return verbose errors as per http://blogs.msdn.com/b/phaniraj/archive/2008/06/18/debugging-ado-net-data-services.aspx? My guess is that you may be missing the [DataServiceKey] attribute on NewsInfos.
Just return an anonymous object from your select and it should work.
from n in NewsInfos
select new
{
n.NewsId,
n.NewsTitle,
n.NewsLead,
n.NewsDate
};

Implementing a flexible searching infrastructure using nHibernate

My aim is to implement a quite generic search mechanism. Here's the general idea:
you can search based on any property of the entity you're searching for (for example- by Employee's salary, or by Department name etc.).
Each property you can search by is represented by a class, which inherits from EntityProperty:
public abstract class EntityProperty<T>
where T:Entity
{
public enum Operator
{
In,
NotIn,
}
/// <summary>
/// Name of the property
/// </summary>
public abstract string Name { get; }
//Add a search term to the given query, using the given values
public abstract IQueryable<T> AddSearchTerm(IQueryable<T> query, IEnumerable<object> values);
public abstract IQueryable<T> AddSortingTerm(IQueryable<T> query);
protected Operator _operator = Operator.In;
protected bool _sortAscending = false;
public EntityProperty(Operator op)
{
_operator = op;
}
//use this c'tor if you're using the property for sorting only
public EntityProperty(bool sortAscending)
{
_sortAscending = sortAscending;
}
}
all of the properties you're searching / sorting by are stored in a simple collection class:
public class SearchParametersCollection<T>
where T: Entity
{
public IDictionary<EntityProperty<T>,IEnumerable<object>> SearchProperties { get; private set; }
public IList<EntityProperty<T>> SortProperties { get; private set; }
public SearchParametersCollection()
{
SearchProperties = new Dictionary<EntityProperty<T>, IEnumerable<object>>();
SortProperties = new List<EntityProperty<T>>();
}
public void AddSearchProperty(EntityProperty<T> property, IEnumerable<object> values)
{
SearchProperties.Add(property, values);
}
public void AddSortProperty(EntityProperty<T> property)
{
if (SortProperties.Contains(property))
{
throw new ArgumentException(string.Format("property {0} already exists in sorting order", property.Name));
}
SortProperties.Add(property);
}
}
now, all the repository class has to do is:
protected IEnumerable<T> Search<T>(SearchParametersCollection<T> parameters)
where T : Entity
{
IQueryable<T> query = this.Session.Linq<T>();
foreach (var searchParam in parameters.SearchProperties)
{
query = searchParam.Key.AddSearchTerm(query, searchParam.Value);
}
//add order
foreach (var sortParam in parameters.SortProperties)
{
query = sortParam.AddSortingTerm(query);
}
return query.AsEnumerable();
}
for example, here's a class which implements searching a user by their full name:
public class UserFullName : EntityProperty<User>
{
public override string Name
{
get { return "Full Name"; }
}
public override IQueryable<User> AddSearchTerm(IQueryable<User> query, IEnumerable<object> values)
{
switch (_operator)
{
case Operator.In:
//btw- this doesn't work with nHibernate... :(
return query.Where(u => (values.Cast<string>().Count(v => u.FullName.Contains(v)) > 0));
case Operator.NotIn:
return query.Where(u => (values.Cast<string>().Count(v => u.FullName.Contains(v)) == 0));
default:
throw new InvalidOperationException("Unrecognized operator " + _operator.ToString());
}
}
public override IQueryable<User> AddSortingTerm(IQueryable<User> query)
{
return (_sortAscending) ? query.OrderBy(u => u.FullName) : query.OrderByDescending(u => u.FullName);
}
public UserFullName(bool sortAscending)
: base(sortAscending)
{
}
public UserFullName(Operator op)
: base(op)
{
}
}
my questions are:
1. firstly- am I even on the right track? I don't know of any well-known method for achieving what I want, but I may be wrong...
2. it seems to me that the Properties classes should be in the domain layer and not in the DAL, since I'd like the controller layers to be able to use them. However, that prevents me from using any nHibernate-specific implementation of the search (i.e any other interface but Linq). Can anybody think of a solution that would enable me to utilize the full power of nH while keeping these classes visible to upper layers? I've thought about moving them to the 'Common' project, but 'Common' has no knowledge of the Model entities, and I'd like to keep it that way.
3. as you can see by my comment for the AddSearchTerm method- I haven't really been able to implement 'in' operator using nH (I'm using nH 2.1.2 with Linq provider). any sugggestions in that respect would be appriciated. (see also my question from yesterday).
thanks!
If you need good API to query NHIbernate objects then you should use ICriteria (for NH 2.x) or QueryOver (for NH 3.x).
You over complicating DAL with these searches. Ayende has a nice post about why you should not do it
I ended up using query objects, which greatly simplified things.