Dynamic Where Clause in EF Core 2.2 - c#

I want to dynamicly add a where clause to my LINQ query. I have the filter property name and the filter property value, so I need to build something like this:
var assignmentListQuery = context.Assignments;
if (!string.IsNullOrWhiteSpace(bookingStep.FilterPropertyName) && !string.IsNullOrWhiteSpace(bookingStep.FilterPropertyValue))
{
assignmentListQuery = assignmentListQuery.Where(item => PROPERTYNAME == PROPERTYVALUE)
}
ar assignmentList = await assignmentListQuery.ToListAsync();
I've tried to get the propertyinfo of the property, which seems not to me here.
var item = context.Set<Assignment>().First();
object value = item.GetType().GetProperty(bookingStep.FilterPropertyName).GetValue(item, null);
Has anyone an idea on how to create this kind of where clause?
public class Assignment
{
/// <summary>
///
/// </summary>
[Display(Name = nameof(Id))]
public int Id { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = nameof(OrderNumber))]
public string OrderNumber { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = nameof(ScheduledLoading))]
public DateTime ScheduledLoading { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = nameof(CustomerOrderNumber))]
public string CustomerOrderNumber { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = nameof(ArticleNumber))]
public string ArticleNumber { get; set; }
/// <summary>
///
/// </summary>
[Display(Name = nameof(Comment))]
public string Comment { get; set; }
[Display(Name = nameof(CustomerId))]
public int? CustomerId { get; set; }
[Display(Name = nameof(Customer))]
public virtual Customer Customer { get; set; }
}
That's the Assignment entity, the FilterPropertyName is e.g. "CustomerOrderNumber"

Thanks everyone. I got it working using
List<Assignment> assignmentList;
if (!string.IsNullOrWhiteSpace(bookingStep.FilterPropertyName) &&
!string.IsNullOrWhiteSpace(bookingStep.FilterPropertyValue))
{
assignmentList = await
assignmentListQuery.Where(e =>
EF.Property<string>(e, bookingStep.FilterPropertyName) ==
bookingStep.FilterPropertyValue).ToListAsync();
}
else
{
assignmentList = await assignmentListQuery.ToListAsync();
}

Building off #Doppelmoep answer (which saved me, btw), you can also use the special EF Core EF.Functions.Like extension to dynamically generate SQL Like statements with dynamic PROPERTYNAMEs and dynamic SQL Like statements (with wildcards - %_[^] are valid wildcard characters in a SQL like):
assignmentList = await
assignmentListQuery.Where(e =>
EF.Functions.Like(EF.Property<string>(e, bookingStep.FilterPropertyName),
bookingStep.FilterPropertyValue)).ToListAsync();

Related

Newtonsoft Json serialize dynamic property back to respective T Type

