How to add distinct value in database using Entity Framework - c#

IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
}
}
db.SaveChanges();
I want to add only distinct values to database in above code. Kindly help me how to do it as I am not able to find any solution.

IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
var a = db.WebsiteWebPages.Where(i => i.WebPage == value.WebPage.ToString()).ToList();
if (a.Count == 0)
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
db.SaveChanges();
}
}
}
This is the code that I used to add distinct data.I hope it helps

In addition to the code sample Furkan Öztürk supplied, Make sure your DB has a constraint so that you cannot enter duplicate values in the column. Belt and braces approach.

I assume that by "distinct values" you mean "distinct value.WebPage values":
// get existing values (if you ever need this)
var existingWebPages = db.WebsiteWebPages.Select(v => v.WebPage);
// get your pages
var webPages = GetWebPages().Where(v => v.WebPage.Contains(".htm"));
// get distinct WebPage values except existing ones
var distinctWebPages = webPages.Select(v => v.WebPage).Distinct().Except(existingWebPages);
// create WebsiteWebPage objects
var websiteWebPages = distinctWebPages.Select(v =>
new WebsiteWebPage { WebPage = v, WebsiteId = websiteid});
// save all at once
db.WebsiteWebPages.AddRange(websiteWebPages);
db.SaveChanges();

Assuming that you need them to be unique by WebPage and WebSiteId
IEnumerable<WebsiteWebPage> data = GetWebPages();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
if (db.WebsiteWebPages.All(c=>c.WebPage != value.WebPage|| c.WebsiteId != websiteid))
{
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
db.WebsiteWebPages.Add(pagesinfo);
}
}
}
db.SaveChanges();
UPDATE
To optimize this (given that your table contains much more data than your current list), override your equals in WebsiteWebPage class to define your uniqueness criteria then:
var myWebsiteWebPages = data.select(x=> new WebsiteWebPage { WebPage = x.WebPage, WebsiteId = websiteid}).Distinct();
var duplicates = db.WebsiteWebPages.Where(x=> myWebsiteWebPage.Contains(x));
db.WebsiteWebPages.AddRange(myWebsiteWebPages.Where(x=> !duplicates.Contains(x)));
this is a one database query to retrieve ONLY duplicates and then removing them from the list

You can use the following code,
IEnumerable<WebsiteWebPage> data = GetWebPages();
var templist = new List<WebsiteWebPage>();
foreach (var value in data)
{
if (value.WebPage.Contains(".htm"))
{
WebsiteWebPage pagesinfo = new WebsiteWebPage();
pagesinfo.WebPage = value.WebPage;
pagesinfo.WebsiteId = websiteid;
templist.Add(pagesinfo);
}
}
var distinctList = templist.GroupBy(x => x.WebsiteId).Select(group => group.First()).ToList();
db.WebsiteWebPages.AddRange(distinctList);
db.SaveChanges();
Or you can use MoreLINQ here to filter distinct the list by parameter like,
var res = tempList.Distinct(x=>x.WebsiteId).ToList();
db.WebsiteWebPages.AddRange(res);
db.SaveChanges();

Related

Object multiple reference IEntityTracker error while saving audit table

