I'm trying to map the response from an SP to corresponding models, custom for the specific SP. The SP uses column names that I don't want to see anywhere in my code because they're violating all standards (even bad spelling).
So, I tried creating a model like this one.
public sealed class Product
{
[Column("Id_PRODUCT")]
public int Id {get; set;}
[Column("PruductTilte")]
public string Title {get; set;}
// Column Name = Description
public string Description {get; set;}
}
And then in the partial Context
public partial class ContosoContext
{
public async Task<Product[]> GetProductsAsync()
{
using (var cmd = Database.Connection.CreateCommand())
{
cmd.CommandText = "GeTpRoDuCtS";
using (var reader = await cmd.ExecuteReaderAsync(CommandBehavior.CloseConnection))
{
return ((IObjectContextAdapter)this)
.ObjectContext
.Translate<Product>(reader)
.ToArray();
// Actually calling reader.NextResult() and doing more translates here (multi-set response).
}
}
}
}
When executing this like var products = await new Context().GetProductsAsync() only Description is set, EF6 seems to be ignoring the ColumnAttribute.
Isn't this supposed to work? Is there any good work-around?
Related
I’m using Swashbuckle 6.1.4 in a .net 5.0 project.
I want to customise the ordering of the elements in a schema. The default order, i.e. that in which the properties are declared) isn’t good because when models extend a base model, the properties of the base model are listed at the bottom.
I’ve managed to apply a document filter to sort properties alphabetically:
public class SchemaSortingFilter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
var descs = context.ApiDescriptions.ToList();
// only applying to the SupporterDTO for now...
string model = "SupporterDTO";
if (swaggerDoc.Components.Schemas.ContainsKey(model))
{
var props = swaggerDoc.Components.Schemas[model].Properties.OrderBy(x => x.Key).ToArray();
swaggerDoc.Components.Schemas[model].Properties.Clear();
foreach (var prop in props)
{
swaggerDoc.Components.Schemas[model].Properties.Add(prop.Key, prop.Value);
}
}
}
}
But what I really want is to use a custom attribute to manage the order. Like this:
public class SwaggerOrderAttribute : Attribute
{
public int Order { get; private set; }
public SwaggerOrderAttribute(int order)
{
Order = order;
}
}
Which I’d use to decorate properties thus:
[SwaggerOrder(1)]
String PropertyZ {get; set;}
[SwaggerOrder(3)]
String PropertyX {get; set;}
[SwaggerOrder(2)]
String PropertyY {get; set;}
My problem is that the list of attribute values that is exposed in my filter via swaggerDoc.Components.Schemas[model].Properties does not include my custom attributes.
During my explorations, and thanks to a hint in this question I’ve tried to get hold of them like this but it didn’t see them:
if (apiDesc.TryGetMethodInfo(out MethodInfo mi))
{
var atts = mi.DeclaringType
.GetCustomAttributes(true)
.OfType<SwaggerOrderAttribute>()
.ToList();
}
How do I bring in my custom attributes for inclusion in my sorting linq query?
You need to implement ISchemaFilter. Then you'll be able to get hold of custom attributes from MemberInfo:
context.MemberInfo.CustomAttributes
Here is an implementation of getting an attribute from MemberInfo:
ISchemaFilter:
public class DefaultValuesSwaggerExtensions : Swashbuckle.AspNetCore.SwaggerGen.ISchemaFilter
{
public void Apply(OpenApiSchema schema, SchemaFilterContext context)
{
var attributes = context?.MemberInfo?.GetCustomAttributes(true).OfType<SwaggerDefaultValueAttribute>();
}
}
Swagger
services.AddSwaggerGen(c =>
{
c.SchemaFilter<DefaultValuesSwaggerExtensions>();
});
When using the OData Client (.NET) for v4 to create/post a new entity we are not getting the auto-generated (auto-incremented) ID/Key back from the service. When we create the new entity, we have no ID assigned (it is an 'int' so the value is '0'). After calling SaveChanges the result JSON response has the new auto-assigned id (e.g. '4662'). The issue is that the Entity on the client side still has '0' for its ID (it's not mapped back to the orig. entity).
I also opened the issue on GitHub: https://github.com/OData/odata.net/issues/775
Assemblies affected
Microsoft.Data.OData - v5.7.0
Microsoft.OData.Client - v6.15.0
Microsoft.OData.Core - v6.15.0
Microsoft.OData.Edm - v6.15.0
Reproduce steps
Save a new entity that will automatically have an ID assigned on the server-side (do not set this ID on the client-side)
After saving, check the ID property on your new entity (on the client-side)
Expected result
The newly created (and saved) entity will have the ID updated to match the server-side response (JSON) that came back to the client.
Actual result
JSON response form the server-side has the correct ID, but the Entity on the client-side is never updated with this new information.
In order to support your scenario, some requirements want to be met.
For a full reference, see my sample project at GitHub.
Given a model:
public class Item {
[Key] //define as key
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] //used by EF to auto generate Ids and mapping them back on inserts
public int Id { get; set; }
[Required(AllowEmptyStrings = false)]
public string Name { get; set; }
}
An EntityFramwork-DbContext:
public class SampleContext : DbContext {
public SampleContext()
: base("name=SampleContext") {
}
public DbSet<Person> Persons { get; set; }
public DbSet<Item> Items { get; set; }
}
A straight-forward OData-configuration:
var builder = new ODataConventionModelBuilder();
builder.EntitySet<Person>("Persons");
builder.EntitySet<Item>("Items");
config.MapODataServiceRoute("ODataRoute", "odata", builder.GetEdmModel());
and a corresponding ODataController:
public class ItemsController : ODataController {
private readonly SampleContext _Db;
public ItemsController() {
_Db = new SampleContext();
}
...
[ResponseType(typeof(CreatedODataResult<Item>))]
public IHttpActionResult Post(Item p) {
if (!ModelState.IsValid)
return BadRequest(ModelState);
var inserted = _Db.Items.Add(p);
_Db.SaveChanges();
//return entity with any server side changes. This would also work for other DB-generated columns.
return Created(inserted);
}
}
Should produce the desired result:
public void AddingItemTransportsServerGeneratedIdBackToClientSideModel() {
var item = new Item() {
Name = Guid.NewGuid().ToString()
};
var container = new Container(new Uri("http://localhost/ODataAutoId/odata"));
container.AddToItems(item);
container.SaveChanges();
var actual = item.Id;
var unexpected = 0;
Assert.AreNotEqual(unexpected, actual);
}
Your actual implementation might vary or miss some of the requirements. In order to help in your particular scenarion, provide more details, as stated in my comment.
I'm using VS2013 and building a simple MVC5 app with EF6 (learning C#/MVC)
The app is simple. There's a table and an SP in an SQL database that serves up sequential job nos to users via web page. This SP will also be called by a another app so the biz logic is in the SP.
The SP takes a username and returns a JobNo (PK), which is derived as Max(JobNo) + 1
I'm using DB First (as I don't currently understand enough about migrations to production with code first and nearly everything I will write has to work with existing DB's and existing SP's)
I created the model from the DB using ADO.NET and chose the table and a few SP's. This created the following:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Manual changes to this file may cause unexpected behavior in your application.
// Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace JobNoServer.Models
{
using System;
using System.Collections.Generic;
public partial class JobNo
{
public int JobNo1 { get; set; }
public System.DateTime CreateDateTime { get; set; }
public string UserName { get; set; }
}
}
The problem I've got is that when I call the SP (user clicks "Get new Job No"), I only want to pass the username. The CreateDateTime will be current datetime (set in SP) and JobNo is determined in SP.
I tried removing the setter in class members:
public int JobNo1 { get; }
but then I get the error "must declare a body because it is not marked abstract or extern", but can't figure out how to fix this.
The other issue I have is that the controller created this Create method:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create([Bind(Include = "JobNo1,CreateDateTime,UserName")] JobNo jobNo)
{
if (ModelState.IsValid)
{
db.JobNo.Add(jobNo);
await db.SaveChangesAsync();
return RedirectToAction("Index");
}
return View(jobNo);
}
But I can't pass a job no, as it doesn't exist until after Create is called. When I remove the JobNo from the add method, I get a message saying there is no overload that takes zero params. When i look at the definition of Add it's some kind of generic class and the create view is saying job no is mandatory
public class DbSet<TEntity> : DbQuery<TEntity>, IDbSet<TEntity>, IQueryable<TEntity>, IEnumerable<TEntity>, IQueryable, IEnumerable, IInternalSetAdapter where TEntity : class
{
Could someone point me in the right direction of how to have a create method that takes just the UserName, calls the SP and gets the return value?
There are lot's of way to do this let's look at a few options:
//add a post method to the JobNo class (makes controller logic dead simple)...
public partial class JobNo
{
public int JobNo1 { get; set; }
public System.DateTime CreateDateTime { get; set; }
public string UserName { get; set; }
public Task Post(){
using (var db = new My Entities){
//any issues found here are purely Data access related!
db.JobNo.Add(UserName);
await db.SaveChangesAsync();
}
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<ActionResult> Create(JobNo vm)
{
if (ModelState.IsValid)
{
await vm.Post();
RedirectToAction("Index");
}
return View(vm);
}
But first you have to solve the problem of the SP wanting more than just 1 parameter right? Did you map the SP using DB first? Is JobNo class from the SP? There seems to be an "impedance mismatch"
By the looks of it you have your db mapped successfully, however if your db relies on Stored procedures to create PK instead of it's default functionality MVC can get a little confused, which is where you're having troubles.
Have you tried mapping the stored procedure to your context. Basically right click in your edmx diagram and 'update from database'. Instead of adding your tables(which you already did), you'll find the SP under the Stored Procedures and Functions section and add it. Done. Here's the MS how to...
If you map your stored procedure you can simply do something like the following
public JobNo myCreate( DateTime createDT, string uName)
{
int jobNo = 0;
using(Context db = new Context())
{
jobNo = db.yourSP(youParams probably uName, createDT);
}
return new JobNo() { JobNo1 = jobNo, CreateDateTime = createDT, UserName = uName};
}
Then you can reference the Create function in your question and just pass it the JobNo object you just created.
If all else fails and you can't seem to map your SP, remember you could always default to exectuting SQL directly against your context, and call your SP that way.
public JobNo myCreate( DateTime createDT, string uName)
{
int jobNo = 0;
using(Context db = new Context())
{
jobNo = db.Database.SqlQuery<int>("YourProcName #param1, #param2",
new SqlParameter("param1", createDT.toString()),
new SqlParameter("param2", uName));
}
return new JobNo() { JobNo1 = jobNo, CreateDateTime = createDT, UserName = uName};
}
I just followed an MVC tutorial on creating an image Gallery, which connects the Controller to the data connection, like this:
ImageController.cs:
...
private CustomMembershipDB db = new CustomMembershipDB();
public ViewResult Index()
{
return View(db.lm_pics.ToList());
}
...
Instead of connecting directly to CustomMembershipDB, I'd like to use my own Model named GalleryModel.cs. I'm thinking this would allow me to create more functionality that just direct data access.
I am not sure how to write this model, or how to reference it in the controller so that it behaves the same way as a direct database connection does now.
Currently, my GalleryModel.cs file looks lke this (edited to correct error):
namespace LMProj_MVC.Models
{
public class GalleryModel
{
public string Picname { get; set; }
public string Decription{ get; set; }
public int Userid { get; set; }
}
public class PicDBContext : CustomMembershipDB
{
public DbSet<GalleryModel> GalleryModel { get; set; }
}
}
I'd like to be able to show the gallery using an iEnumberable list as I am doing now, in addition to creating other methods. Could someone tell me what I'm missing?
You need to create an instance of your model object for each of your database pictures. You could use LINQ to do this, for example:
var picSummaries = db.lm_pics.Select(pic => new GalleryModel{
Picname = pic.Name,
Description = pic.Description,
Userid = pic.User.Id
});
Or you could use a for each loop:
var picSummaries = new List<GalleryModel>();
foreach (var pic in db.lm_pics)
{
picSummaries.Add(new GalleryModel{
Picname = pic.Name,
Description = pic.Description,
Userid = pic.User.Id
});
}
then return the view as before:
return View(picSummaries);
Perhaps there is an easy solution for my problem but I simply cannot seem to find it. I have read lots of tutorials about Knockout so I get the basics but I ask this question because my entity-structure is a bit more complicated than a person with a name and a list of friends which may or may not be on Twitter (Video on Channel9: Helping you build dynamic JavaScript UIs with MVVM and ASP.NET). Here's my situation:
I have a class PersonnelClass with this basic structure:
[Serializable]
//The interface is for the implementation of 'Name' and 'Description'
public class PersonnelClass : IPersonnelClassOrPerson
{
public PersonnelClass() : this(Guid.NewGuid(), "", "") { }
public PersonnelClass(Guid id, String name, String description = null)
{
if (id == Guid.Empty) { throw new ArgumentNullException("id"); }
Id = id;
Name = name;
Description = description;
Properties = new PropertyCollection();
}
public Guid Id { get; private set; }
public String Name { get; set; }
public String Description { get; set; }
public PropertyCollection Properties { get; private set; }
}
The PropertyCollection class and associated AbstractProperty class look like this:
[Serializable]
public class PropertyCollection: List<AbstractProperty> { }
[Serializable]
public abstract class AbstractProperty: IEntity, IProperty
{
public AbstractProperty(String name, String description = null) : this(Guid.NewGuid(), name, description) { }
public AbstractProperty(Guid id, String name, String description = null)
{
if (id == Guid.Empty) { throw new ArgumentNullException("id"); }
if (String.IsNullOrEmpty(name)) { throw new ArgumentNullException("name"); }
Id = id;
Name = name;
Description = description;
}
public Guid Id { get; private set; }
public String Name { get; private set; }
public String Description { get; private set; }
}
In my Controller, I create an instance of a PersonnelClassViewModel that has this structure:
public class PersonnelClassViewModel
{
public PersonnelClass PersonnelClass { get; set; }
public List<AbstractProperty> Properties { get; set; }
}
I fill this viewmodel with a new PersonnelClass and two test-properties to pass to my View like this:
var properties = new List<AbstractProperty>
{
new TextProperty("prop1", "descr1"),
new TextProperty("prop2", "descr2")
//TextProperty is derived from AbstractProperty
};
var vm = new PersonnelClassViewModel { Properties = properties };
return View(vm);
I get everything in my View as I wanted. From the View I want to create a new PersonnelClass with a set of selected properties. I have the fields for Name and Description and to add the properties I have a ListBox with the properties that already exist (for demo-purpose they came from the controller now). Through a bit of Knockout JavaScript code I can select items from this list and populate an HTML select-control () with the selected properties to add to the PersonnelClass. This all works fine, until I want to build up an object to pass back to the Controller and create the PersonnelClass.
My question is: what Knockout JS code is needed to build up this object and pass it to the Controller by submitting the form and in my Controller how should I receive this object, meaning: what type of object should this be (PersonnelClass, PersonnelClassViewModel, ...) ?
If any more info/code is needed, please do ask. Thanks in advance!
Update after answer of 'B Z':
I followed a few more of Steven Sanderson's tutorials about this to be sure I understand this, especially the one you provided in your answer. Now I have following code in my View to start with:
var initialData = #Html.Raw(new JavaScriptSerializer().Serialize(Model));
var viewModel = {
personnelClassViewModel : ko.mapping.fromJS(initialData),
properties : personnelClassViewModel.Properties,
selectedProperties : ko.observableArray([]),
addedProperties : ko.observableArray([])
};
ko.applyBindings(viewModel);
The variable 'initialData' contains the values I expect it to have but then I get the following error:
Microsoft JScript runtime error: 'personnelClassViewModel' is undefined
I have no clue anymore. Can anyone help me fix this?
Steven Sanderson has an example of how to to work with variable length lists and knockoutjs
http://blog.stevensanderson.com/2010/07/12/editing-a-variable-length-list-knockout-style/
Having said that, I think your problem isn't so much on the knockout side and more on the how to databind the data correctly on the server side. In the link above, Steven uses a FromJson attribute to model bind which you may find useful...
HTH