I have a class that I am converting into a json string and saving. The class contains 2 dynamic properties which are of some type T. In my case its LdrEntity. Now when I am deserializing and getting the object back, it is assigning a json string to that dynamic property. Is there a way to specify to serilialize the dynamic property back to LdrEntity instead of the json string. Please help.
Here is my class (Please see LdrReagentEntity and BciReagentEntity properties)
using Domain.Model.Shared.Entity.Panel;
using System.Collections.Generic;
namespace Authoring.DataAccess.DataModel
{
/// <summary>
/// Cocktail reagent structure to coordinate data.
/// </summary>
public class CocktailReagent
{
/// <summary>
/// Id.
/// </summary>
public string Id { get; set; }
/// <summary>
/// Reagent type.
/// </summary>
public ReagentType Type { get; set; }
/// <summary>
/// Reagent name.
/// </summary>
public string ActualName { get; set; }
/// <summary>
/// Reagent name.
/// </summary>
public int Version { get; set; }
/// <summary>
/// Stability days.
/// </summary>
public int StabilityDays { get; set; }
/// <summary>
/// Number of tests.
/// </summary>
public int NumberOfTests { get; set; }
/// <summary>
/// Reagent comments.
/// </summary>
public string Comments { get; set; }
/// <summary>
/// Cocktail reagent Items.
/// </summary>
public List<CocktailReagentItem> SelectedReagents { get; set; }
}
public class CocktailReagentItem
{
public string Id { get; set; }
public uint Index { get; set; }
public uint Volume { get; set; }
public dynamic LdrReagentEntity { get; set; } //The original type is LdrEntity
public dynamic BciReagentEntity { get; set; } //The original type is LdrEntity
}
}
The two dynamic properties when converted are of type LdrEntity.
So when I convert to json string, I do the following.
public class LdrExportObj
{
public List<CocktailReagent> CocktailReagents { get; set; }
public string HashCode { get; set; }
}
public void Export()
{
var exportObj = new LdrExportObj
{
CocktailReagents = listOfCocktailReagents, //These are the list of items I am saving
HashCode = HashCodeHelper.GenerateHashCode(str),
};
var exportStr = JsonConvert.SerializeObject(exportObj, Formatting.Indented); //I get the json string and save it.
//Save to file
}
When I Import I say
public void Import()
{
var obj = JsonConvert.DeserializeObject<LdrExportObj>(str);
var cocktailsStr = JsonConvert.SerializeObject(obj.CocktailReagents, Formatting.Indented); //If I expand the object and see the dynamic properties, its a json string.
return new LdrExportObj()
{
CocktailReagents = obj.CocktailReagents,
HashCode = obj.HashCode,
};
}
When I debug, its not serializing the internal json string back to LdrEntity, instead its assigning a json string to the dynamic property, See the BciReagentEntity Property in the screenshot. I want it to be conveted back to the original LdrEntity type.

Why IFormFile is null in nested object?

I've got these models
public sealed class UpdateFacilityReportFirstStepCommand : ICommand<ExecutionResult>
{
// other props
/// <summary>
/// Gets or sets the hired staff.
/// </summary>
/// <value>The hired staff.</value>
public List<HiredStaffUpsertModel> HiredStaff { get; set; } = new List<HiredStaffUpsertModel>();
}
public class HiredStaffUpsertModel
{
// other props
/// <summary>
/// Gets or sets the insurance card file.
/// </summary>
/// <value>The insurance card file.</value>
public HiredStaffCarInsuranceCardModel CarInsuranceCardFile { get; set; } = new HiredStaffCarInsuranceCardModel();
}
public class HiredStaffCarInsuranceCardModel
{
/// <summary>
/// Gets or sets the file.
/// </summary>
/// <value>The file.</value>
[FileValidator("pdf,doc,docx", 10 * 1024 * 1024, true)]
public IFormFile File { get; set; }
/// <summary>
/// Gets or sets a value indicating whether this instance is changed.
/// </summary>
/// <value><c>true</c> if this instance is changed; otherwise, <c>false</c>.</value>
public bool IsChanged { get; set; }
}
And in my controller I'm expecting public [FromForm] UpdateFacilityReportFirstStepCommand command.
That's how I send form in Postman (content-type: multipart/form-data):
And that's what I get:
I have no idea why my File is null, although bool IsChanged is received. My frontend developer said that he'll send me form keys like on the Postman screenshot, and I don't get why it works fine with primitive types and doesn't with files.
The way I found to solve it, it's same as yours.
public class Purchase
{
public string Customer { get; set; }
public IEnumerable<Product> Products { get; set; }
}
public class Product
{
public string ProductCode { get; set; }
public IFormFile ProductImage { get; set; }
}
You have to send it like Products[0].ProductImage, otherwise you'll get null in ProductImage.

Implementing history tracking in SaveChanges override