first I know this question has been asked but I really couldn't find an answer nor find the root of the problem so maybe a someone points me in the right direction.
I'm having the An entity object cannot be referenced by multiple instances of IEntityChangeTracker. error when trying to save into the log tables.
for the log table, I'm using
https://github.com/thepirat000/Audit.NET/tree/master/src/Audit.EntityFramework
so inside my DbContext class where I define the dbset, I have to override the onscopecreated function
the problem here is that when context.Savechanges run for the first audit record for each table it works but after first record, I get the multiple reference error.
so let's say I have the following tables
Languages table. with the following values
English,French,German
Countries Table with the following values
UK,France,Germany
for languages table, if I change English to English3 and save it works It records to the audit table but then for languages table, I can not do any changes at any records it's the same in every table
what am I missing?
private void SaveToLogTable(AuditScope auditScope)
{
foreach (var entry in ((AuditEventEntityFramework)auditScope.Event).EntityFrameworkEvent.Entries)
{
if(entry.Action is null) return;
if (TABLES.Any(x => x.T_TABLE_NAME.Equals(entry.Table)))
{
var newLog = new LOGS
{
LOG_ACTION = ACTIONS.FirstOrDefault(x => x.A_DESC == entry.Action)?.A_CODE,
LOG_DATE = DateTime.Now,
USERS = MyGlobalSettings.MyUser
};
if (entry.Changes != null)
{
foreach (var changes in entry.Changes)
{
var ch = new CHANGES
{
CH_COLUMN = changes.ColumnName,
CH_NEW_VALUE = changes.NewValue.ToString(),
CH_ORIGINAL_VALUE = changes.OriginalValue.ToString()
};
newLog.CHANGES.Add(ch);
}
}
if (entry.ColumnValues != null)
{
foreach (var kv in entry.ColumnValues)
{
var val = new VALUES
{
ColumnName = kv.Key,
ColumnValue = kv.Value.ToString()
};
newLog.VALUES.Add(val);
}
}
TABLES.First(x => x.T_TABLE_NAME.Equals(entry.Table)).LOGS.Add(newLog);
}
else
{
var table = new TABLES {T_TABLE_NAME = entry.Table};
var newLog = new LOGS
{
LOG_ACTION = ACTIONS.FirstOrDefault(x => x.A_DESC.Equals(entry.Action))?.A_CODE,
LOG_DATE = DateTime.Now,
LOG_USER_REFNO = MyGlobalSettings.MyUser.U_ROWID
//USERS = MyGlobalSettings.MyUser
};
if (entry.Changes != null)
{
foreach (var changes in entry.Changes)
{
var ch = new CHANGES
{
CH_COLUMN = changes.ColumnName,
CH_NEW_VALUE = changes.NewValue.ToString(),
CH_ORIGINAL_VALUE = changes.OriginalValue.ToString()
};
newLog.CHANGES.Add(ch);
}
}
if (entry.ColumnValues != null)
{
foreach (var kv in entry.ColumnValues)
{
var val = new VALUES
{
ColumnName = kv.Key,
ColumnValue = kv.Value is null? "": kv.Value.ToString()
};
newLog.VALUES.Add(val);
}
}
table.LOGS.Add(newLog);
//TABLES.Attach(table);
//TABLES.First(x => x.T_TABLE_NAME.Equals(entry.Table)).LOGS.Add(newLog);
TABLES.Add(table);
//TablesList.Add(table);
}
//entry.Entity
}
}

Stepping out of foreach after finding right productID

Each of our products are based on a certain categoryID
When looping through our products it has to first find the product within that category (which it does) and then step out of it. The problem is that when it has gone through the foreach the first time (finding the right prouducts) it does not step out, it just keeps going through it finding the remainding products with other categoryIDs.
How do we solve this?
(We are using C# - Entity Framework - MSSQL)
CHEERS!
public ActionResult Index(int id)
{
var m = new Models.Product.Index();
//m.DisplayName = "Produkter";
//var scp = db.SCPconnection.FirstOrDefault(s => s.CategoryID == id);
//if (id == scp.ProductID)
{
foreach (var item in db.Product.OrderBy(p => p.ProductName))
{
var mp = new Models.Product.ModelProduct();
mp.SectorName = item.ProductName;
mp.ProductID = item.ProductID;
mp.DetailsUrl = item.Details;
m.AllProducts.Add(mp);
}
}
return View(m);
}
Just break it.
foreach (var item in db.Product.OrderBy(p => p.ProductName))
{
var mp = new Models.Product.ModelProduct();
mp.SectorName = item.ProductName;
mp.ProductID = item.ProductID;
mp.DetailsUrl = item.Details;
m.AllProducts.Add(mp);
if(condition) //if it is needed
break;
}
It is better to filter using Where clause for a particular ID.
I would strongly suggest Optimized code, why would you require to get all Products details to business logic, instead query for what is needed with a condition.
foreach (var item in db.Product.Where(p => p.ProductID.Equals(requiredID)).OrderBy(p => p.ProductName))
{
var mp = new Models.Product.ModelProduct();
mp.SectorName = item.ProductName;
mp.ProductID = item.ProductID;
mp.DetailsUrl = item.Details;
m.AllProducts.Add(mp);
}
The break keyword immediately exits the foreach at that point. Code after the break and remaining iteration items will NOT be processed.
public ActionResult Index(int id)
{
var m = new Models.Product.Index();
foreach (var item in db.Product.OrderBy(p => p.ProductName))
{
var mp = new Models.Product.ModelProduct();
mp.SectorName = item.ProductName;
mp.ProductID = item.ProductID;
mp.DetailsUrl = item.Details;
m.AllProducts.Add(mp);
if (item.ProductID == id)
break;
}
}
return View(m);
}

