Entity Framework updating entity with lookup table - c#

So,
I have built a repository/service application which I have been using successfully for a while.
I have started a new project and in this project I have a lookup table attached to one of my models/entities.
The model looks like this:
public class Team
{
public int Id { get; set; }
public string Name { get; set; }
public string Sport { get; set; }
public IList<Colour> Colours { get; set; }
}
I also have a binding ViewModel which looks like this:
public class TeamBindingViewModel
{
public int Id { get; set; }
[Required] public string Name { get; set; }
[Required] public string Sport { get; set; }
public IList<ColourBindingViewModel> Colours { get; set; }
}
public class ColourBindingViewModel
{
public int Id { get; set; }
[Required] public string Name { get; set; }
[Required] public string Hex { get; set; }
}
In my dbContext class I have set up this:
public class DatabaseContext : DbContext
{
// Define our tables
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
public DbSet<Colour> Colours { get; set; }
public DbSet<Team> Teams { get; set; }
/// <summary>
/// static constructor (only gets called once)
/// </summary>
static DatabaseContext()
{
// Create the database and insert our records
//Database.SetInitializer<DatabaseContext>(new DatabaseInitializer());
}
/// <summary>
/// Default constructor
/// </summary>
public DatabaseContext()
: base("DefaultConnection")
{
// Disable Lazy Loading
base.Configuration.LazyLoadingEnabled = false;
}
/// <summary>
/// Overrides the inherited OnModelCreated method.
/// </summary>
/// <param name="modelBuilder">The DbModelBuilder</param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Remove Cascading Delete
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
// Map the TeamColours table
modelBuilder.Entity<Team>()
.HasMany(m => m.Colours)
.WithMany()
.Map(m =>
{
m.MapLeftKey("TeamId");
m.MapRightKey("ColourId");
m.ToTable("TeamColours");
});
}
}
(nb: I have stripped this down to make it easier to read)
So, the problem I have is when I try to do an update, if I try to add colours, I get an error.
Here is a look at my base repository class:
public class Repository<T> : IDisposable, IRepository<T> where T : class
{
private readonly DbContext context;
private readonly DbSet<T> dbEntitySet;
public Repository(DbContext context)
{
if (context == null)
throw new ArgumentNullException("context");
this.context = context;
this.dbEntitySet = context.Set<T>();
}
public IQueryable<T> GetAll(params string[] includes)
{
IQueryable<T> query = this.dbEntitySet;
foreach (var include in includes)
query = query.Include(include);
return query;
}
public void Create(T model)
{
this.dbEntitySet.Add(model);
}
public void Update(T model)
{
this.context.Entry<T>(model).State = EntityState.Modified;
}
public void Remove(T model)
{
this.context.Entry<T>(model).State = EntityState.Deleted;
}
public void Dispose()
{
this.context.Dispose();
}
}
I then have a base service class like this:
public class Service<T> where T : class
{
private readonly IRepository<T> repository;
protected IRepository<T> Repository
{
get { return this.repository; }
}
public Service(IUnitOfWork unitOfWork)
{
if (unitOfWork == null)
throw new ArgumentNullException("unitOfWork");
this.repository = unitOfWork.GetRepository<T>();
}
}
and finally here is my TeamService class:
/// <summary>
/// Team service handles all team related functions
/// </summary>
public class TeamService : Service<Team>
{
/// <summary>
/// Default constructor
/// </summary>
/// <param name="unitOfWork"></param>
public TeamService(IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
/// <summary>
/// Get all teams asynchronously
/// </summary>
/// <param name="includes">Eager loading includes</param>
/// <returns>A list of colours</returns>
public async Task<IList<Team>> GetAllAsync(params string[] includes)
{
return await this.Repository.GetAll(includes).ToListAsync();
}
/// <summary>
/// Get a team by id
/// </summary>
/// <param name="id">The id of the colour</param>
/// <param name="includes">Eager loading includes</param>
/// <returns>A colour</returns>
public async Task<Team> GetAsync(int id, params string[] includes)
{
var models = await this.GetAllAsync(includes);
return models.Where(model => model.Id == id).SingleOrDefault();
}
/// <summary>
/// Create a team
/// </summary>
/// <param name="model">The team model</param>
public void Create(Team model)
{
// Create a team
this.Repository.Create(model);
}
/// <summary>
/// Update a team
/// </summary>
/// <param name="model">The team model</param>
public void Update(Team model)
{
// Update a team
this.Repository.Update(model);
}
/// <summary>
/// Delete a team
/// </summary>
/// <param name="model">The team model</param>
public void Remove(Team model)
{
// Remove a team
this.Repository.Remove(model);
}
}
I know there is a lot of code here, but I need to give you my entire process if anyone can help me :)
So, If I create an update method in my controller like this:
private async Task<IHttpActionResult> Save(TeamBindingViewModel model)
{
// If our model is invalid, return the errors
if (!ModelState.IsValid)
return BadRequest(ModelState);
// Create a list of colours
var colours = new List<Colour>();
// For each colour in our model, add to our list
foreach (var colour in model.Colours)
colours.Add(new Colour()
{
Id = colour.Id,
Name = colour.Name,
Hex = colour.Hex
});
// If there is an id
if (model.Id > 0)
{
// Update our team
await this.Update(model, colours);
}
else
{
// Create our team
this.Create(model, colours);
}
// Save the database changes
await this.unitOfWork.SaveChangesAsync();
// Return Ok
return Ok(model);
}
private void Create(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
var team = new Team()
{
Id = model.Id,
Name = model.Name,
Sport = model.Sport
};
// Assign our colours to our team
team.Colours = colours;
// Otherwise, create a new team
this.service.Create(team);
}
private async Task Update(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
var team = new Team()
{
Id = model.Id,
Name = model.Name,
Sport = model.Sport
};
// Update the team
this.service.Update(team);
}
private IList<Colour> GetDifference(IList<Colour> firstList, IList<Colour> secondList)
{
// Create a new list
var list = new List<Colour>();
// Loop through the first list
foreach (var first in firstList)
{
// Create a boolean and set to false
var found = false;
// Loop through the second list
foreach (var second in secondList)
{
// If the first item id is the same as the second item id
if (first.Id == second.Id)
{
// Mark it has being found
found = true;
}
}
// After we have looped through the second list, if we haven't found a match
if (!found)
{
// Add the item to our list
list.Add(first);
}
}
// Return our differences
return list;
}
The update will process and everything works. But if I change my update method to this:
private async Task Update(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
var team = new Team()
{
Id = model.Id,
Name = model.Name,
Sport = model.Sport
};
// Get our current model
var current = await this.service.GetAsync(model.Id, "Colours");
var currentColours = current.Colours;
// Assign our original colours to our team
team.Colours = currentColours;
// Get our colours to remove and add
var coloursToRemove = GetDifference(currentColours, colours);
var coloursToAdd = GetDifference(colours, currentColours);
// Loop through our colours to remove and remove them
if (coloursToRemove.Count > 0)
foreach (var colour in coloursToRemove)
team.Colours.Remove(colour);
// Loop through the colours to add and add them
if (coloursToAdd.Count > 0)
foreach (var colour in coloursToAdd)
team.Colours.Add(colour);
// Update the team
this.service.Update(team);
}
I get this error:
"exceptionMessage": "Attaching an entity of type 'Models.Team' failed because another entity of the same type already has the same primary key value. This can happen when using the 'Attach' method or setting the state of an entity to 'Unchanged' or 'Modified' if any entities in the graph have conflicting key values. This may be because some entities are new and have not yet received database-generated key values. In this case use the 'Add' method or the 'Added' entity state to track the graph and then set the state of non-new entities to 'Unchanged' or 'Modified' as appropriate."
I am not sure why I get this error, but I assume it has something to do with trying to add / remove colours from the lookup table.
Can anyone provide me with a solution to this problem?

