I´m new to ASP.NET, I want to know if someone can explain how Insert works using a stored procedure. I'm using Dapper.
I have a product model like this:
using Dapper.Contrib.Extensions;
using System;
namespace Dto.Entities.Products
{
[Table("Product.ProductDetail")]
public class ProductDetail
{
[Key]
public int ProductDetailId { get; set; }
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
}
Now can someone explain me how can I do an insert in controller using a stored procedure? Regards
The quickest way is to handle all the logic in the controller as you said:
[HttpPost]
public async Task<IHttpActionResult> Post([FromBody]ProductDetail payload)
{
using (var connection = new SqlConnection("<your_connectionstring>"))
{
var parameters = new
{
ProductId = payload.ProductId,
Name = payload.Name,
Description = payload.Description
};
await connection.ExecuteAsync("<your_insert_sp>", parameters, commandType: CommandType.StoredProcedure).ConfigureAwait(false);
}
return Ok();
}
This is not the recommended way to go though.
You should always use a dto class instead of your real data model as the type going in or leaving your controller.
Also the db logic should be moved outside of the controller, for example to a repository class. Or you can use Mediatr to move your logic away from your action and keep your controllers thin.
Related
I added this line to resolve circular errors in my JSON:
services.AddMvc()
.AddJsonOptions(
options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
But now, I find that some of my controllers return too much information.
I found this similiar question, but reading through it, I couldn't really figure out how to apply it to my code:
Avoiding Circular referencing providing too much data
For example, this simple controller returns data that I don't want:
[HttpGet("{id}")]
public async Task<ActionResult<BookList>> GetBookList(string id)
{
var bookList = await _context.BookList.FindAsync(id);
return bookList;
}
Here is the model for that data:
public partial class BookList
{
public BookList()
{
BookLinks = new HashSet<BookLinks>();
}
public string BookId { get; set; }
public Guid LibraryId { get; set; }
public string BookTitle { get; set; }
public string BookText { get; set; }
public byte? BookType { get; set; }
public virtual LibraryList Library { get; set; }
public virtual ICollection<BookLinks> BookLinks { get; set; }
}
}
So when I hit the controller above, I am getting all the unwanted data for BookLinks in addition to the data from BookList.
I just want the data from BookList based on a particular BookId.
I was under the impression, that if I wanted all data returned(including the BookLinks data), I'd have to do something like this:
var bookList = await _context.BookList
.Include(i => i.BookLinks)
.Where(b => b.BookId == id)
.ToListAsync();
That said, is there way to limit or exclude data that I don't want?
Thanks!
Your navigation props are virtual, so I'm assuming you have lazy-loading enabled. When the serializer walks the object, it will trigger the get for each of these properties, which will in turn issue queries one by one to backfill the data, which will then get serialized as well. Assuming that it doesn't encounter a circular reference, it will continue to walk down the related entities, loading and serializing each relationship.
This is a perfect illustration of why you should never serialize entities. Entities are for working with the database. They are not for and should not be used for returning responses, rendering views, etc.
Instead, create a view model/DTO/whatever you want to call it. Map your entity class(es) on to that, and then return the view model instead. That way, you can control exactly what the response is.
I am building a Web API and have two models: Task and Feature:
public class Feature
{
[Key]
public long FeatureId { get; set; }
public string Analyst_comment { get; set; }
public virtual ICollection<User_Task> Tasks { get; set; }
public Feature()
{
}
}
public class User_Task
{
[Key]
public long TaskId { get; set; }
public string What { get; set; }
[ForeignKey("FeatureId")]
public long? FeatureId { get; set; }
public User_Task()
{
}
}
I create Tasks first and then create a Feature that combines few of them. Task creation is successful, however while creating a Feature with existing Tasks, my controller throws an error saying the task already exists:
My FeatureController has following method:
//Create
[HttpPost]
public IActionResult Create([FromBody] Feature item)
{
if (item == null)
{
return BadRequest();
}
** It basically expects that I am creating a Feature with brand new tasks, so I guess I will need some logic here to tell EF Core that incoming tasks with this feature already exist **
_featureRepository.Add(item);
return CreatedAtRoute("GetFeature", new { id = item.FeatureId }, item);
}
How to tell EF core that incoming Feature has Tasks that already exist and it just needs to update the references instead of creating new ones?
My context:
public class WebAPIDataContext : DbContext
{
public WebAPIDataContext(DbContextOptions<WebAPIDataContext> options)
: base(options)
{
}
public DbSet<User_Task> User_Tasks { get; set; }
public DbSet<Feature> Features { get; set; }
}
And repo:
public void Add(Feature item)
{
_context.Features.Add(item);
_context.SaveChanges();
}
When calling Add on a DBSet with a model that was not loaded from EF, it thinks it is untracked and will always assume it is new.
Instead, you need to load the existing record from the dbcontext and map the properties from the data passed into the API to the existing record. Typically that is a manual map from parameter object to domain. Then if you return an object back, you would map that new domain object to a DTO. You can use services like AutoMapper to map the domain to a DTO. When you're done mapping, you only need to call SaveChanges.
Generally speaking, loading the record and mapping the fields is a good thing for the security of your API. You wouldn't want to assume that the passed in data is pristine and honest. When you give the calling code access to all the properties of the entity, you may not be expecting them to change all the fields, and some of those fields could be sensitive.
I am using Hot towel template and extended functionality of it by using breeze. I have used breeze.partial-entities.js file to conver breeze entities to proper dtos that can be used by knockout observables as shown below.
function dtoToEntityMapper(dto) {
var keyValue = dto[keyName];
var entity = manager.getEntityByKey(entityName, keyValue);
if (!entity) {
// We don't have it, so create it as a partial
extendWith = $.extend({ }, extendWith || defaultExtension);
extendWith[keyName] = keyValue;
entity = manager.createEntity(entityName, extendWith);
}
mapToEntity(entity, dto);
entity.entityAspect.setUnchanged();
return entity;
}
For few of the entities it is working properly and getting breeze data converted to entities but for one of the entity implementation is failing. Model for the same is given as below.
public class StandardResourceProperty
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int StandardResourceId{ get; set; }
public int InputTypeId{ get; set; }
public int ListGroupId{ get; set; }
public string Format{ get; set; }
public string Calculation{ get; set; }
public bool Required{ get; set; }
public int MinSize{ get; set; }
public int MaxSize{ get; set; }
public string DefaultValue{ get; set; }
public string Comment { get; set; }
public virtual StandardResource AssociatedStandardResource { get; set; }
public virtual List AssociatedList { get; set; }
}
The error i am getting is
TypeError: this[propertyName] is not a function
[Break On This Error]
thispropertyName;
breeze.debug.js (line 13157)
]
with code
proto.setProperty = function(propertyName, value) {
this[propertyName](value);
// allow set property chaining.
return this;
};
Please let me know . What can be possible issue with the implementation also , it would be great if i can get more suggestion on how to debug and trace such issues.
Let's back up. I do not understand what you mean by "convert breeze entities to proper dtos that can be used by knockout observables". Breeze entities are already configured as KO observables (assuming you are using the default Breeze model library configuration). What are you trying to do?
I suspect you are following along with the Code Camper Jumpstart course where it does a getSessionPartials projection query. That query (like all projections) returns DTOs - not entities - and maps them with the dtoToEntityMapper method into Session entities.
The CCJS dtoToEntityMapper method cannot be used with entities. It is for converting from a DTO to an Entity and takes DTOs - not entities - as input.
Goodbye to dtoEntityMapper
The dtoToEntityMapper method pre-dates the ability of Breeze to automate projection-to-entity mapping by adding .toType('StandardResourceProperty') to your projection query.
Here is what the CCJS getSessionPartials query could look like now:
var query = EntityQuery
.from('Sessions')
.select('id, title, code, speakerId, trackId, timeSlotId, roomId, level, tags')
.orderBy(orderBy.session)
.toType('Session');
If you go this way, be sure to set the default state of the isPartial flag to true in the custom constructor (see model.js)
metadataStore.registerEntityTypeCtor(
'Session', function () { this.isPartial = true; }, sessionInitializer);
Note that this.isPartial = true is the reverse of the CCJS example where the default was false.
Make sure that you set isPartial(false) when you query or create a full entity. In CCJS there are two places to do that: in the success-callback of getSessionById AND in createSession which would become:
var createSession = function () {
return manager.createEntity(entityNames.session, {isPartial: false});
};
I have a method like this that works as expected.
[WebGet]
public IQueryable<BuildJobModel> GetCustomers()
{
var context = new MyDataContext(); // ADO.NET Entity Data Model
var query = from c in context.Customers
select new CustomerModel {
Id = c.Id,
Name = c.Name
};
return query;
}
But when I try to create a more complex query like this, it doesn't work.
[WebGet]
public IQueryable<BuildJobModel> GetCustomers()
{
var context = new MyDataContext(); // ADO.NET Entity Data Model
var query = from c in context.Customers
select new CustomerModel {
CustomerId = c.CustomerId,
Name = c.Name,
Orders = from o in c.Orders
select new OrderModel {
OrderId = o.OrderId,
Details = o.Details
}
};
return query;
}
The Models look like this:
public class CustomerModel
{
public int CustomerId { get; set; }
public string Name { get; set; }
public IEnumerable<OrderModel> Orders { get; set; }
}
public class OrderModel
{
public int OrderId { get; set; }
public string Details { get; set; }
}
The Exception:
Cannot serialize member Proj.CustomerModel.Logs of type System.Collections.Generic.IEnumerable`1[[Proj.OrderModel, Proj, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]] because it is an interface.
My Goals:
I want to be able to expose an IQueryable interface.
I want to return nested data.
I want to use my own Models, not the ado.net entities
I want to hit the database with as few queries as possible (one would be best).
I would strongly recommend not exposing IQueryable as a return type of a WCF service.
WCF was designed to return data not queries
You lose control over the nature of the query: someone might potentially use this query in a way that is resource-intensive
Ideally, if you want to return collections use array or a generic list.
With respect to your list of goals:
Can you explain this? I don't see what IQueryable interface has to do with nested data.
You can still return arrays or lists of your models
You have better control over performance if you execute query locally and return results instead
Update: Have a look at WCF Data Services - that might be able to do what you want.
In case you're trying to return JSON: the build in JsonFormatter is not able to (de)serialize interfaces. You should try the JSON.NET Formatter from WebApiContrib.
I think you just need to use an array rather than IEnumerable, like so...
public class CustomerModel
{
public int CustomerId { get; set; }
public string Name { get; set; }
public OrderModel[] Orders { get; set; }
}
public class OrderModel
{
public int OrderId { get; set; }
public string Details { get; set; }
}
If you're serializing this to json/xml I suspect the built-in serializers don't know what to do with IEnumerable.
I working to code first technique with EF4.1, WCF webservice, and Sql azure. To improve the performance i wanted to create the View to fetch data at server side. but according to this
we can not use sql View with EF code first.
Because the database and models are defined. it has the data. to re create and dump data for the only creating view is time taking process.
so i just created the View explicitly at sql server.
now I want to call that view from my wfc web service. Now i want to access that view as dataset in WCF. need guidlines. and right approach
Could you define a table in codefirst with the same columns, then create a custom initializer that drops the table from the database and creates it again as a view?
Then you should be able to query it just like normal.
Edit Update to show working example
public class User
{
public int Id { get; set; }
public string Email { get; set; }
}
public class UserView
{
public int Id { get; set; }
public string Email {get; set;}
}
public class TestContext : DbContext
{
static TestContext()
{
Database.SetInitializer<TestContext>(new DatabaseInitializer());
}
public DbSet<User> Users { get; set; }
public DbSet<UserView> UserView { get; set; }
}
class DatabaseInitializer : DropCreateDatabaseIfModelChanges<TestContext>
{
protected override void Seed(TestContext context)
{
base.InitializeDatabase(context);
context.Database.ExecuteSqlCommand("drop table UserViews");
context.Database.ExecuteSqlCommand(#"CREATE VIEW [dbo].[UserViews] AS SELECT *
from [dbo].[Users]
GO");
context.Users.Add(new User() { Email = "test#test.com" });
}
}
...
using (TestContext context = new TestContext())
{
context.UserView.ToList(); //contains all the users
}