How to avoid repeating when I get data from stored procedure?

I get a list from stored procedure with its child date I want to avoid repeating when I add items to the the list
Here is my code:
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = "[dbo].[T]";
db.Database.Connection.Open();
var reader1 = cmd.ExecuteReader();
var objectContext1 = ((IObjectContextAdapter)db).ObjectContext;
var usersLi = objectContext1.Translate<GetAllNewsForUser>(reader1).ToList();
List<GetAllNewsForUser> lll = new List<GetAllNewsForUser>();
foreach (var item in usersLi)
{
NewsInfo newsInfo = new NewsInfo();
GetAllNewsForUser g = new GetAllNewsForUser();
g.UserName = item.UserName;
NewsInfo nnn = new NewsInfo();
nnn.NewsTitle = item.NewsTitle;
nnn.NewsId = item.NewsId;
g.NewsInfos.Add(nnn);
lll.Add(g);
}
are there any unique identifiers for g, for example a user Id. in which case you could easily use the following:
if (!lll.Any(x=>x.UserId ==g.UserId)) //include system.linq
{
lll.Add(g);
}
You could also look at modifying your stored procedure to not return duplicates.
EDIT ----
Just twigged as to what you actually after, sorry.....
Its just a rough draft but i think this is what you require.
List<GetAllNewsForUser> lll = new List<GetAllNewsForUser>();
foreach (var item in usersLi)
{
NewsInfo nnn = new NewsInfo();
nnn.NewsTitle = item.NewsTitle;
nnn.NewsId = item.NewsId;
if (!lll.Any(x=> x.UserName == item.UserName)
{
GetAllNewsForUser g = new GetAllNewsForUser();
g.UserName = item.UserName;
g.NewsInfos.Add(nnn);
lll.Add(g);
}
else
{
lll.Where(x=>x.UserName == item.Username).FirstOrDefault().NewsInfos.Add(nnn)
}
}
From what I understand so far, you are trying to avoid adding same rows in the list(based on value of some column).
LINQ might help here.
usersLi.GroupBy(e => new {
UserID = e.UserUD
}).Select(g => g.First());

Trying to find the quickest (least costly) way to loop values in from a .csv through two tables

The application I am building allows a user to upload a .csv file, which will ultimately fill in fields of an existing SQL table where the Ids match. First, I am using LinqToCsv and a foreach loop to import the .csv into a temporary table. Then I have another foreach loop that loops the fields from the temporary table into an existing table where the Ids match. The only way I have gotten this to work consistently and successfully is nesting the second foreach loop within the first:
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new TestEntities();
foreach (var item in model)
{
var tc = new TemporaryCsvUpload
{
Id = item.Id,
CreditInvoiceAmount = item.CreditInvoiceAmount,
CreditInvoiceDate = item.CreditInvoiceDate,
CreditInvoiceNumber = item.CreditInvoiceNumber,
CreditDeniedDate = item.CreditDeniedDate,
CreditDeniedReasonId = item.CreditDeniedReasonId,
CreditDeniedNotes = item.CreditDeniedNotes
};
entity.TemporaryCsvUploads.Add(tc);
var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id);
foreach (var number in idMatches)
{
number.CreditInvoiceDate = tc.CreditInvoiceDate;
number.CreditInvoiceNumber = tc.CreditInvoiceNumber;
number.CreditInvoiceAmount = tc.CreditInvoiceAmount;
number.CreditDeniedDate = tc.CreditDeniedDate;
number.CreditDeniedReasonId = tc.CreditDeniedReasonId;
number.CreditDeniedNotes = tc.CreditDeniedNotes;
}
}
entity.SaveChanges();
entity.Database.ExecuteSqlCommand("TRUNCATE TABLE TemporaryCsvUpload");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
The issue is speed. It takes about 1 minute and 49 seconds to search through an SQL table of 7000 entries, match the ids, and fill in the fields.
So, I looked at this and thought that the second loop really didn't need to be nested. I switched up the code like so:
[HttpPost]
public ActionResult UploadValidationTable(HttpPostedFileBase csvFile)
{
var inputFileDescription = new CsvFileDescription
{
SeparatorChar = ',',
FirstLineHasColumnNames = true
};
var cc = new CsvContext();
var filePath = uploadFile(csvFile.InputStream);
var model = cc.Read<Credit>(filePath, inputFileDescription);
try
{
var entity = new TestEntities();
var tc = new TemporaryCsvUpload();
foreach (var item in model)
{
tc.Id = item.Id;
tc.CreditInvoiceAmount = item.CreditInvoiceAmount;
tc.CreditInvoiceDate = item.CreditInvoiceDate;
tc.CreditInvoiceNumber = item.CreditInvoiceNumber;
tc.CreditDeniedDate = item.CreditDeniedDate;
tc.CreditDeniedReasonId = item.CreditDeniedReasonId;
tc.CreditDeniedNotes = item.CreditDeniedNotes;
entity.TemporaryCsvUploads.Add(tc);
}
var idMatches = entity.Authorizations.ToList().Where(x => x.Id == tc.Id);
foreach (var number in idMatches)
{
number.CreditInvoiceDate = tc.CreditInvoiceDate;
number.CreditInvoiceNumber = tc.CreditInvoiceNumber;
number.CreditInvoiceAmount = tc.CreditInvoiceAmount;
number.CreditDeniedDate = tc.CreditDeniedDate;
number.CreditDeniedReasonId = tc.CreditDeniedReasonId;
number.CreditDeniedNotes = tc.CreditDeniedNotes;
}
entity.SaveChanges();
entity.Database.ExecuteSqlCommand("TRUNCATE TABLE TemporaryCsvUpload");
TempData["Success"] = "Updated Successfully";
}
catch (LINQtoCSVException)
{
TempData["Error"] = "Upload Error: Ensure you have the correct header fields and that the file is of .csv format.";
}
return View("Upload");
}
This time around, it only took 19 seconds to complete. A vast improvement on the first. But when I checked the database, only one row of the 7 that should match was filled in. Can anybody spot a reason why the second code block would not be filling in all the rows it should be? Or a better way to optimize the first block? Thanks!