your prolblem occurs because you are trying to update "team" while you have the "current" variable holdes the entity in the same scope try this:
private async Task Update(TeamBindingViewModel model, IList<Colour> colours)
{
// Create our new model
//var team = new Team()
//{
// Id = model.Id,
// Name = model.Name,
// Sport = model.Sport
//};
// Get our current model
var current = await this.service.GetAsync(model.Id, "Colours");
current.Name = model.Name;
current.Sport = model.Sport;
var currentColours = current.Colours;
// Assign our original colours to our team
//team.Colours = currentColours;
// Get our colours to remove and add
var coloursToRemove = GetDifference(currentColours, colours);
var coloursToAdd = GetDifference(colours, currentColours);
// Loop through our colours to remove and remove them
if (coloursToRemove.Count > 0)
foreach (var colour in coloursToRemove)
current.Colours.Remove(colour);
// Loop through the colours to add and add them
if (coloursToAdd.Count > 0)
foreach (var colour in coloursToAdd)
current.Colours.Add(colour);
// Update the team
this.service.Update(current);
}

Related

PartitionKey value must be supplied for this operation in cosmosdb delete operaiton

I am trying to delete a document from Cosmos DB
My code is like this:
public async Task<IHttpActionResult> DeletePartner(string id)
{
var telemetry = new TelemetryClient();
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var customers = await CosmosStoreHolder.Instance.CosmosStoreCustomer.Query().Where(x=> x.PartnerId == id).ToListAsync();
var userStore = CosmosStoreHolder.Instance.CosmosStoreUser;
var users = await userStore.Query().Where(x => x.PartnerId == id).ToListAsync(); ;
if (customers.Count> 0 || users.Count>0)
{
return BadRequest("You cant delete partners with existing customers or users");
}
else
{
var result = await CosmosStoreHolder.Instance.CosmosStorePartner.RemoveByIdAsync(id, "/CosmosEntityName");
return Ok(result);
}
}
catch (Exception ex)
{
string guid = Guid.NewGuid().ToString();
var dt = new Dictionary<string, string>
{
{ "Error Lulo: ", guid }
};
telemetry.TrackException(ex, dt);
return BadRequest("Error Lulo: " + guid);
}
}
}
[SharedCosmosCollection("shared")]
public class Partner : ISharedCosmosEntity
{
/// <summary>
/// Partner id
/// </summary>
[JsonProperty("Id")]
public string Id { get; set; }
/// <summary>
/// Partner name
/// </summary>
public string PartnerName { get; set; }
/// <summary>
/// Partner contact name
/// </summary>
public string PartnerContact { get; set; }
/// <summary>
/// Partner contact phone
/// </summary>
public string PartnerPhone { get; set; }
/// <summary>
/// Partner contact Office 365 domain
/// </summary>
public string PartnerDomain { get; set; }
/// <summary>
/// Partner type, silver, gold or platinum
/// </summary>
[ValidEnumValue]
public PartnerType PartnerType { get; set; }
/// <summary>
/// Partner start date
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// Partner end date
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// Parter enabled
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// CosmosEntityname
/// </summary>
[CosmosPartitionKey]
public string CosmosEntityName { get; set; }
}
/// <summary>
/// Partner type Enum
/// </summary>
public enum PartnerType
{
///Silver
Silver,
///Gold
Gold,
///Platinum
Platinum
}
But I got this error:
PartitionKey value must be supplied for this operation
I was trying to send as string "/CosmosEntityName" as second parameter, but it doesnt work
I am using Cosmonaut
You need to use the request options. For example, if your collection is partitioned by CosmosEntityName;
await this.documentClient.DeleteDocumentAsync(productDocument._self, new RequestOptions { PartitionKey = new Microsoft.Azure.Documents.PartitionKey(productDocument.CosmosEntityName) });
EDIT:
Here's what you need with Cosmonaut SDK
You need to provide the partition key value not the partition key
definition when you delete. Your delete request should look like this,
assuming the id is your partition key.
var deleted = await this._cosmonautClient.DeleteDocumentAsync(this._databaseName, collectionName, message.Id, new RequestOptions { PartitionKey = new PartitionKey(message.Id) });
You need to pass the value of the partition key of the element you want to delete as second parameter, not the path and attribute name.
var result = await CosmosStoreHolder.Instance.CosmosStorePartner.RemoveByIdAsync(id, "<partition key value for that id>");
Since the attribute you have defined as PK is CosmosEntityName, you need that attribute's value for that document.