I am currently trying to implement history tracking on all of my tables in my app in a generic way by overriding the SaveChanges method and making use of reflection. As a simple case, let's say I have 2 classes/dbsets for my domain objects and a history table for each like the following:
DbSet<Cat> Cats { get; set; }
DbSet<CatHistory> CatHistories { get; set; }
DbSet<Dog> Dogs { get; set; }
DbSet<DogHistory> DogHistories { get; set; }
The CatHistory class looks like the following (DogHistory follows the same scheme):
public class CatHistory : HistoricalEntity
{
public int CatId { get; set; }
public virtual Cat Cat{ get; set; }
}
My Goal is when an object is saved, I would like to insert a record in the appropriate history table. I am having trouble overcoming type difference when using reflection. My current attempt is below and I seem to be stuck on the //TODO: line:
var properties = entry.CurrentValues.PropertyNames.Where(x => entry.Property(x).IsModified).ToList();
//get the history entry type from our calculated typeName
var historyType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(x => x.Name == historyTypeName);
if(historyType != null)
{
//modified entries
if (dbSet != null && historyDbSet != null && entry.State == EntityState.Modified)
{
var existingEntry = dbSet.Find(entry.Property("Id").CurrentValue);
//create history record and add entry to table
var newHistories = GetHistoricalEntities(existingEntry, type, entry);
var listType = typeof(List<>).MakeGenericType(new[] { historyType });
var typedHistories = (IList)Activator.CreateInstance(listType);
//TODO: turn newHistories (type = List<HistoricalEntity>) into specific list type (List<MyObjectHistory>) so I can addrange on appropriate DbSet (MDbSet<MyObjectHistory>)
historyDbSet.AddRange(newHistories);
}
}
You could use AutoMapper to map your historical entities. I just created a little test, hopefully it replicates your situation:
IList dogs = new List<Dog>() { new Dog { Id = 1, Name = "Alsatian" }, new Dog { Id = 2, Name = "Westie" } };
var dogHistoryType = typeof(DogHistory);
var listType = typeof(List<>).MakeGenericType(new[] { dogHistoryType });
var typedHistories = (IList)Activator.CreateInstance(listType);
mapper.Map(dogs, typedHistories);
foreach (var historyItem in typedHistories)
{
this.Add(historyItem);
}
I will try to explain the way I have implemented in my application.
I have created Models with name ending History for models for which application needs to insert before deleting the record from the original table.
BaseModel.cs
namespace ProductVersionModel.Model
{
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
/// <summary>
/// all common properties of the tables are defined here
/// </summary>
public class BaseModel
{
/// <summary>
/// id of the table
/// </summary>
[Key]
public int Id { get; set; }
/// <summary>
/// user id of the user who modified last
/// </summary>
public string LastModifiedBy { get; set; }
/// <summary>
/// last modified time
/// </summary>
public DateTime LastModifiedTime { get; set; }
/// <summary>
/// record created user id
/// </summary>
[Required]
public string CreatedBy { get; set; }
/// <summary>
/// record creation time
/// </summary>
public DateTime CreationTime { get; set; }
/// <summary>
/// Not mapped to database, only for querying used
/// </summary>
[NotMapped]
public int RowNumber { get; set; }
}
}
Product.cs
namespace ProductVersionModel.Model
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
/// <summary>
/// store detals of the product
/// </summary>
public class ProductStatus : BaseModel
{
/// <summary>
/// Name of the product
/// </summary>
[Required, MaxLength(100)]
public string Name { get; set; }
/// <summary>
/// product version validity start date
/// </summary>
public DateTime ValidFrom { get; set; }
/// <summary>
/// product version valid till
/// </summary>
public DateTime? ValidTill { get; set; }
/// <summary>
/// This field used to keep track of history of a product
/// </summary>
public int ProductNumber { get; set; }
}
}
HistoryBaseModel.cs
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ProductVersionModel.Model.History
{
public class HistroyBaseModel
{
/// <summary>
/// id of the table
/// </summary>
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string DeletedBy { get; set; }
public DateTime? DeletedTime { get; set; }
/// <summary>
/// record created user id
/// </summary>
[Required]
public string CreatedBy { get; set; }
/// <summary>
/// record creation time
/// </summary>
public DateTime CreationTime { get; set; }
}
}
ProductStatusHistory.cs
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using ProductVersionModel.Model.History;
// ReSharper disable once CheckNamespace
namespace ProductVersionModel.Model.History
{
public class ProductStatusHistory : HistroyBaseModel
{
/// <summary>
/// Name of the product
/// </summary>
[MaxLength(100)]
public string Name { get; set; }
/// <summary>
/// product version validity start date
/// </summary>
public DateTime ValidFrom { get; set; }
/// <summary>
/// product version valid till
/// </summary>
public DateTime? ValidTill { get; set; }
/// <summary>
/// This field used to keep track of history of a product
/// </summary>
public int ProductNumber { get; set; }
}
}
In Delete method of your CrudRepository
public virtual int Delete(List<object> ids, string userName)
{
try
{
foreach (var id in ids)
{
var dbObject = _table.Find(id);
HistroyBaseModel historyRecord = null;
var modelAssembly = Assembly.Load(nameof(ProductVersionModel));
var historyType =
modelAssembly.GetType(
// ReSharper disable once RedundantNameQualifier - dont remove namespace it is required
$"{typeof(ProductVersionModel.Model.History.HistroyBaseModel).Namespace}.{typeof(TModel).Name}History");
if (historyType != null)
{
var historyObject = Activator.CreateInstance(historyType);
historyRecord = MapDeletingObjectToHistoyObject(dbObject, historyObject, userName);
DatabaseContext.Entry(historyRecord).State = EntityState.Added;
}
DatabaseContext.Entry(dbObject).State = EntityState.Deleted;
}
return DatabaseContext.SaveChanges();
}
catch (DbUpdateException ex)
{
throw HandleDbException(ex);
}
}
protected virtual HistroyBaseModel MapDeletingObjectToHistoyObject(object inputObject, object outputObject, string userName)
{
var historyRecord = MapObjectToObject(inputObject, outputObject) as HistroyBaseModel;
if (historyRecord != null)
{
historyRecord.DeletedBy = userName;
historyRecord.DeletedTime = DateTime.UtcNow;
}
return historyRecord;
}
protected virtual object MapObjectToObject(object inputObject, object outputObject)
{
var inputProperties = inputObject.GetType().GetProperties();
var outputProperties = outputObject.GetType().GetProperties();//.Where(x => !x.HasAttribute<IgnoreMappingAttribute>());
outputProperties.ForEach(x =>
{
var prop =
inputProperties.FirstOrDefault(y => y.Name.Equals(x.Name) && y.PropertyType == x.PropertyType);
if (prop != null)
x.SetValue(outputObject, prop.GetValue(inputObject));
});
return outputObject;
}
Where TModel is the type of the model
public class CrudRepository<TModel> : DataAccessBase, ICrudRepository<TModel> where TModel : class, new()
public class ProductStatusRepository : CrudRepository<ProductStatus>, IProductStatusRepository
You can override methods MapDeletingObjectToHistoyObject and MapObjectToObject in your related repository if you want to map complex entitites like child element list.