How can I write a LINQ to SQL query to update tags?

I have an image site where users can tag photos much like you can tag a question on Stackoverflow.
I have the following tables:
Images [ID, URL, etc]
Tags [ID, TagName]
ImageTag [TagID, ImageID]
I want to write a method with the signature:
public void UpdateImageTags(int imageId, IEnumerable<string> currentTags)
This method will do the following:
Create any new Tags in currentTags that don't already exist in the Tags table.
Get the old ImageTag's for an image.
Delete any ImageTag's that no longer exist in the currentTags
Add any ImageTag's that are new between the currentTags and oldTags.
Here is my attempt at that method:
public void UpdateImageTags(int imageId, IEnumerable<string> currentTags)
{
using (var db = new ImagesDataContext())
{
var oldTags = db.ImageTags.Where(it => it.ImageId == imageId).Select(it => it.Tag.TagName);
var added = currentTags.Except(oldTags);
var removed = oldTags.Except(currentTags);
// Add any new tags that need created
foreach (var tag in added)
{
if (!db.Tags.Any(t => t.TagName == tag))
{
db.Tags.InsertOnSubmit(new Tag { TagName = tag });
}
}
db.SubmitChanges();
// Delete any ImageTags that need deleted.
var deletedImageTags = db.ImageTags.Where(it => removed.Contains(it.Tag.TagName));
db.ImageTags.DeleteAllOnSubmit(deletedImageTags);
// Add any ImageTags that need added.
var addedImageTags = db.Tags.Where(t => added.Contains(t.TagName)).Select(t => new ImageTag { ImageId = imageId, TagId = t.TagId });
db.ImageTags.InsertAllOnSubmit(addedImageTags);
db.SubmitChanges();
}
}
However, this fails on the line:
db.ImageTags.DeleteAllOnSubmit(deletedImageTags);
With the error:
Local sequence cannot be used in LINQ to SQL implementations of query
operators except the Contains operator.
Is there an easier way I can handle the operation of adding new tags, deleting old ImageTags, adding new ImageTags in LINQ to SQL?
Seems like this would be easiest
public void UpdateImageTags(int imageId, IEnumerable<string> currentTags)
{
using (var db = new ImagesDataContext())
{
var image = db.Images.Where(it => it.ImageId == imageId).First()
image.Tags.Clear();
foreach(string s in currentTags)
{
image.Tags.Add(new Tag() { TagName = s});
}
db.SubmitChanges();
}
}
This might have to be modified slightly for LinqtoSQL. EF is what i have been using most recently. Also this is dependent on Lazy loading being enabled. If it is not, you will have to force the include of the image tags.
Here is a helper method to deal with many-to-many relationships:
public static void UpdateReferences<FK, FKV>(
this EntitySet<FK> refs,
Expression<Func<FK, FKV>> fkexpr,
IEnumerable<FKV> values)
where FK : class
where FKV : class
{
Func<FK, FKV> fkvalue = fkexpr.Compile();
var fkmaker = MakeMaker(fkexpr);
var fkdelete = MakeDeleter(fkexpr);
var fks = refs.Select(fkvalue).ToList();
var added = values.Except(fks);
var removed = fks.Except(values);
foreach (var add in added)
{
refs.Add(fkmaker(add));
}
foreach (var r in removed)
{
var res = refs.Single(x => fkvalue(x) == r);
refs.Remove(res);
fkdelete(res);
}
}
static Func<FKV, FK> MakeMaker<FKV, FK>(Expression<Func<FK, FKV>> fkexpr)
{
var me = fkexpr.Body as MemberExpression;
var par = Expression.Parameter(typeof(FKV), "fkv");
var maker = Expression.Lambda(
Expression.MemberInit(Expression.New(typeof(FK)),
Expression.Bind(me.Member, par)), par);
var cmaker = maker.Compile() as Func<FKV, FK>;
return cmaker;
}
static Action<FK> MakeDeleter<FK, FKV>(Expression<Func<FK, FKV>> fkexpr)
{
var me = fkexpr.Body as MemberExpression;
var pi = me.Member as PropertyInfo;
var assoc = Attribute.GetCustomAttribute(pi, typeof(AssociationAttribute))
as AssociationAttribute;
if (assoc == null || !assoc.DeleteOnNull)
{
throw new ArgumentException("DeleteOnNull must be set to true");
}
var par = Expression.Parameter(typeof(FK), "fk");
var maker = Expression.Lambda(
Expression.Call(par, pi.GetSetMethod(),
Expression.Convert(Expression.Constant(null), typeof(FKV))), par);
var cmaker = maker.Compile() as Action<FK>;
return cmaker;
}
Usage:
IEnumerable<Tag> values = ...;
Image e = ...;
e.ImageTags.UpdateReferences(x => x.Tag, tags);

Categories