How to insert EntityFramework with circular references withouth Violation of PRIMARY KEY constraint

Error: Violation of PRIMARY KEY constraint 'PK_dbo.CompanyDtoes'.
Cannot insert duplicate key in object 'dbo.CompanyDtoes'. The
duplicate key value is (b20a140d-440b-4a41-b2c3-6763fa752246). The
statement has been terminated.
PersonDto
public class PersonDto : PartnerDto, IPartner
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string BirthPlace { get; set; }
public string MothersName { get; set; }
public string TaxId { get; set; }
public List<CompanyDto> OwnedCompanies { get; set; }
/// <summary>
/// Partner címe(i)
/// </summary>
public List<PersonAddressDto> Addresses { get; set; }
public PersonDto()
{
OwnedCompanies = new List<CompanyDto>();
Addresses = new List<PersonAddressDto>();
}
}
CompanyDto
public class CompanyDto : PartnerDto, IPartner
{
public string TaxNumber { get; set; }
public int CompanyValue { get; set; }
public List<PersonDto> Owners { get; set; }
/// <summary>
/// Partner címe(i)
/// </summary>
public List<CompanyAddressDto> Addresses { get; set; }
public CompanyDto()
{
Owners = new List<PersonDto>();
Addresses = new List<CompanyAddressDto>();
}
}
My DBContext:
public class PartnerDBContext : DbContext
{
public DbSet<PersonDto> Persons { get; set; }
public DbSet<CompanyDto> Companies { get; set; }
public DbSet<AddressDto> Addresses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PersonDto>()
.HasKey(k => k.PartnerId);
modelBuilder.Entity<CompanyDto>()
.HasKey(k => k.PartnerId);
modelBuilder.Entity<AddressDto>()
.HasKey(k => k.ID);
}
}
I try to insert a new person, that contains some references for already exists companies:
public bool InsertPerson(PersonDto personToInsert)
{
try
{
using (var db = new PartnerDBContext())
{
db.Persons.Add(personToInsert);
db.SaveChanges();
}
}
catch (Exception ex)
{
return false;
}
return true;
}
My problem that, I can't insert it, cos it writes Violation of primary key for CompanyDto. I know it's already exists and I don't want to add a new one, but how should I add it? I use it in a WCF service, that called from UWP. Unfortunately can't use DataAnnonations using from UWP (it's a bug), so I use ModelBuilder...
public bool InsertPerson(PersonDto personToInsert)
{
try
{
using (var db = new PartnerDBContext())
{
var companies = personToInsert.OwnedCompanies;
personToInsert.OwnedCompanies = new List<CompanyDto>();
foreach (var company in companies)
{
var companyInDb = db.Companies.Find(company.PartnerId);
personToInsert.OwnedCompanies.Add(companyInDb);
}
db.Persons.Add(personToInsert);
db.SaveChanges();
}
}
catch (Exception ex)
{
return false;
}
return true;
}
I found that solution if I get the companies from the db it saves everything as I wanted.
The problem is that when you add the person entity to the context, it also adds the related entities and marks them as Added (i.e. new). The SaveChanges() in turn tries to insert them in the database and you get the duplicate PK exception.
The solution you posted works, but involves unnecessary database trips for retrieving the related objects. Since you know they are existing, you can avoid that by simply attaching them to the context in advance, which will mark them as Unchanged (i.e. existing). Then SaveChanges will insert only the person record and the links.
using (var db = new PartnerDBContext())
{
foreach (var company in personToInsert.OwnedCompanies)
db.Companies.Attach(company);
db.Persons.Add(personToInsert);
db.SaveChanges();
}
Alternatively you can mark them as Unchanged after adding the person to the context:
using (var db = new PartnerDBContext())
{
db.Persons.Add(personToInsert);
foreach (var company in personToInsert.OwnedCompanies)
db.Entry(company).State = EntityState.Unchanged;
db.SaveChanges();
}
In my test (latest EF6.1.3, short lived new DbContext as in the posted sample) both approaches work.

Foreign-Key References to the Same Entity

I have the below model:
class Client
{
[Key]
public int Id { get; set; }
public string Nom { get; set; }
public string Prenom { get; set; }
public Nullable<DateTime> date_naissance { get; set; }
public Sex? Sexe { get; set; }
public Client Parent { get; set; }
}
I used code first to generate my table. When I tried to save the records using the code below, I wasn't able to determine how to populate the Parent field. A Client can be a parent of other Clients.
Client client = new Client();
client.Id = int.Parse(item.ID);
client.Nom = item.Nom;
client.Prenom = item.Prenom;
client.date_naissance = DateTime.Parse(item.DateNaissance);
client.Sexe = (Sex)Enum.Parse(typeof(Sex), item.Sexe);
int parent;
bool par = int.TryParse(item.Parent, out parent);
// this does not work:
if (par)
client.Parent.Id = parent;
db.clients.Add(client);
db.SaveChanges();
If the parent Client instance is not already created, you will need to create a new one. You can always create a new Client instance and assign it the parent's Id, but the instance assigned will lack all of the other information about the parent. An example of this is below.
client.Parent = new Client() { Id = parentId };
Ideally, you will look up the parent from the context and assign it to the client:
var parent = context.Clients.Find(parentId);
if (parent != null)
{
client.Parent = parent;
}
else
{
// Handle an invalid ID
}
Additionally, I would suggest changing the Parent property to a virtual property:
public virtual Client Parent { get; set; }
This will allow you to take advantage of two useful features of the Entity Framework: lazy loading and automatic change tracking. Entities or collections of entities referenced by navigation properties specified with the virtual keyword will only be loaded the first time they are used. With the virtual keyword, accessing the Parent property the first time will load the Parent entity for that Client and return it. Without the virtual keyword, the Parent property will return null unless you explicitly load and assign a value to it.
Try making the navigation properties (i.e. Parent) virtual and don't forget to initialize them. So first you have to change your class property to:
public virtual Client Parent { get; set; }
and then in the code:
client.Parent = new Parent();
client.Parent.Id = parentId;
You may not want to pass the whole Client structure as the parent, you may pass only the integer ID to refer to the parent.
you must create an instance of the parent. or you can't set it.
class Client
{
public int Id { get; set; }
public Client Parent { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Client"/> class.
///
/// WITH PARENT
/// </summary>
/// <param name="id">The identifier.</param>
/// <param name="parent">The parent.</param>
public Client(int id, Client parent)
{
this.Id = id;
this.Parent = parent;
}
/// <summary>
/// Initializes a new instance of the <see cref="Client"/> class.
///
/// WITHOUT PARENT
/// </summary>
/// <param name="id">The identifier.</param>
public Client(int id)
{
this.Id = id;
}
}
public static void Main(string[] args)
{
Client client = new Client(1);
Client clientWithParent = new Client(2, client);
Console.Write("parent id :" + clientWithParent.Parent.Id);
}

How to create domain entities for a NoSQL application

I want to create an application that makes use of a NoSQL database in such a way to it plays nicely with my domain entities.
Right now when I create a "domain entity" for MongoDB then I have to define it something like this:
public class User
{
[BsonId]
public int Id {get;set;}
[BsonElement("Username")]
public string Username {get;set;}
[BsonElement("Email")]
public string Email {get;set;}
}
But this means my entity isn't persistence ignorant. So how can I achieve that when using a NoSQL database like MongoDB?
One way is to define a POCO class in your domain like this
namespace MyApp.Domain
{
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
}
}
and define the same class for each persistence type, for example MongoDB
namespace MyApp.MongoDb
{
public class User
{
[BsonId]
public int Id { get; set; }
[BsonElement("Username")]
public string Username { get; set; }
[BsonElement("Email")]
public string Email { get; set; }
}
}
You business logic will interact with your domain classes through repository interfaces, and when you persist data, you just need copy the instance over.
You don't have to write copying data code manually because automapper can help you with that. Alternatively, you can use my simplified code to copy data between domain classes below:
/// <summary>
/// Copy public fields from an instance of the source type TV to an instance of the destination type T.
/// A source property will be copied if there is a property in the destination type with the same name.
/// For instance, TV.Name will be copied to T.Name
/// </summary>
/// <typeparam name="T">The destination type</typeparam>
/// <typeparam name="TV">The source type</typeparam>
/// <param name="input">The source data</param>
/// <param name="existingInstance">The instance that we want to copy values to, if it is null a new instance will be created</param>
/// <returns>An instance of type T</returns>
public static T CopyFields< TV,T>(TV input, T existingInstance=null)where T:class where TV:class
{
var sourcePublicFields = typeof (TV).GetProperties();
var instance =existingInstance ?? Activator.CreateInstance<T>();
var destinationPublicFields = typeof(T).GetProperties();
Debug.WriteLine("Copying data from source type {0} to destination type {1}", typeof(TV), typeof(T));
foreach (var field in sourcePublicFields)
{
var destinationField = destinationPublicFields.FirstOrDefault(it => it.Name == field.Name);
if (destinationField == null || destinationField.PropertyType != field.PropertyType)
{
Debug.WriteLine("No Destination Field matched with the source field. Source Field name {0}, source field type {1} ", field.Name, field.PropertyType);
continue;
}
var sourceValue = field.GetValue(input);
//Set the value
destinationField.SetValue(instance,sourceValue);
}
return instance;
}
Your entities can be POCO objects.
Almost all your rules you can resolve by using ConvetionRegistry.
Fox example this code set Id as string and ignore extra fields.
var convention = new ConventionPack {
new IgnoreExtraElementsConvention(true),
new IdGeneratorConvention() };
ConventionRegistry.Register("CubeConventions", convention, x => true);
public class IdGeneratorConvention : ConventionBase, IPostProcessingConvention
{
public void PostProcess(BsonClassMap classMap)
{
var idMemberMap = classMap.IdMemberMap;
if (idMemberMap == null || idMemberMap.IdGenerator != null)
{
return;
}
idMemberMap.SetIdGenerator(StringObjectIdGenerator.Instance);
}
}

Sending data to the server with fnServerParams and aoData for jquery DataTable does not work in MVC4

I want to send extra data to serverside (ASP.Net MVC4) for my jquery datatable. There are many examples on how to this client side, but I can't get it to work on the serverside.
Here's the code:
javascript:
$(document).ready(function () {
var oTable = $('#myDataTable').dataTable({
"bServerSide": true,
"sAjaxSource": "SearchPatient/DataHandler",
"fnServerParams": function (aoData) {
alert('in fnServerParams');
aoData.push( { "name": "more_data", "value": "my_value" } );
}
});
});
Note: the alert goes off, so the function itself is working.
My model class:
/// <summary>
/// Class that encapsulates most common parameters sent by DataTables plugin
/// </summary>
public class JQueryDataTableParamModel
{
/// <summary>
/// fnServerparams, this should be an array of objects?
/// </summary>
public object[] aoData { get; set; }
/// <summary>
/// Request sequence number sent by DataTable, same value must be returned in response
/// </summary>
public string sEcho { get; set; }
/// <summary>
/// Text used for filtering
/// </summary>
public string sSearch { get; set; }
/// <summary>
/// Number of records that should be shown in table
/// </summary>
public int iDisplayLength { get; set; }
/// <summary>
/// First record that should be shown(used for paging)
/// </summary>
public int iDisplayStart { get; set; }
/// <summary>
/// Number of columns in table
/// </summary>
public int iColumns { get; set; }
/// <summary>
/// Number of columns that are used in sorting
/// </summary>
public int iSortingCols { get; set; }
/// <summary>
/// Comma separated list of column names
/// </summary>
public string sColumns { get; set; }
/// <summary>
/// Text used for filtering
/// </summary>
public string oSearch { get; set; }
}
and finally my Controller:
public ActionResult DataHandler(JQueryDataTableParamModel param)
{
if (param.aoData != null)
{
// Get first element of aoData. NOT working, always null
string lSearchValue = param.aoData[0].ToString();
// Process search value
// ....
}
return Json(new
{
sEcho = param.sEcho,
iTotalRecords = 97,
iTotalDisplayRecords = 3,
aaData = new List<string[]>() {
new string[] {"1", "IE", "Redmond", "USA", "NL"},
new string[] {"2", "Google", "Mountain View", "USA", "NL"},
new string[] {"3", "Gowi", "Pancevo", "Serbia", "NL"}
}
},
JsonRequestBehavior.AllowGet);
}
Note: the action handler gets hit, so the ajax call to get data is also working and my datatable gets filled with 3 rows..
The problem is: aoData is always null. I expect the first element to hold "my_value".
Any help is much appreciated!
After searching for hours to find the answer finally posted it here. Only to come up with the answer in minutes:
This does the trick:
Add this line serverside in the DataHandler:
var wantedValue = Request["more_data"];
So the value is in the request and not in the model.
Thanks.
The value(s) are indeed in the model, but they are passed as individual fields, not as elements of aoData:
public class JQueryDataTableParamModel {
/// The "more_data" field specified in aoData
public string more_data { get; set; }
public string sEcho { get; set; }
public string sSearch { get; set; }
public int iDisplayLength { get; set; }
public int iDisplayStart { get; set; }
public int iColumns { get; set; }
public int iSortingCols { get; set; }
public string sColumns { get; set; }
public string oSearch { get; set; }
}
Usage:
public ActionResult DataHandler(JQueryDataTableParamModel param) {
/// Reference the field by name, not as a member of aoData
string lSearchValue = param.more_data;
return Json(
new {
sEcho = param.sEcho,
iTotalRecords = 97,
iTotalDisplayRecords = 3,
aaData = new List<string[]>() {
new string[] {"1", "IE", "Redmond", "USA", "NL"},
new string[] {"2", "Google", "Mountain View", "USA", "NL"},
new string[] {"3", "Gowi", "Pancevo", "Serbia", "NL"}
}
},
JsonRequestBehavior.AllowGet
);
}
I will post another answer just to show a way to avoid code duplication.
You can also make your JQueryDataTableParamModel as a base class for others. Datatables will send the custom data in the same object, so you can't make the model bind it directly, unless your C# View Model matches exactly the DataTables sent object.
This can be achieved as #SetFreeByTruth answered, but you could have some code duplication if you want it to be used in a whole project. This table has more_data, what if you have another table with only a property called custom_data? You would need to fill your object with multiple fields or create several datatables view models, each with your custom data.
For your scenario, you could use inheritance. Create a new class like this:
//Inheritance from the model will avoid duplicate code
public class SpecificNewParamModel: JQueryDataTableParamModel
{
public string more_data { get; set; }
}
Using it like this in a controller:
public JsonResult ReportJson(SpecificNewParamModel Model)
{
//code omitted code for clarity
return Json(Return);
}
If you send the DataTables request and inspect the Model, you can see that it's filled with your custom data (more_data) and the usual DataTables data.

Categories