I have a multiple selection form that needs to be saved into the database. Users can edit their selections on their subsequent visits. The items are defined in a table with unique IDs so the form only pass back a list of IDs.
It is something like:
Fruits:
{ id=1, name="apple" }
{ id=2, name="orange" }
{ id=3, name="banana" }
People:
{ id=1, name="user", fruits=apple,orange }
In DB there is a link table linking the id of people and id of fruits.
Now when I receive a request to edit, I'll need to compare with existing fruits so I know whether I need to add or delete an entry.
foreach (var fruit in existing_fruits)
{
if (post_fruits.Where(e => e.id == fruit.id).Count() == 0) user.Fruits.Remove(fruit);
}
foreach (var fruit in post_fruits)
{
if (existing_fruits.Where(e => e.id == fruit.id).Count() == 0)
{
var entity = context.Fruit.Where(e => e.id == fruit.id);
user.Fruits.Add(entity);
}
}
As you can see there are multiple loops and multiple which call on the lists, which makes me wonder if there is a cleaner way in doing this?
There are a lot of useful functions in EntityFramework.Extended. It contains Batch Update and Delete feature which can be useful in your situation, it also eliminates the need to retrieve and load an entity before modifying it, so it can increase your performance in future.
What if you use .Any method here (although it still uses two loops but it is more efficient) :
foreach (var fruit in existing_fruits)
if (!post_fruits.Any(e => e.id == fruit.id)) user.Fruits.Remove(fruit);
foreach (var fruit in post_fruits)
{
if (existing_fruits.Any(e => e.id == fruit.id)) continue;
var entity = context.FirstOrDefault(e => e.id == fruit.id);
if(entity != null) user.Fruits.Add(entity);
}
But it's better to change DB architecture to:
Fruits:
{ id=1, name="apple" }
{ id=2, name="orange" }
{ id=3, name="banana" }
People:
{ id=1, name="user" }
PeopleFruits
{ id=1, fruitId = 1, personId = 1, isSelected = 0}
All you need to update records now is to get this PeopleFruits entities of some person.
PeopleFruits[] personPplFruits = cont.PeopleFruits.Where(pf => pf.personId == 1).ToArray();
And update .isSelected properties according to what user has selected.
Check this article please : https://www.simple-talk.com/sql/database-administration/how-to-get-database-design-horribly-wrong/
Related
I'm working on a project where various files are to be uploaded to a platform. The platform has over 100 DocumentTypeIds. The files have no set naming convention. So in order to determine a file's DoumentTypeId I'm currently doing this in a method that returns a string
if (fileName.Contains("401k") || fileName.Contains("401(k)") || fileName.Contains("457b") || fileName.Contains("457(b)") || fileName.Contains("retire"))
{
return "401k-and-retirement";
}
else if (fileName.Contains("aflac"))
{
return "aflac";
}
else if ( fileName.Contains("beneficiary form") || fileName.Contains("beneficiaries") || fileName.Contains("beneficiary")
)
{
return "beneficiary-forms";
}
else if (fileName.Contains("benefit enrollment") || fileName.Contains("benefits enrollment") || fileName.Contains("benefit form") || fileName.Contains("benefits form") || fileName.Contains("benefit paperwork") || fileName.Contains("qualifying event") || fileName.Contains("enrollment") || fileName.Contains("b enroll") || fileName.Contains("benefit enrollnent")) //yes, the typo is on purpose. there are typos in some of the file names to import
{
return "benefits-election";
}
//etc
As you can imagine, this method is disgustingly ugly and long (~300 lines). I want to refactor this and make use of a database. I'm thinking of having a table with two fields DocumentTypeId and FileNameContains where FileNameContains is a comma-separated list of the OR case strings. This would allow for adding any cases without doing any code changes.
What I'm unsure about is how to do a string.Contains() comparison on a database. I'm aware of LIKE but that's not quite the same as string.Contains(). I've also thought about querying the database to convert FileNameContains field into an array or List for each record and doing an extension method(something like this) that loops through and does a string.Contains(). But that doesn't seem very efficient and fast.
Am I approaching this wrong? I just know there has to be a better way than a bunch of else if statements with OR cases. And I really think having a database would make this more elegant and scalable without any code changes and purely SQL UPDATE statements. Some help and input would be greatly appreciated.
I'd use a dictionary, or list of keyvaluepair.. the key being "find this", the value being "the file type"
var d = new Dictionary<string, string>{
{ "401k", "401k-and-retirement" },
{ "401(k)", "401k-and-retirement" },
{ "457b", "401k-and-retirement" },
{ "457(b)", "401k-and-retirement" },
{ "retire", "401k-and-retirement" },
{ "aflacs", "aflacs" },
...
};
foreach(var kvp in d)
if(filename.Contains(kvp.Key)) return kvp.Value;
Add more entries to your list/dict, or even fill it from a db
What I'm unsure about is how to do a string.Contains() comparison on a database
Well, you could transport this same concept into the db and store your values like this in your table:
Find, Ret
%401k%, 401k-and-retirement
%401(k)%, 401k-and-retirement
And query like:
SELECT ret FROM table WHERE #pFilename LIKE Find
With a c# side parameter of
//adjust type and size to match your column
command.Parameters.Add("#pFilename", SqlDbType.VarChar, 50).Value = "my401k.txt";
Or whatever equivalent you'll use in Dapper, EF etc..
context.FindRets.FirstOrDefault(fr => EF.Functions.Like(filename, fr.Find))
For the love of a relevant deity, please don't store a CSV in a table column. It will bite you sooner than later
I would usually do something like this:
var contains = new []
{
new
{
find = new [] { "401k", "401(k)", "457b", "457(b)", "retire" },
result = "401k-and-retirement"
},
new { find = new [] { "aflac" }, result = "aflac" },
new
{
find = new [] { "beneficiary form", "beneficiaries", "beneficiary" },
result = "beneficiary-forms"
},
new
{
find = new []
{
"benefit enrollment", "benefits enrollment", "benefit form", "benefits form", "benefit paperwork",
"qualifying event", "enrollment", "b enroll", "benefit enrollnent"
},
result = "benefits-election"
},
};
return
contains
.Where(x => x.find.Any(f => fileName.Contains(f)))
.Select(x => x.result)
.FirstOrDefault();
The advantage is that it's easier to add and maintain the items you're looking for. It's all on one portion of the screen.
You could go one step further and save this away in a text file that looks like this:
401k-and-retirement
401k
401(k)
457b
457(b)
retire
aflac
aflac
beneficiary-forms
beneficiary form
beneficiaries
beneficiary
benefits-election
benefit enrollment
benefits enrollment
benefit form
benefits form
benefit paperwork
qualifying event
enrollment
b enroll
benefit enrollnent
Then you can do this:
var contains =
File
.ReadLines("config.txt")
.Aggregate(
new[] { new { find = new List<string>(), result = "" } }.ToList(),
(a, x) =>
{
if (x.StartsWith(' '))
{
a.Last().find.Add(x.Substring(1));
}
else
{
a.Add(new { find = new List<string>(), result = x });
}
return a;
}, a => a.Skip(1).ToArray());
contains.Dump();
return
contains
.Where(x => x.find.Any(f => fileName.Contains(f)))
.Select(x => x.result)
.FirstOrDefault();
Now you can just add more items to the config file as you need.
I have a list of action type objects. Each object has a list of departments.
I have a user and a user object has n number of deparments.
I would like to get a list of user actions that the user can take. Here is what I came up with:
List<Item> userActions = new List<Item>();
foreach (var actionType in actionTypelist)
{
foreach (var dept in actionType.availableDepts)
{
if (data.currentUser.secondaryServDepts.Where(x => x.Id == dept.servDeptId).Count() > 0)
{
userActions.Add(actionType);
}
}
}
Can you please suggest a better way to get a quicker result? Thanks in advance.
One possible optimisation instead of:
if (data.currentUser.secondaryServDepts.Where(x => x.Id == dept.servDeptId).Count() > 0)
{
userActions.Add(actionType);
}
Use:
if (data.currentUser.secondaryServDepts.Any(x => x.Id == dept.servDeptId))
{
userActions.Add(actionType);
}
Count will enumerate the entire collection while Any will stop at the first match (since all you need is at least one item)
Below is my class :
public partial class Ads
{
public int Id { get; set; }
public int RegionId { get; set; }
public string Name { get; set; }
public int Group { get; set; }
}
Records :
Id Name Group
1 abc 1
2 xyz 1
3 lmn 1
4 xxx 2
5 ppp 2
6 ttt 3
7 ggg 3
Now I want to remove all records/only that record with particular id of same group for some ids.
Code :
public void Delete(int[] ids,bool flag = false)
{
using (var context = new MyEntities())
{
context.Ads.RemoveRange(
context.Ads.Where(t => (flag ?
(context.Ads.Any(x => ids.Contains(x.Id) && x.Group == t.Group)) : false)));
context.SaveChanges();
}
}
What I am trying to do is something like below :
If flag is false with ids=3,5 then
I want to delete only records with Id=3,5
Else if flag is true with ids=3,5 then
I want to delete records with Id=3,5 but all other records too of the group to which ids=3,5 belong to.
Here id=3 belongs to group 1 so I want to delete all records of group1 i.e id=1,2 like wise ids=5 belongs to
group 2 so I want to delete all records of group 2 i.e id=4.
Expected output for this last case(flag=true) :
Id Name Group
6 ttt 3
7 ggg 3
But I think that I haven't done this is a proper way, and there is some source of improvement in the query.
Note : ids[] will always contains ids from different group and that too highest ids from different group.
How can I to improve my query for both the cases(flag=true and false)?
What about
var removeRecs=context.Ads.where(t => ids.contains(t.id))
if(flag)
removeRecs.AddRange(context.Ads.where(t=> removeRecs.Any(r =>t.groupId==r.Id)))
Ads.RemoveRange(removeRecs);
Do not make it too hard for your self, not everything must/can be done in the where statement of a query. Also a general rule of thumb in a loop try to factor out all the constant values and checks. So try this:
public static void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var query = context.Ads.AsQueryable();
query = flag
? query.Where(x => context.Ads
.Where(i => ids.Contains(i.Id))
.Select(i => i.Group)
.Contains(x.Group))
: query.Where(x => ids.Contains(x.Id));
context.Ads.RemoveRange(query);
context.SaveChanges();
}
}
public void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var items = context.Ads.Where(x => ids.Any(a => x.Id == a));
if (!flag)
{
//flag=false --> delete items with Id in ids[]
context.Ads.RemoveRange(items);
}
else
{
var groups = items.GroupBy(a => a.Group).Select(a => a.Key);
//flag=true --> delete all items in selected groups
context.Ads.RemoveRange(context.Ads.Where(x => groups.Any(a => x.Group == a)));
}
context.SaveChanges();
}
You should separate your tasks...
if (flag)
{
groupIds = db.Ads.Where(x => ids.Contains(x.Id)).Select(x => x.Group).ToList();
db.Ads.RemoveRange(db.Ads.Where(x => groupIds.Contains(x.Group)).ToList());
}
else
{
db.Ads.RemoveRange(db.Ads.Where(x => ids.Contains(x.Id)).ToList());
}
To me it looks like you have two different deletes here.
In first case you are only deleting the ads with given ID and this is pretty straight forward.
In second case you are deleting the ads with given ID and all other ads that contain the group of the recently deleted Ads. So in this case instead of deleting the ads with given Id first why not actualy get distinct groups for these ID-s and than just delete the groups.
EDIT
You can do it like this.
using (var context = new TestEntities())
{
if (!flag)
context.Ads.RemoveRange(context.Ads.Where(a => ids.Contains(a.Id)));
else
context.Ads.RemoveRange(context.Ads.Where(a => context.Ads.Where(g => ids.Contains(g.Id)).Select(x => x.Group).Distinct().Contains(a.Group)));
context.SaveChanges();
}
For the more complicated case I am trying to get distinct groups for given id-s. So for ID-s 3 and 5 I am selecting the groups and than I am doing distinct on the groups since it might happen that the id-s have the same group. Than I am fetching all the ads that have these groups. So for passed values of 3 and 5 I would get groups 1 and 2 which I would than use to get all the ads that have that group. That in turn would yield id-s 1, 2, 3, 4, and 5 which I would than delete.
EDIT 2
If the complexity of second Linq query bothers you than write a SQL query.
context.Database.ExecuteSqlCommand(
"DELETE Ads WHERE Group IN (SELECT Group FROM Ads WHERE Id IN(#p1, #p2))", new SqlParameter("#p1", ids[0]), new SqlParameter("#p2", ids[1]));
This should be extra performant rather than rely on EF which will delete it one by one.
I am trying to remove category object from News but it doesn't work
My code looks like this:
var OriginalCategoriesIds = db.News.Where(w => w.NewsId == 1)
.SelectMany(v => v.Categories)
.ToList();
News NewsToUpdate = new News() { NewsId = 1 };
db.News.Attach(NewsToUpdate);
foreach (var category in OriginalCategoriesIds)
{
if (!model.SelectedCategoriesIds.Contains(category.CategoryId))
{
NewsToUpdate.Categories.Remove(category);
}
}
db.SaveChanges();
Here is your issue:
var OriginalCategoriesIds = db.News.Where(w => w.NewsId == 1).SelectMany(v => v.Categories).ToList();
News NewsToUpdate = new News() { NewsId = 1 };
db.News.Attach(NewsToUpdate);
foreach (var category in OriginalCategoriesIds)
{
if (!model.SelectedCategoriesIds.Contains(category.CategoryId))
{
db.News.Categories.Remove(category);// <---change like this
}
}
db.SaveChanges();
EF should be smart enough to look at your updated entity and know which ones to remove based on which ones are present. Instead of checking which ones don't belong anymore, check which ones do. In theory it should properly sync up then. Also, most likely you also want to check which ones were added that weren't there. Instead of loading existing category ids from the news, just load all the ids that are now attached and then add them all in.
News NewsToUpdate = new News() { NewsId = 1 };
var updatedCategoryIds = model.SelectedCategoriesIds;
NewsToUpdate.Categories.AddRange(db.Categories.Where(c => updatedCategoryIds.Contains(c.CategoryId));
db.News.Attach(NewsToUpdate);
db.SaveChanges();
Question: what is the LINQ-to-Entity code to insert an order for a specific customer?
Update
Here is the a solution (see one of the submitted answers below for a much cleaner solution):
using (OrderDatabase ctx = new OrderDatabase())
{
// Populate the individual tables.
// Comment in this next line to create a new customer/order combination.
// Customer customer = new Customer() { FirstName = "Bobby", LastName = "Davro" };
// Comment in this line to add an order to an existing customer.
var customer = ctx.Customers.Where(c => c.FirstName == "Bobby").FirstOrDefault();
Order order = new Order() { OrderQuantity = "2", OrderDescription = "Widgets" };
// Insert the individual tables correctly into the hierarchy.
customer.Orders.Add(order);
// Add the complete object into the entity.
ctx.Customers.AddObject(customer);
// Insert into the database.
ctx.SaveChanges();
}
Your code isn't far off. Just change your second line to read as follows:
Customer customer = ctx.Customer.FirstOrDefault(c => c.FirstName == "Bobby");
if (customer != null)
{
//...
Just replace the c.FirstName == "Bobby" with something that can strongly identify the customer you're looking for (e.g. c.Id == customerID if you already know what the ID is).
Notice that Order has a Customer property. You don't have to add the Order to the Customer -- you can do it the other way around. So, instead of creating a new Customer, get the Customer using Linq, then add it to your new Order.
using (OrderDatabase ctx = new OrderDatabase())
{
ctx.AddOrder(new Order()
{
OrderQuantity = 2,
OrderDescription = "Widgets",
Customer = ctx.Customers.First<Customer>(c => c.CustomerId == yourId)
});
ctx.SaveChanges();
}
I don't get what the problem is, exactly.
var mycustomer = context.Customers.Where(x => x.id == 100).FirstOrDefault();
if(mycustomer != null)
{
mycustomer.Orders.Add(myorder);
}
context.SaveChanges();
L2E does not support set-based operations currently (update without select). See Use linq to generate direct update without select