My .saveChanges() method is not saving because it is saying an object reference is required for the non-static field, method, or property

I am getting an error when I call the .SaveChanges(). It is telling me an object reference is required for the non-static field, method, or property 'SongDatabase.Songs'. Here is my code for my controller. Can someone tell me why this error is occurring?
[HttpPost]
public ActionResult Add(Song song)
{
using (SongDatabase db = new SongDatabase())
{
SongDatabase.Songs.Add(song);
SongDatabase.SaveChanges();
}
return RedirectToAction("Music");
}
Here is my code for my database for the songs.
public class SongDatabase : DbContext
{
public DbSet<Song> Songs { get; set; }
public DbSet<Artist> Artists { get; set; }
public DbSet<Album> Albums { get; set; }
public SongDatabase()
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<SongDatabase>());
}
}
And lastly, here is the code for my Song class.
public class Song
{
public Song()
{
Album = new List<Album>();
}
/// <summary>
/// The Id of the song.
/// </summary>
public int SongId { get; set; }
/// <summary>
/// The name of the song.
/// </summary>
public string SongName { get; set; }
/// <summary>
/// The artist of the song.
/// </summary>
public int ArtistId { get; set; }
/// <summary>
/// The duration of the song.
/// </summary>
public double Duration { get; set; }
/// <summary>
/// Whether or not this song should be excluded when calculating the total duration of the current playlist.
/// </summary>
public bool Exclude { get; set; }
/// <summary>
/// Navigation property linking the album class to the song class.
/// </summary>
public virtual ICollection<Album> Album { get; set; }
/// <summary>
/// Navigation property linking the artist class to the song class.
/// </summary>
public virtual Artist Artist { get; set; }
}
You're trying to access public DbSet<Song> Songs { get; set; } as a static value, in your code here
[HttpPost]
public ActionResult Add(Song song)
{
using (SongDatabase db = new SongDatabase())
{
SongDatabase.Songs.Add(song);
SongDatabase.SaveChanges();
}
return RedirectToAction("Music");
}
You create an instance of SongDatabase with SongDatabase db = new SongDatabase() but you're not using that instance db. You should change it for
[HttpPost]
public ActionResult Add(Song song)
{
using (SongDatabase db = new SongDatabase())
{
db.Songs.Add(song);
db.SaveChanges();
}
return RedirectToAction("Music");
}

