Entity Framework Core with undefined/dynamic columns - c#

I have a db with some tables in which items contains some localized string. The plan is to have an ID to a dedicated "Localization" table.
public class User
{
public int Id {get; set; }
public string Name { get; set; }
public Localization Signature { get; set; }
}
public class Item
{
public int Id {get; set; }
...
public Localization Title { get; set; }
}
public class Localization
{
public int Id { get; set; }
public string En { get; set; }
public string Fr { get; set; }
public string De { get; set; }
}
This works. But the goal is now to have a "dynamic" list of languages, so we could easy extend the localization to other languages.
I can solve this with a JSON field in which I serialize the languages string, but it has the disadvantage to lose the readability in any DB Viewer. So if possible, I would like to have real columns.
Is there any way to solve this use case with EF Core ?

I'd approach this not by adding columns to the database (which requires a change to the DB structure) but by adding rows:
public class Item
{
public int Id {get; set; }
...
public string Title { get; set; }
}
public class Localization
{
public int Id { get; set; }
public int ItemId { get; set; }
public string LanguageCode { get; set; }
public string Text { get; set; }
}
Then load item's title along the lines of:
item.Title = context.Localizations
.Where(l => l.ItemId == item.Id && l.LanguageCode == "en")
.FirstOrDefault();
(You can also load Title in the same query where you load Item... that query is meant to be illustrative of the concept).

Related

Searching in json array

