Using the recently released EF Core 7, I have seen in Code magazine that you can map to stored procedures as follows (for example):
modelBuilder.Entity<Person>()
.InsertUsingStoredProcedure("PeopleInsert",
spbuilder => spbuilder
.HasParameter(p => p.FirstName)
.HasParameter(p => p.LastName)
)
But what is the C# syntax for calling the stored procedure once it is mapped in this way?
I can of course call the stored procedure using FromSqlRaw as previously but I thought that the mapping would lead to a more elegant way of calling it, which I have as yet been unable to establish.
Your stored procedure will be called when you INSERT a new item (triggered by the database context). Similar setups are available for DELETE and UPDATE.
Related
EF Core supports executing stored procedures and mapping the results to an entity (either existing entities or entities created specifically for the stored procedure).
I've read many issues around this topic, such as https://github.com/aspnet/EntityFrameworkCore/issues/1862 https://github.com/aspnet/EntityFrameworkCore/issues/245.
I thought that #1862 would enable server evaluation for stored procedures that only perform a SELECT operation.
However, it seems that my code results in 2 calls to the database. Is there a way to execute the following code with a single call to the database?
var customIdsQuery = _dbContext.Set<CustomId>()
.FromSql($"dbo.GetCustomIDs {username}").AsNoTracking();
var eventsQuery = _dbContext.Event.Where(ev => ev.User.Username == username)
.Join(customIdsQuery, ev => rev.CustomId, cid=> cid.Id, (ev, cid) => ev).AsNoTracking();
var events = eventsQuery.ToList(); // 2 database calls
The model builder looks something like that:
modelBuilder.Entity<CustomId>(); // For the stored procedure
modelBuilder.Entity<Event>(entity => // Key is defined as attribute on the model
{
entity.HasOne(d => d.Meeting)
.WithMany(p => p.Event)
.HasForeignKey(d => d.MeetingId);
entity.HasOne(d => d.User)
.WithMany(p => p.Event)
.HasForeignKey(d => d.UserId);
});
Am I missing something obvious in order to be able to have the entire query evaluate on the SQL server? From the documentation (https://learn.microsoft.com/en-us/ef/core/querying/raw-sql):
SELECT statements passed to this method should generally be composable: If EF Core needs to evaluate additional query operators on the server (e.g. to translate LINQ operators applied after FromSql), the supplied SQL will be treated as a subquery
My stored procedure is in fact like a SELECT statement, is there a way to enforce it being recognized as such?
Thanks.
I used a table-valued function instead, it is composable since EFC knows that it is read only.
I currently use a database-first approach using Entity Framework (EF). I'm investigating different ways of calling stored procedures (stored procedures that are NOT tied to entities) using EF. Currently, I'm using the approach found here using the designer (in this approach, I import a stored procedure, create a function import and use a complex type as my mapped object). I don't like this because it relies on a bloated EDMX file and it causes merge conflicts in source control when two or more people perform this procedure and check in their code.
I found this post which claims I can call a stored procedure and map to a plain old C# object (POCO).
My question is if I want to map to a POCO to a stored procedure in EF not using the designer approach, how do I get aliases for column names? For example, let's say I call a stored procedure and get a particular column back named "CustomerID" and I want the property mapped to it to be named "Id" instead of "CustomerID". How can I do this without using the designer approach?
Something like the following should work:
[your db context].Database.SqlQuery<[your POCO class]>("[name of stored proc] [comma separated parameters]", parameter1, parameter2, .....)
Here's example from one of my applications:
_context.Database.SqlQuery<Library>("usp_paged_select_libraries #userId, #offset, #fetch", userIdParameter, offsetParameter, fetchParameter);
Within your POCO you would mark up your properties with:
[Column("[your alias here]")]
I have a challenge. We have a database that was originally designed to be used with VB6 and most of the functionality sits in stored procedures. I cannot make changes to the stored procedures as the old application will still need to work for a while and I do not have my own copy of the database that I can modify even briefly.
So is it possible to execute a stored procedure from EF and have it do it's best to write the results into an array/collection of POCOs?
I've tried the database first approach and import but EF says the stored procedure does not return any columns and so cannot create a complex type. I've found there are ways to change the stored procedure to allow this to work but I cannot alter the database I'm using.
Another challenge is that the names of the columns in the results are things like 'Date last changed' in other words with spaces. How will EF try to map these? Would it become DataLastChanged or possibly Data_last_changed? Is there a way to mark my POCO with attributes to say how they are mapped?
What I was hoping for is something like
var resuls = efContext.ExecuteStoredProcedure<MyPOCOType>("spName",param1, param2, ...);
And have EF do it's best to match the results to the type. Does such a thing exist? Incidentally we are using EF4 but I believe 5 is available to us.
I think I've cracked part of the problem for myself. The following snippet does what I need.
using (DbContext context = new DbContext("DBConnectionStringNameFromAppConfig"))
{
SqlParameter[] parameters =
{
new SqlParameter("#OwnerID", DBNull.Value),
new SqlParameter("#ExternalColorID", colorOwner.ExternalColorID),
new SqlParameter("#ProductionSiteID", DBNull.Value),
new SqlParameter("#PanelstatusNr", DBNull.Value),
new SqlParameter("#DateLastChecked", DBNull.Value),
new SqlParameter("#rowcount", DBNull.Value),
};
var colors = context.Database.SqlQuery<Models.ColorSelectEvaluation>("[dbo].[sp_Color_Select_Evaluation] #OwnerID, #ExternalColorID, #ProductionSiteID, #PanelstatusNr, #DateLastChecked, #rowcount", parameters).ToList();
}
The confusing this is still the naming of the columns. They mostly seem to work but EF is not mapping the resulting column 'Needs evaluation' to the property NeedsEvaluation on my object.
Regarding the column names not matching. Another Q&A on stackoverflow deals with this nicely.
Why is my DbModelBuilder configuration ignored when mapping Entity from DbSet<T>.SqlQuery?
To summarise, MS think it would be great but they do not support mapping of names in this way. The only solution is to change the stored procedure and that is no option for me as it would break the legacy applications still using it.
Say I got this situation: I have to filter one of my entities with data which I get from an stored procedure:
var results = from c in db.Customer
join p in db.GetSPResults() on c.Id equals p.Id
select c;
on my Context class I got this:
public ObjectResult<Example> GetSPResults()
{
return (this as IObjectContextAdapter).ObjectContext.ExecuteFunction<Example>("Proc_Example");
}
So far I run into 2 problems:
I get an InvalidOperationException when code strikes the ExcecuteFunction line:
The FunctionImport 'xxx' could not be found in the container 'xxx'.
Assuming you guys can help me to solve that problem, would it be possible to query that way? Using those stored procedure results like a context entity? I think EF won't allow that cause it's not an entity, nor a "Constant Value".
I'm using EF 4.3.
instead of calling your procedure like that by name as string you can import it in your entity model then you can have a type safe / strongly typed method call directly on your DbContext.
basically you need to execute a function import, see here for an example: Using stored procedures with Entity Framework
Update: for POCO / Code first, see here: EF Code-First - Mapping stored procedures
I am trying to get to grips with the Entity framework, and have a requirement to order results by distance from a point on the globe. I have decided on previous advice to do this using a stored procedure which I have successfully done populating a view. However I need to return multiple tables, which I understand I cannot do directly using stored Procedures on the Entity Framework. If this is not correct, I would be grateful if someone could advise how I might do this.
Anyway I therefore have defined a simple sp (SELECT id FROM table) and then wanted to perform a linq query to join this with the equivalent object in my model as follows:
var sp = db.StoredProcedure();
var ret = from x in db.X
join y in sp on x.ID equals y.ID
select x;
However when I perform this I get the following exception resulting from the query:
"Unable to create a constant value of type 'System.Collections.Generic.IEnumerable'1'.Only primitive types('suchas Int32, String, Guid') are supported in this context."
Why is this happening? Is this the right approach? (Note that my final sp will be more complex, and I will be returning multiple classes from the select in 'ret')
Use EF Extensions
Stored procedures are really badly supported in EF. Even if they return entity results, they don't provide any name mappings, so you have to rename columns in stored procedures yourself.
But. There's project called Entity Framework Extensions that will make all kinds of different scenarios with stored procedures possible.
Using EF extensions you can use stored procedures in any way you want:
you can do column remappings in your custom materializer (so your stored procedure returns same columns as they are in the DB without the need to rename columns to entity property names)
you can return multiple result sets (great for 1:* and : relations)
you can use scalar stored procedures or even procedures that don't return anything
you can consume results from a stored procedure that returns multiple entities per row (when having 1:1 relation between two of them)
you can also use output parameters which is great if you create a stored procedure that does paging (returns a subset of records as entities and returns out parameters with total count)
etc.
You can do preety much anything. We've used EF Extensions with much success on a some project. We wrote our own materializers (basically a lamba expression) for entities that our stored procedures returned. And then we materialized their results.
I don't think EF4 will support stored procedures to this level anyway, so getting acquainted to EF Extensions is always valuable.
The EF in .NET 3.5 SP1 cannot map procs which return scalar values (in .NET 4.0 you can). The SP must return all the values necessary to materialize a full entity, which is likely more than just the ID.
Also, it's almost never correct to use the "join" reserved word in LINQ to Entities. You traverse relationships in your client schema instead.
Start by writing a proc which returns all values necessary for an entity type. Map that proc. Then do:
IQueryable<MyEntity> q = from e in Context.MyEntities
select e;
Then move on from there.