Deserialize into object with variable name

I hope someone can help me with this; I have just started working on a website which requires to make API calls. I use an open source library for the API calls. Most of the calls work great, but I can't get the most important one to work. The json string when deserialized returns an empty object.
JSON String:
{"benbeun":{"id":27266833,"name":"BenBeun","profileIconId":25,"summonerLevel":30,"revisionDate":1393655593000}}
Call, where responseText is the above JSON string:
public static T CreateRequest(string url)
{
var result = new T();
var getRequest = (HttpWebRequest)WebRequest.Create(url);
using (var getResponse = getRequest.GetResponse())
using (var reader = new System.IO.StreamReader(getResponse.GetResponseStream()))
{
var responseText = reader.ReadToEnd();
result = JsonConvert.DeserializeObject<T>(responseText);
}
return result;
}
Default class from the library:
public class SummonerDto
{
/// <summary>
/// Summoner ID.
/// </summary>
[JsonProperty("id")]
public long Id { get; set; }
/// <summary>
/// Summoner name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// ID of the summoner icon associated with the summoner.
/// </summary>
[JsonProperty("profileIconId")]
public int ProfileIconId { get; set; }
/// <summary>
/// Date summoner was last modified specified as epoch milliseconds.
/// </summary>
[JsonProperty("revisionDate")]
public long RevisionDate { get; set; }
/// <summary>
/// Summoner level associated with the summoner.
/// </summary>
[JsonProperty("summonerLevel")]
public long SummonerLevel { get; set; }
}
I can get the class below to work in my calls; however the 'benbeun' string is variable, so this class cannot be used.
public class Benbeun
{
public int id { get; set; }
public string name { get; set; }
public int profileIconId { get; set; }
public int summonerLevel { get; set; }
public long revisionDate { get; set; }
}
public class SummonerDto
{
public Benbeun benbeun { get; set; }
}
Any pointers? I already tried numerous options provided in other questions, but I fear my knowledge is lacking in where exactly my problem lies. I feel I am close with the code below, however it returns an empty object aswell.
public class SummonerDto
{
public IDictionary<string, Summoner> Summoner { get; set; }
}
public class Summoner
{
/// <summary>
/// Summoner ID.
/// </summary>
[JsonProperty("id")]
public long Id { get; set; }
/// <summary>
/// Summoner name.
/// </summary>
[JsonProperty("name")]
public string Name { get; set; }
/// <summary>
/// ID of the summoner icon associated with the summoner.
/// </summary>
[JsonProperty("profileIconId")]
public int ProfileIconId { get; set; }
/// <summary>
/// Date summoner was last modified specified as epoch milliseconds.
/// </summary>
[JsonProperty("revisionDate")]
public long RevisionDate { get; set; }
/// <summary>
/// Summoner level associated with the summoner.
/// </summary>
[JsonProperty("summonerLevel")]
public long SummonerLevel { get; set; }
}
Use Dictionary<string,Summoner> to deserialize.
var obj = JsonConvert.DeserializeObject<Dictionary<string,Summoner>>(json);

Categories