{
"medic":[
{
"ace":[
{
"name":"lisinopril",
"strength":"10 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}
],
"anti":[
{
"name":"nitroglycerin",
"strength":"0.4 mg Sublingual Tab",
"dose":"1 tab",
"route":"SL",
"sig":"q15min PRN",
"pillCount":"#30",
"refills":"Refill 1"
}
],
"anticoag":[
{
"name":"warfarin sodium",
"strength":"3 mg Tab",
"dose":"1 tab",
"route":"PO",
"sig":"daily",
"pillCount":"#90",
"refills":"Refill 3"
}
],
}
]
}
class Program
{
static void Main(string[] args)
{
// ""reporttype"":""post"",
string jsonString = #"..."; //The above json
Console.WriteLine("Enter the Medication name in which you want to Find STRENGTH value :");
string medicname = Console.ReadLine();
var rootInstance = JsonConvert.DeserializeObject<Rootobject>(jsonString);
}
}
var result = rootInstance.medications[0].Where(x=>x.name == medicname ).Select(t => t.strength).ToList();
But when i run the above query, I get this below error:
'Medication' does not contain a definition for 'Where' and no accessible extension method 'Where' accepting a first argument of type 'Medication' could be found (are you missing a using directive or an assembly reference?)
I have added all necessary namespaces to my code.
and Here is my object class
public class Rootobject
{
public List<Medication> medications { get; set; }
}
public class Medication
{
public List<aceInhibitors> aceinhibitors { get ; set ; }
public List<anti> antianginal {get; set; }
public List<anticoag> anticoagulants {get; set; }
}
public class aceInhibitors
{
[JsonProperty("name")]
public string name { get; set; }
[JsonProperty("strength")]
public string strength { get; set; }
[JsonProperty("dose")]
public string dose { get; set; }
[JsonProperty("route")]
public string route { get; set; }
[JsonProperty("sig")]
public string sig { get; set; }
[JsonProperty("pillCount")]
public string pillCount { get; set; }
[JsonProperty("refills")]
public string refills { get; set; }
}
public class anti
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
public class anticoag
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
Your Medication object itself is not searchable. Instead it holds a bunch of list and each contains a different type (where all properties are the same). So maybe you should use some base class for the medicine and add another property to your Medication class. In that case you would have a class layout something like this:
public class Rootobject
{
public List<Medication> medications { get; set; }
}
public class Medication
{
public List<aceInhibitors> aceinhibitors { get; set; }
public List<antianginal> antianginal { get; set; }
public List<anticoagulants> anticoagulants { get; set; }
public List<betaBlocker> betablocker { get; set; }
public List<diuretic> diuretic { get; set; }
public List<Mineral> mineral { get; set; }
public IEnumerable<Medicine> Medicines => Enumerable.Empty<Medicine>()
.Concat(aceinhibitors)
.Concat(antianginal)
.Concat(anticoagulants)
.Concat(betablocker)
.Concat(diuretic)
.Concat(mineral);
}
public class Medicine
{
[JsonProperty("name")]
public string name { get; set; }
[JsonProperty("strength")]
public string strength { get; set; }
[JsonProperty("dose")]
public string dose { get; set; }
[JsonProperty("route")]
public string route { get; set; }
[JsonProperty("sig")]
public string sig { get; set; }
[JsonProperty("pillCount")]
public string pillCount { get; set; }
[JsonProperty("refills")]
public string refills { get; set; }
}
public class aceInhibitors : Medicine
{
}
public class antianginal : Medicine
{
}
public class anticoagulants : Medicine
{
}
public class betaBlocker : Medicine
{
}
public class diuretic : Medicine
{
}
public class Mineral : Medicine
{
}
And prepared with that you could now ask something like that:
var result = rootInstance.medications[0].Medicines
.Where(x => x.name == medicname)
.Select(t => t.strength)
.ToList();
If the model of the classes really matches your desires is up to you, but it should give you starting point.
If you want it more inline you could also do something like this:
public class Medication : IEnumerable<Medicine>
{
public List<aceInhibitors> aceinhibitors { get; set; }
public List<antianginal> antianginal { get; set; }
public List<anticoagulants> anticoagulants { get; set; }
public List<betaBlocker> betablocker { get; set; }
public List<diuretic> diuretic { get; set; }
public List<Mineral> mineral { get; set; }
public IEnumerator<Medicine> GetEnumerator()
{
return Enumerable.Empty<Medicine>()
.Concat(aceinhibitors)
.Concat(antianginal)
.Concat(anticoagulants)
.Concat(betablocker)
.Concat(diuretic)
.Concat(mineral)
.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
And in that case you could write something like this:
var result = rootInstance.medications[0]
.Where(x => x.name == medicname)
.Select(t => t.strength)
.ToList();
Your domain model is bit suboptimal as it was pointed out by Oliver. If you need to stick to this model, then you can do the following.
Introduce an interface for fields that are interesting from your query point of view:
public interface InterestingFields
{
string name { get; }
string strength { get; }
}
Each medication class can be easily adjusted to implement it, like:
public class Mineral: InterestingFields
{
public string name { get; set; }
public string strength { get; set; }
public string dose { get; set; }
public string route { get; set; }
public string sig { get; set; }
public string pillCount { get; set; }
public string refills { get; set; }
}
Make the properties of the Medication class queryable
var properties = typeof(Medication).GetProperties()
.Where(prop => prop.PropertyType.IsGenericType
&& prop.PropertyType.GetGenericTypeDefinition() == typeof(List<>)
&& typeof(InterestingFields).IsAssignableFrom(prop.PropertyType.GetGenericArguments()[0]))
.ToList();
I've used reflection where the property's type is a List<T> and T is assignable to InterestingFields
Go through the properties, retrieve the actual value and do the filtering based on that
var medication = rootInstance.medications[0];
var result = from property in properties
let collection = property.GetValue(medication) as IEnumerable<InterestingFields>
let element = collection?.ToArray().First()
where element?.name == medicname
select element.strength;
Console.WriteLine(result.First());
Proper design would lead to a separation from the data handling and the way that your data is stored. This way, it is easy to reuse the stored data for other handling, it is easier to unit test the data handling with test code, you can change the way that the data is stored, to for instance a CSV file, or XML, without having to change the data handling code.
So you need a class Medication:
class Medication
{
public string Name {get; set;}
public string Strength {get; set;}
public string Dose {get; set;}
... // etc.
}
Consider to change Dose and Strength to a numerical value.
Apparently you have stored all Medications somewhere. A proper software design would hide where it is stored, and what format it is stored in. All you know is, that you can store Medications in it, and fetch it back later, even after your program is restarted. Such a storage is often called a Repository:
class MedicationRepository
{
public IEnumerable<Medication> ReadMedications() {...}
}
The actual implementation is up to you. I think you'll use Nuget Package NewtonSoft Json for this. Maybe you also want methods to Add / Change / Remove Medications?
Consider to let the Repository class implement IEnumerable<Medication>, or even ICollection<Medication>, depending on what is most efficient in your case.
class MedicationRepository : IEnumerable<Medication>
{
public IEnumerator<Medication> GetEnumerator()
{
return this.ReadMedications().GetEnumerator();
}
...
}
Now that you've got a method to read all Medications, we can get back to your LINQ problem:
I need get input string from user(which is medication name in json) i need to check if input matches the name in medication and need to display corresponding strength value.
So you've got a procedure to read the medication name:
public string ReadMedicationName() {...}
And you want the Strength of all Medications with this name.
MedicationRepository medications = ...
string requestedMedicationName = this.ReadMedicationName();
string medicationStrength = medications
.Where(medication => medication.Name == requestedMedicationName)
.Select(medication => medication.Strength)
.FirstOrDefault();
In words: from all Medications, keep only those Medications that have a name that equals requestedMedicationName. If the name is unique, then there will be zero or one Medication left. From all remaining Medications, take only the value of property Strength, and take the first strength, or null if there is no Medication with this Name at all.
Can it be that there are several Medications with this name? Which one do you want in that case, just any Strength (= .FirstOrDefault()), all Strengths (= ToList())? In the latter case: how do you distinguish which Medication with this name contains which Strength? Consider to Select more properties in that case.
Conclusion
By separating the storage of the data and how you get the requested Medication Name from the data handling, it is easier to change the storage (to XML, to CSV, to a database), and it is easier to unit test the LINQ using specific test data.
Similarly: you've hidden how you get the name of the requested Medication: is it a DOS prompt? Did you read it from a file? Maybe you've changed it to a WinForms application and you read it from a Textbox, or a ComboBox. Because you separated, the LINQ doesn't have to change, and can be reused in several platforms.

Accessing a single field in a foreach using Linq Dapper

I am using dapper and I want to use Linq to be able to update a single field called status in one table I am trying to use.
public async Task<Int32> ProcessUnprocessedTransactions(IEnumerable<BomComponentData> items)
{
IEnumerable<BomComponentData> _itemstoBeProcesed = items.Where(w => w.Status == (int)BomStatus.Scanned);
foreach (BomComponentData item in _itemstoBeProcesed)
{
item.Status = (int)BomStatus.Completed;
}
return await database.UpdateAsync(_itemstoBeProcesed);
}
My class is as follows:
public class BomComponentData
{
public int Sequence { get; set; }
public string BOMShortName { get; set; }
public string OperationName { get; set; }
public long BomId { get; set; }
public long BOMLineID { get; set; }
public long StockItemID { get; set; }
public string BomLineType { get; set; }
public int Quantity { get; set; }
public long UnitID { get; set; }
public decimal? MultipleOfBaseUnit { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
public long ProductGroupID { get; set; }
public string ProductGroupCode { get; set; }
public int Status { get; set; }
public int BinLocation { get; set; }
public string BinName { get; set; }
public string UOM { get; set; }
public int CalculatedValue { get; set; }
public int BomPickedCount { get; set; }
public int TotalLeftTopick
{
get { return Quantity - BomPickedCount; }
}
public enum BomStatus
{
Listed=1,
Scanned=2,
Confirmed=3,
Processed=4,
Completed=4,
InVisible=5
}
public override string ToString()
{
return Code;
}
}
But it does not work if I use a foreach like above. I am sure it should update the items properly but I think that because I'm going through singular items in my foreach and my list in the update it's not updating correct.
All I want to do is mark the items as completed and ready for transfer, I am doing so by the status column and an int enum.
Maybe I am missing a declaration of what is my primary key?
Edit 2
When I use a key declaration of the primary key I get the following:
Unhandled Exception: System.AggregateException: One or more errors occurred. (Constraint
Edit 3
I have set key of my class but as you see I have auotincrement on my db and it still crashes. How should insert be handled?
Edit 4
For example I am inserting into the database as follows. Shouldn't this work?
List<BomComponentData> _bomList = new List<BomComponentData>();
_bomList.Add(new BomComponentData { Sequence = 1, Barcode = "0000000001498", BinLocation = 23, BinName = "A", BOMShortName = "GYNE-TXX", OperationName = "Example Product", Code = "TB9175CEA", Name = "Tiburon Peri/Gynae Pack-10", Quantity = 1, UOM = "Each" });
await database.InsertAllAsync(_bomList,true);
I have placed the tag key for the update that works ok but when I attempt to do an insert with the key it doesn't it says constraint error but the update works. Does anybody no how i can solve both the insert and update in Dapper contrib.
You are using Dapper.Contrib and this extension requires that you decorate your class with some attributes to help in the automatic handling of your data.
In particular for an Update method you need to define the Table attribute and the Key attribute to identify the primary key
[Table ("BomComps")]
public class BomComponentData
{
// [ExplictKey]
[Key]
public int Sequence { get; set; }
....
Here, for example, I have added the attribute Table to set a possible table name on the database, but if the name of the physical table matches the name of the class then you can omit the attribute. Also I have added the Key attribute to set the property that contains the primary key of your table (so the statement that updates your records could be formed with the proper WHERE condition).
Notice that the Key attribute should be used if the column has an Identity property set to yes while, if not, you use the ExplicitKey attribute to signal that the primary key is defined by your code.
This was actually the issue I had to decoate my class with the following Leaving this here so that anyone else has issue I was using the pcl libary but for some reason dapper contribe did not detect the key element it had to be declared as follows.
[SQLite.PrimaryKey, SQLite.AutoIncrement]
public class StockTransferArgs
{
[SQLite.PrimaryKey, SQLite.AutoIncrement]
public int StockTransferArgsId { get; set; }
public long StockItemID { get; set; }
public string Code { get; set; }
public string OperationName { get; set; }
public string Description { get; set; }
public decimal Quantity { get; set; }
public long SourceWarehouseID { get; set; }
public string SourceBinName { get; set; }
public long TargetWarehouseID { get; set; }
public string TargetBinName { get; set; }
public string Reference { get; set; }
public string SecondReference { get; set; }
public string BarCode { get; set; }
public int Status { get; set; }
}

with list of objects updating wrongly

Here is the class
public class CartManage
{
public int prodId { get; set; }
// public string prodId { get; set; }
public int qty { get; set; }
public string color { get; set; }
public string size { get; set; }
public string Name { get; set; }
public string ShortDescription { get; set; }
public string Specification { get; set; }
public double Price { get; set; }
public string skew { get; set; }
public string weight { get; set; }
public string maxqty { get; set; }
public string productimage { get; set; }
public string prd_vendor_id { get; set; }
public string prdDeliveryDays { get; set; }
public bool GiftingEnabled { get; set; }
public int GiftingId { get; set; }
}
I use this to manage cart in my website i.e List < CartManage > prodList_temp. Now when a person increase the quantity of an item in cart. I add another object in the list(I used to just increase the quantity of the object but due to some functionality i have to implement i need separate objects). I did this by doing a linq query on the list and just adding the result to cart.
var session_updating_data = prodList_temp.Where(p => p.skew == reqired_skew);
prodList_temp.Add(session_updating_data.FirstOrDefault());
The problem im having is that after inserting when ever change anyting in one of the copies the changes are reflected in all the copies. for example i have two quantity of item A, i.e the cart contains two objects containing details of item a created as shown above. Now if I change the giftingEnabled property of any one of the objects it gets reflected in both. Now i found a work around for this by creating a new object. but i would like to know why this weird phenomenon is happening for future reference.
var session_updating_data = prodList_temp.Where(p => p.skew == reqired_skew);
prodList_temp.Add(session_updating_data.FirstOrDefault());
You are passing a reference to that object to your list, so any change in the object will be reflected everywhere. Where as when you create a new object, it's a separate instance.

C# Linq search inside object linked property

i'm trying to do some search inside some attributes of my object set but i'm getting some trouble on the right way to mount my linq query, i have my VT_Video class which has its attributes and some linked objects
public partial class VT_Video
{
public int ID { get; set; }
public string title { get; set; }
public string description { get; set; }
public virtual ICollection<VT_VideoTag> VT_VideoTag { get; set; }
}
public partial class VT_VideoTag
{
public int ID { get; set; }
public int tagID { get; set; }
public int videoID { get; set; }
public virtual VT_Tag VT_Tag { get; set; }
public virtual VT_Video VT_Video { get; set; }
}
public partial class VT_Tag
{
public int ID { get; set; }
public string name { get; set; }
public virtual ICollection<VT_VideoTag> VT_VideoTag { get; set; }
}
What i want to accomplish is search a user given word inside my Video collection by VT_Video.title, VT_Video.description and also by VT_Video.VT_VideoTag.VT_Tag.name, what i managed to do so far is only search the title and description:
var myVideos = db.VT_Video.Include("VT_VideoTag")
.Include("VT_VideoTag.VT_Tag")
.Where(vid =>
vid.descricao.Contains(strBusca) ||
vid.titulo.Contains(strBusca)).ToList();
Now, i know i can do what i want with some foreach and extra code but i wondered if it would be possible to do it using linq and also keep my code clean.
Thanks.
I have not worked with LINQ to SQL much, but it seems like .Any() would satisfy your requirement:
var myVideos = db.VT_Video.Include("VT_VideoTag")
.Include("VT_VideoTag.VT_Tag")
.Where(vid =>
vid.descricao.Contains(strBusca) ||
vid.titulo.Contains(strBusca) ||
vid.VT_VideoTag.Any(tag => tag.name.Contains(strBusca))).ToList();
Notice I added this clause:
vid.VT_VideoTag.Any(tag => tag.name.Contains(strBusca))
Which returns true if any tag in the collection has a name that contains your search string.

Updating List<T> in DbContext

I have a Model like this
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<string> SolvedBy { get; set; }
}
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
}
and then Controller like this. But I cannot update the List "SolvedBy", the next time I step through with the debugger, the list is still empty.
[HttpPost]
public string Index(string flag = "", int id=0)
{
Challenge challenge = db.Challenges.Find(id);
if (flag == challenge.Flag)
{
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<string>();
}
chall.SolvedBy.Add(User.Identity.Name);
db.Entry(chall).State = EntityState.Modified;
db.SaveChanges();
//congrats, you solved the puzzle
return "got it";
}
else
{
return "fail";
}
}
is there any way around it to make a list of strings kept in the database?
EF don't know how to store an array in database table so it just ignore it. You can create another table/entity or use XML/JSON to store the list. You can serialize the list before saving and deserialize it after loading from database
A List<T> in a model would normally map to a second table, but in your DbContext you only have a single table. Try adding a second table.
public class ChallengeDBContext : DbContext
{
public DbSet<Challenge> Challenges { get; set; }
public DbSet<Solution> Solutions {get; set;}
}
public class Challenge
{
public int ID { get; set; }
public string Name { get; set; }
public string Blurb { get; set; }
public int Points { get; set; }
public string Category { get; set; }
public string Flag { get; set; }
public List<Solution> SolvedBy { get; set; }
}
public class Solution
{
public int ID { get; set; }
public string Name { get; set; }
}
Then your controller can use code along the lines of...
var chall = db.Challenges.Find(id);
if (chall.SolvedBy == null)
{
chall.SolvedBy = new List<Solution>();
}
chall.SolvedBy.Add(new Solution {Name=User.Identity.Name});
None of the above has been tested and I may have made some mistakes there, but the general principle I want to illustrate is the fact that you need another table. The List<T> represents a JOIN in SQL.

Categories

Resources