Entity Framework is deleting an Entry - c#

I'm fetching information from a webpage in two pages:
First page:
- Content c1 is created and a Translation c1.t1 is created;
- Content c2 is created and Translation c2.t1 is created;
Second page:
- The system detects that c1 already exists and just adds c1.t2 to the proper table;
- The system detects that c2 already exists and just adds c2.t2 to the proper table;
Somehow, on the second page, the system is overritting c1.t1 with c1.t2 and only the second translation is available on the database. When debbugging, found that it is deletting c1.t1 at some point but I couldn't figure out why.
This is my actual stuff:
EF 4.1
Code-First Aproach
DbContext
I have this POCO Entities (minimized):
RegionalContent: - It's like a tranlation and regional info about a content:
public class XBLRegionalContent
{
[Key, Column(Order = 0)]
public string ContentId { get; set; }
[ForeignKey("ContentId")]
public virtual XBLContent Content { get; set; }
[Key, Column(Order = 1)]
public string RegionId { get; set; }
[ForeignKey("RegionId")]
public virtual XBLRegion Region { get; set; }
public string Name { get; set; }
}
Content: - Unique content per GUID:
public class XBLContent
{
#region [ Properties ]
/// <summary>
/// The GUID
/// </summary>
[Key]
[StringLength(36, ErrorMessage="Must have 36 characters")]
[Required(ErrorMessage="Must have a unique GUID")]
public string GUID { get; set; }
public string Type { get; set; }
public virtual ICollection<XBLRegionalContent> RegionalInfo { get; set; }
}
Region - Pretty straight forward:
public class XBLRegion
{
[Key]
[StringLength(5, ErrorMessage="ID must have 5 characters")]
[Required]
[RegularExpression(#"[a-z]{2}-[A-Z]{2}", ErrorMessage = "ID must be in ISO 639 standard")]
public string ID { get; set; }
public string Country { get; set; }
public string Language { get; set; }
}
DbContext class has nothing different, just DbSets.
One content has many translations. One translation has one content related. The translation primary key is compound of content guid and region id.
I have a class in Model that populates the database and creates a local list that the View uses to display information. That way, I only access the Database one time to save, and don't need to retrieve information when it is saved.
Here is only the important information about this class:
public class XBLChart : IDisposable
{
XBLContentContext db = new XBLContentContext();
private string baseurl = "http://foo.bar/";
public string Locale { get; private set; }
public string HTML { get; private set; }
public string URL { get; set; }
public ContentType Type { get; private set; }
public List<XBLContent> Contents { get; set; }
public XBLChart(ContentType type, string sort, string locale)
{
Type = type;
if (sort == null)
sort = Enum.GetName(typeof(SortBy), SortBy.OfferStartDate);
if (locale != null && locale.Length == 5)
Locale = locale;
else
Locale = "en-US";
URL = baseurl + Locale + "/" + sort;
HTML = FeedUtils.RequestHTML(URL);
Contents = new List<XBLContent>();
PopulateList();
}
private void PopulateList()
{
MatchCollection itens = Regexes.ChartItems().Matches(HTML);
MatchCollection titulos = Regexes.ChartTitles().Matches(HTML);
int type = (int)Type;
int start = type * 12;
this.Title = HttpUtility.HtmlDecode(titulos[type].Groups["title"].Value);
if (titulos.Count < 8 && start > 1)
{
start = (type - 1) * 12;
type--;
}
XBLRegion region;
if (!db.XBLRegions.Any(x => x.ID == Locale))
{
region = new XBLRegion { ID = Locale };
db.XBLRegions.Add(region);
db.SaveChanges();
}
else
region = db.XBLRegions.SingleOrDefault(x => x.ID == Locale);
for (int i = start; i < (start + 2); i++)
{
string guid = itens[i].Groups["guid"].Value;
XBLContent c = new XBLContent(guid);
if (!db.XBLContents.Any(x => x.GUID == guid))
{
c.Type = Type.ToString();
c.PopularInfo(Locale);
db.XBLContents.Add(c);
}
else
c = db.XBLContents.Single(x => x.GUID == c.GUID);
XBLRegionalContent regionalcontent = new XBLRegionalContent(guid, Locale);
if (!db.XBLRegionalInfos.Any(x => x.ContentId == guid && x.RegionId == Locale))
{
if (c.HTML == null)
c.PopularInfo(Locale);
regionalcontent.Populate(c.HTML);
regionalcontent.Name = HttpUtility.HtmlDecode(itens[i].Groups["name"].Value);
db.XBLRegionalInfos.Add(regionalcontent);
}
else
regionalcontent = db.XBLRegionalInfos.Single(x => x.ContentId == guid && x.RegionId == Locale);
db.SaveChanges();
c.RegionalInfo.Clear();
regionalcontent.Region = region;
c.RegionalInfo.Add(regionalcontent);
Contents.Add(c);
}
}
}

you are missing a db.SaveChanges() after
db.SaveChanges();
c.RegionalInfo.Clear();
regionalcontent.Region = region;
c.RegionalInfo.Add(regionalcontent);

Related

Entity Framework Core: FromSqlInterpolated(). An item with the same key has already been added. Key: Id

I want to get a list of my friends' stories. It didn't work through LINQ, but I was able to write a good SQL query that really works and returns what I need. I use .NET 7 & EF Core 7.
int userId = 1;
StoryContainerRto test = await _context.StoryContainer
.FromSqlInterpolated($#"SELECT s."Id", s."AuthorId", u."Id", u."AccountHeaderUrl", u."AvatarUrl", u."Description", u."Email", u."IsAuthorOfQualityContent", u."IsDeleted", u."IsInShadowBan", u."IsVerifiedProfile", u."LinkInBio", u."MedalsAmount", u."Name", u."Nickname", u."PasswordHash", u."PasswordSalt", u."PhoneNumber", u."PhoneNumberPrefix", u."PhoneNumberVerefied", u."RegistrationDateTime", u."TelegramId", u."TelegramVerifyingChatId"
FROM ""StoryContainer"" as s
INNER JOIN ""StoryContentRefs"" as scr ON scr.""StoryId"" = s.""Id""
LEFT JOIN ""User"" as u ON u.""Id"" = s.""AuthorId""
LEFT JOIN ""StoryContentRefRtoUserRto"" as whoView ON whoView.""ViewedId"" = {userId} AND whoView.""ViewedStoriesId"" = scr.""Id""
WHERE s.""AuthorId"" IN (
SELECT u.""Id""
FROM ""UserFriend"" as f, ""User"" as u
WHERE
CASE
WHEN f.""FirstUserFriendId"" = {userId}
THEN f.""SecondUserFriendId"" = u.""Id""
WHEN f.""SecondUserFriendId"" = {userId}
THEN f.""FirstUserFriendId"" = u.""Id""
END
)
AND scr.""CreateTimestamp"" >= NOW() - '1 day'::INTERVAL
AND scr.""IsDelete"" = false").ToListAsync();
Output:
System.ArgumentException: An item with the same key has already been added. Key: Id
If you execute this code, the SQL query will be identical (given that the SQL query is lightweight. It just gets the story and the author, but does not get the rest of the information, because it is needed only for clarity)
StoryContainerRto entity = await _context.StoryContainer
.AsNoTracking()
.Include(e => e.Author)
.Where(e => e.AuthorId == 1)
.ToListAsync();
Generated SQL (note the duplication of the "Id" column):
SELECT s."Id", s."AuthorId", u."Id", u."AccountHeaderUrl", u."AvatarUrl", u."Description", u."Email", u."IsAuthorOfQualityContent", u."IsDeleted", u."IsInShadowBan", u."IsVerifiedProfile", u."LinkInBio", u."MedalsAmount", u."Name", u."Nickname", u."PasswordHash", u."PasswordSalt", u."PhoneNumber", u."PhoneNumberPrefix", u."PhoneNumberVerefied", u."RegistrationDateTime", u."TelegramId", u."TelegramVerifyingChatId"
FROM "StoryContainer" AS s
INNER JOIN "User" AS u ON s."AuthorId" = u."Id"
WHERE s."AuthorId" = 1
User table:
[Table("User")]
public class UserRto
{
[Key] public int Id { get; set; }
[Required] public string Nickname { get; set; }
public string? Email { get; set; }
[Required] public byte[] PasswordHash { get; set; }
[Required] public byte[] PasswordSalt { get; set; }
public string? Name { get; set; }
public string? Description { get; set; }
public string? LinkInBio { get; set; }
public int MedalsAmount { get; set; }
public string? AvatarUrl { get; set; }
public bool IsDeleted { get; set; }
public bool? IsVerifiedProfile { get; set; }
public bool? IsAuthorOfQualityContent { get; set; }
public bool IsInShadowBan { get; set; }
public DateTime RegistrationDateTime { get; set; }
public string? PhoneNumberPrefix { get; set; }
public string? PhoneNumber { get; set; }
public bool PhoneNumberVerefied { get; set; }
public string? TelegramId { get; set; }
public string? TelegramVerifyingChatId { get; set; }
public string? AccountHeaderUrl { get; set; }
public List<FriendInvitationRto> FriendInvitationsSent { get; set; }
public List<FriendInvitationRto> FriendInvitationsReceived { get; set; }
public List<UserFriendRto> FirstFriends { get; set; }
public List<UserFriendRto> SecondFriends { get; set; }
public List<StoryContentRefRto> ViewedStories { get; set; }
public StoryContainerRto StoryContainer { get; set; }
}
Story table:
[Table("StoryContainer")]
public class StoryContainerRto
{
public int Id { get; set; }
public int AuthorId { get; set; }
public UserRto Author { get; set; }
public List<StoryContentRefRto> Items { get; set; }
}
How do I complete the request? I understand that the fact is that 3 columns of "Id" come from three tables. But at the same time, if you look at the request generated by EF itself, then there are also 2 "Ids". How do I execute such a request correctly?
https://makolyte.com/ef-core-select-queries-involving-multiple-table/
There is an example of code using INNER JOIN like mine. I do not know why the same approach does not work for me
I also applied the AS operator to all the "Id" columns, but in that case I get this error:
The required column 'Id' was not present in the results of a 'FromSql' operation
I was able to solve this problem using a direct SQL query to the database, and then analyzed the information myself
List<StoryRto> friendsStories = new();
using (NpgsqlCommand command = (NpgsqlCommand)(_context as PotokContext)!.Database.GetDbConnection().CreateCommand())
{
command.CommandText = $#"SELECT s.*, scr.*, u.*, whoView.""ViewedId"" as ""IViewThis""
FROM ""Stories"" as s
INNER JOIN ""StoryContentRefs"" as scr ON scr.""StoryId"" = s.""Id""
INNER JOIN ""User"" as u ON u.""Id"" = s.""AuthorId""
LEFT JOIN ""StoryContentRefRtoUserRto"" as whoView ON whoView.""ViewedId"" = 1 AND whoView.""ViewedStoriesId"" = scr.""Id""
WHERE s.""AuthorId"" IN (
SELECT u.""Id""
FROM ""UserFriend"" as f, ""User"" as u
WHERE
CASE
WHEN f.""FirstUserFriendId"" = 1
THEN f.""SecondUserFriendId"" = u.""Id""
WHEN f.""SecondUserFriendId"" = 1
THEN f.""FirstUserFriendId"" = u.""Id""
END
)
AND scr.""CreateTimestamp"" >= NOW() - '1 day'::INTERVAL
AND scr.""IsDelete"" = false
LIMIT {count}
OFFSET {skipCount}";
(_context as PotokContext)!.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
if (reader.HasRows)
{
while (reader.Read())
{
// Stories table
int storyId = reader.GetInt32(0);
int authorId = reader.GetInt32(1);
// StoryContentRefs table
int scrId = reader.GetInt32(2);
int scrStoryId = reader.GetInt32(3);
string scrStoryContentRef = reader.GetString(4);
bool scrIsDelete = reader.GetBoolean(5);
DateTime scrCreateTimestamp = reader.GetDateTime(6);
// UserTable
int userId = reader.GetInt32(7);
string userNickname = reader.GetString(8);
string? userEmail = reader.IsDBNull(9) ? null : reader.GetString(9);
// password hash and salt will not be included
string? userName = reader.IsDBNull(12) ? null : reader.GetString(12);
string? userDescription = reader.IsDBNull(13) ? null : reader.GetString(13);
string? userLinkInBio = reader.IsDBNull(14) ? null : reader.GetString(14);
int userMedalsAmount = reader.GetInt32(15);
string? userAvatarUrl = reader.IsDBNull(16) ? null : reader.GetString(16);
bool userIsDeleted = reader.GetBoolean(17);
bool? userIsVerifiedProfile = reader.IsDBNull(18) ? null : reader.GetBoolean(18);
DateTime userRegistrationDateTime = reader.GetDateTime(19);
bool? userIsAuthorOfQualityContent = reader.IsDBNull(20) ? null : reader.GetBoolean(20);
string? userPhoneNumber = reader.IsDBNull(21) ? null : reader.GetString(21);
string? userPhoneNumberPrefix = reader.IsDBNull(22) ? null : reader.GetString(22);
bool userPhoneNumberVerified = reader.GetBoolean(23);
string? userTelegramId = reader.IsDBNull(24) ? null : reader.GetString(24);
string? userTelegramVerifyingChatId = reader.IsDBNull(25) ? null : reader.GetString(25);
bool userIsInShadowBan = reader.GetBoolean(26);
string? userAccountHeaderUrl = reader.IsDBNull(27) ? null : reader.GetString(27);
// WhoViewStory relation
int? iViewThis = reader.IsDBNull(28) ? null : reader.GetInt32(28);
UserRto user = new()
{
Id = userId,
Nickname = userNickname,
Email = userEmail,
Name = userName,
Description = userDescription,
LinkInBio = userLinkInBio,
MedalsAmount = userMedalsAmount,
AvatarUrl = userAvatarUrl,
IsDeleted = userIsDeleted,
IsVerifiedProfile = userIsVerifiedProfile,
RegistrationDateTime = userRegistrationDateTime,
IsAuthorOfQualityContent = userIsAuthorOfQualityContent,
PhoneNumber = userPhoneNumber,
PhoneNumberPrefix = userPhoneNumberPrefix,
PhoneNumberVerefied = userPhoneNumberVerified,
TelegramId = userTelegramId,
TelegramVerifyingChatId = userTelegramVerifyingChatId,
IsInShadowBan = userIsInShadowBan,
AccountHeaderUrl = userAccountHeaderUrl
};
List<UserRto> viewed = new();
if (iViewThis != null) viewed.Add(user);
StoryContentRefRto storyContentRef = new()
{
Id = scrId,
StoryId = scrStoryId,
ContentRefs = scrStoryContentRef,
IsDelete = scrIsDelete,
CreateTimestamp = scrCreateTimestamp,
Viewed = viewed
};
if (friendsStories.FirstOrDefault(e => e.Id == scrStoryId) == null)
{
// Create new
StoryRto story = new()
{
Id = storyId,
AuthorId = authorId,
Author = user,
Items = new() { storyContentRef }
};
friendsStories.Add(story);
}
else
{
// Add to existing
StoryRto story = friendsStories.First(e => e.Id == scrStoryId);
story.Items.Add(storyContentRef);
}
}
}
else
{
Console.WriteLine("No rows found.");
}
}
}

Elastic search nest dynamic query with object initializer NEST 5.x

Hi I'm a new to elastic nest API and I'm using nest 5.x. I'm currently developing some kind of advanced search page so when user doesn't check a criteria i don't have to include that filter on my query. I'm trying to combine 2 queries under must operator with object initializer approach using nest. How to achieve it? I'm following the example on [https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/bool-queries.html]
var secondSearchResponse = client.Search(new
SearchRequest {
Query = new TermQuery { Field = Field(p => p.Name), Value = "x" } &&
new TermQuery { Field = Field(p => p.Name), Value = "y" } });
But it doesnt work cause Field class doesnt accept type arguments.
I also tried to followed this approach from this topic
[Nest Elastic - Building Dynamic Nested Query
here is my code
public HttpResponseMessage GetSearchResult([FromUri] SearchModels queries)
{
try
{
///
string result = string.Empty;
result += "queryfields + " + queries.queryfields == null ? string.Empty : queries.queryfields;
result += "datefrom + " + queries.datefrom == null ? string.Empty : queries.datefrom;
result += "dateto + " + queries.dateto == null ? string.Empty : queries.dateto;
result += "emitentype + " + queries.emitentype == null ? string.Empty : queries.emitentype;
QueryContainer andQuery = null;
//List<QueryContainer> QueryContainers = new List<QueryContainer>();
IDXNetAnnouncement record = new IDXNetAnnouncement
{
kode_emiten = queries.kodeemiten
};
#region keyword
if (!string.IsNullOrEmpty(queries.queryfields))
{
var val = queries.queryfields;
TermQuery tq = new TermQuery
{
Field = queries.queryfields,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
#endregion keyword
#region kodeemiten
if (!string.IsNullOrEmpty(queries.kodeemiten))
{
var val = queries.kodeemiten;
TermQuery tq = new TermQuery
{
Name = "kode_emiten",
Field = record.kode_emiten,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
#endregion
#region date
if (!string.IsNullOrEmpty(queries.datefrom) && !string.IsNullOrEmpty(queries.dateto))
{
DateRangeQuery dq = new DateRangeQuery();
dq.Name = "tglpengumuman";
dq.LessThanOrEqualTo = DateMath.Anchored(queries.dateto);
dq.GreaterThanOrEqualTo = DateMath.Anchored(queries.datefrom);
dq.Format = "dd/mm/yyyy";
if (andQuery == null)
andQuery = dq;
else
andQuery &= dq;
//QueryContainers.Add(dq);
}
#endregion keyword
var reqs = (ISearchResponse<IDXNetAnnouncement>)null;
if (andQuery != null)
{
reqs = conn.client.Search<IDXNetAnnouncement>(s => s
.AllIndices()
.AllTypes()
.From(queries.indexfrom)
.Size(queries.pagesize)
.Query(q => q.Bool(qb => qb.Must(m => m.MatchAll() && andQuery))));
//var json = conn.client.Serializer.SerializeToString(reqs.ApiCall.ResponseBodyInBytes);
}
else
{
reqs = conn.client.Search<IDXNetAnnouncement>(s => s
.AllIndices()
.AllTypes()
.From(queries.indexfrom)
.Size(queries.pagesize)
.Query(m => m.MatchAll()));
}
//var reqstring = Encoding.UTF8.GetString(conn.client.);
var reslts = this.conn.client.Serializer.SerializeToString(reqs,SerializationFormatting.Indented);
var resp = new HttpResponseMessage()
{
Content = new StringContent(reslts)
};
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return resp;
}
catch (Exception e)
{
var resp = new HttpResponseMessage()
{
Content = new StringContent(e.ToString())
};
resp.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
return resp;
}
}
But that returns zero result. How to achieve this? Thx anyway.
EDIT :
This is the params variabel definition. Its apoco model of search keywords
public class SearchModels
{
public string queryfields { get; set; }
public string datefrom { get; set; }
public string dateto { get; set; }
public string emitentype { get; set; }
public string kodeemiten { get; set; }
public string issuercode { get; set; }
public int indexfrom { get; set; }
public int pagesize { get; set; }
}
IDXNetAnnouncement is a poco model of search result. Its actualy a document type which is stored on the elastic server
public class IDXNetAnnouncement
{
public string perihalpengumuman { get; set; }
public string attachments { get; set; }
public string createddate { get; set; }
public bool efekemiten_spei { get; set; }
public string jmsxgroupid { get; set; }
public string tglpengumuman { get; set; }
public object errordescription { get; set; }
public string ESversion { get; set; }
public int oldfinalid { get; set; }
public bool efekemiten_etf { get; set; }
public object errorcode { get; set; }
public string jenisemiten { get; set; }
public int pkid { get; set; }
public string judulpengumuman { get; set; }
public string form_id { get; set; }
public bool efekemiten_eba { get; set; }
public string jenispengumuman { get; set; }
public string nopengumuman { get; set; }
public string kode_emiten { get; set; }
public string divisi { get; set; }
public string EStimestamp { get; set; }
public bool efekemiten_obligasi { get; set; }
public long finalid { get; set; }
public bool efekemiten_saham { get; set; }
public string kodedivisi { get; set; }
public string SearchTerms
{
get
{
return string.Format("{0} {1} {2}", judulpengumuman, kode_emiten, nopengumuman);
}
}
}
But it doesnt work cause Field class doesnt accept type arguments.
You need to ensure that you include a using static directive for Nest.Infer i.e.
using static Nest.Infer;
with the rest of the using directives.
.Query(q => q.Bool(qb => qb.Must(m => m.MatchAll() && andQuery))));
No need to wrap in a Must(), just do
.Query(q => q.MatchAll() && andQuery)
which will wrap both queries in a bool query must clause. You also don't need to null check andQuery because NEST is smart enough to not combine the two queries if either or both are null.
if (!string.IsNullOrEmpty(queries.queryfields))
{
var val = queries.queryfields;
TermQuery tq = new TermQuery
{
Field = queries.queryfields,
Value = val
};
if (andQuery == null)
andQuery = tq;
else
andQuery &= tq;
//QueryContainers.Add(tq);
}
NEST has the concept of conditionless queries so you don't need to check it queries.queryfields is null or empty, simply build the query and add it to andQuery. So it would become
var val = queries.queryfields;
andQuery &= new TermQuery
{
Field = queries.queryfields,
Value = val
};
Aside
All of the NEST documentation is generated from source code; you can trace back to the original source file by clicking on any edit link within the documentation. This will take you to a github page, such as this one for bool queries. From here, the document contains an important note that links back to the original source.

'System.Reflection.TargetInvocationException' occurred in EntityFramework.SqlServer.dll

I'm calling this method by CascadingDropDownListFor and I'm getting an exception:
An exception of type 'System.Reflection.TargetInvocationException' occurred in EntityFramework.SqlServer.dll but was not handled in user code
public JsonResult GetRaca(string especieId)
{
int esp = Convert.ToInt32(especieId);
var rac = db.Raca.Where(c => c.EspecieId == esp).ToList();
var racas = new List<SelectListItem>();
foreach (var ra in rac)
{
var racaConteudo = db.RacaConteudo
.Where(c => c.RacaId == ra.RacaId)
.Where(c => c.IdiomaId == 1)
.First(); // <= The exception occurred here
racas.Add(new SelectListItem
{
Text = racaConteudo.RacaId.ToString(),
Value = racaConteudo.NomePopular
});
}
return Json(racas, JsonRequestBehavior.AllowGet);
}
The entity:
[Table("RacasConteudo")]
public class RacaConteudo
{
public RacaConteudo(long RacaId, string NomeCientifico, string NomePopular, long IdiomaId)
{
this.RacaId = RacaId;
this.NomeCientifico = NomeCientifico;
this.NomePopular = NomePopular;
this.IdiomaId = IdiomaId;
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long RacaConteudoId { get; set; }
[ForeignKey("RacaId")]
public virtual Raca Raca { get; set; }
public long RacaId { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Nome Cientifico")]
public string NomeCientifico { get; set; }
[DataType(DataType.Text)]
[Display(Name = "Nome Popular")]
public string NomePopular { get; set; }
[ForeignKey("IdiomaId")]
[Display(Name = "Idioma")]
public virtual Idioma Idioma { get; set; }
public long IdiomaId { get; set; }
}
Remove parameterized constructor public RacaConteudo(long RacaId, ..., long IdiomaId), and make the class partial.
[Table("RacasConteudo")]
public partial class RacaConteudo
^^^^^
{
/* public RacaConteudo(...) {} */
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long RacaConteudoId { get; set; }
....
}
Based on your updated question, Raca and RacaConteudo have relationship.
If so, you could even retrieve the desired result in single query which is a lot faster than querying multiple RacaConteudos for each and every Raca.
public JsonResult GetRaca(string especieId)
{
int esp = Convert.ToInt32(especieId);
var result = (from c in db.RacaConteudo
where c.Raca.EspecieId == esp && c.IdiomaId == 1
select new {Text = c.NomePopular, Value = c.RacaId.ToString()}).ToList();
return Json(result, JsonRequestBehavior.AllowGet);
}

How to have multiple lists in an Entity of the same type?

I have an entity that needs to have multiple lists of another entity. Each of these lists will be comprised of the same type of entity however, and this seems to confuse the framework. I've read this question:
Multiple collections of same type in entity framework
and followed the suggestion of differentiating the different lists by inheriting different types for each of the lists of items. This does not seem to do anything though.
The error I get is:
Exception:Thrown: "Invalid column name 'ApprovalStage_Id1'.
Invalid column name 'ApprovalStage_Id2'.
Invalid column name 'ApprovalStage_Id3'." (System.Data.SqlClient.SqlException)
A System.Data.SqlClient.SqlException was thrown: "Invalid column name 'ApprovalStage_Id1'.
Invalid column name 'ApprovalStage_Id2'.
Invalid column name 'ApprovalStage_Id3'."
Time: 2/9/2015 3:22:05 PM
Thread:Worker Thread[11116]
And here are my entities. It's a bit dense, but basically the main object is ComplexApprovalProcess, which has some number of ApprovalStages in a single list (that's all fine). The problem comes in with the fact that each ApprovalStage has three lists of Approver, 'Approvers', 'Viewers', and 'Advisors'. When Entity tries to save these entities it throws the error above. Like I said, I tried differentiating these by inheriting three other classes from Approver, so as you can see they are now collections of ReqApprover, Advisor and Viewer, but it still throws the error above just like it did before. Any ideas why Entity is getting confused on this? Each Approver entity should just have a reference back to the ApprovalStage it belongs to, but Entity seems to think it should be referencing three different ApprovalStages and tries to dynamically find those columns, which don't exist. Thanks.
The Entities:
public class ComplexApprovalProcess
{
[Key]
public long Id { get; set; }
[InverseProperty("ApprovalProcessId")]
public List<ApprovalStage> Stages { get; set; }
[ForeignKey("Form")]
public long FormId { get; set; }
public int CurrentStage { get; set; }
public FormBase Form { get; set; }
bool Approved { get; set; }
bool Denied { get; set; }
private bool CheckCompleted() {
foreach (ApprovalStage stage in this.Stages)
{
if (stage.Completed == false)
{
//if any stage is incomplete, the process is not complete
return false;
}
}
//no stages incomplete means all stages complete
return true;
}
private bool AdvanceStage()
{
//check the completion condition of the current stage, if completed, advance to next stage
ApprovalStage current = Stages.Where(m => m.StageOrder == this.CurrentStage).FirstOrDefault();
if (current != null)
{
//check if stage is completed
if (current.CheckCompletion())
{
//check if stage is approved
if (current.Approved)
{
//check if process contains additional stages
if (this.Stages.Count > this.CurrentStage)
{
//Move to next stage
this.CurrentStage += 1;
ApprovalStage next = Stages.Where(m => m.StageOrder == this.CurrentStage).FirstOrDefault();
if (next != null)
{
next.StartStage();
}
else
{
throw new Exception("Huh?");
}
}
}
}
}
else
{
throw new Exception("Wut");
}
return false;
}
public static ComplexApprovalProcess CreateCheckRequestApprovalProcess(FormBase form)
{
UsersModel user = null;
ComplexApprovalProcess process = new ComplexApprovalProcess();
using (TechnologyProjectPlanContext db = new TechnologyProjectPlanContext())
{
int id = SessionVar.Get<int>(SessionVar.USERID);
user = db.UsersModels.Where(m => m.Id == id).FirstOrDefault();
}
process.Form = form;
ApprovalStage InitialReview = new ApprovalStage();
InitialReview.StageOrder = 1;
InitialReview.Approvers = new List<ReqApprover>();
InitialReview.Advisors = new List<Advisor>();
InitialReview.Viewers = new List<Viewer>();
InitialReview.Form = form;
InitialReview.ApprovalProcess = process;
InitialReview.Approvers.Add(new ReqApprover(user, form, InitialReview));
InitialReview.Advisors.Add(new Advisor(user, form, InitialReview));
InitialReview.Viewers.Add(new Viewer(user, form, InitialReview));
InitialReview.StageName = "Initial Review";
ApprovalStage MiddleApproval = new ApprovalStage();
MiddleApproval.StageOrder = 2;
MiddleApproval.Approvers = new List<ReqApprover>();
MiddleApproval.Advisors = new List<Advisor>();
MiddleApproval.Viewers = new List<Viewer>();
MiddleApproval.Form = form;
MiddleApproval.ApprovalProcess = process;
MiddleApproval.Approvers.Add(new ReqApprover(user, form, MiddleApproval));
MiddleApproval.Advisors.Add(new Advisor(user, form, MiddleApproval));
MiddleApproval.Viewers.Add(new Viewer(user, form, MiddleApproval));
MiddleApproval.StageName = "Middle Approval";
ApprovalStage FinalApproval = new ApprovalStage();
FinalApproval.StageOrder = 3;
FinalApproval.Approvers = new List<ReqApprover>();
FinalApproval.Advisors = new List<Advisor>();
FinalApproval.Viewers = new List<Viewer>();
FinalApproval.Form = form;
FinalApproval.ApprovalProcess = process;
FinalApproval.Approvers.Add(new ReqApprover(user, form, FinalApproval));
FinalApproval.Advisors.Add(new Advisor(user, form, FinalApproval));
FinalApproval.Viewers.Add(new Viewer(user, form, FinalApproval));
FinalApproval.StageName = "Final Approval";
process.Stages = new List<ApprovalStage>();
process.Stages.AddRange(new ApprovalStage[] { InitialReview, MiddleApproval, FinalApproval });
//set default values
process.Approved = false;
process.Denied = false;
process.CurrentStage = 1;
process.Stages[0].StartStage();
return process;
}
public void SaveToDb()
{
//make sure we have at least one stage and either a form reference (new form) or form id (old form) before moving forward
if ((Stages != null && Stages.Count > 0) && (Form != null || FormId > 0))
{
using (TechnologyProjectPlanContext db = new TechnologyProjectPlanContext())
{
//first we have to save the process to get an Id
//copy stages out so we can save without the fuss
List<ApprovalStage> stages = this.Stages;
this.Stages = null;
db.ComplexApprovalProcesses.Add(this);
db.SaveChanges();
//'this' now has an Id
//ok let's work it out from the bottom to the top, first separate out approvers from stages and save:
foreach (ApprovalStage stage in stages)
{
ICollection<ReqApprover> approvers = stage.Approvers;
ICollection<Advisor> advisors = stage.Advisors;
ICollection<Viewer> viewers = stage.Viewers;
stage.FormId = stage.Form.Id;
stage.Form = null;
stage.Approvers = null;
stage.Advisors = null;
stage.Viewers = null;
stage.ApprovalProcessId = this.Id;
db.ApprovalStages.Add(stage);
db.SaveChanges();
//stage now has an id;
//iterate through each set of approvers and save
foreach (Approver approver in approvers)
{
approver.FormId = stage.FormId;
approver.UserId = approver.User.Id;
approver.ApprovalStage_Id = stage.Id;
approver.Form = null;
approver.User = null;
approver.Stage = null;
db.Approvers.Add(approver);
db.SaveChanges();
}
foreach (Advisor approver in advisors)
{
approver.FormId = stage.FormId;
approver.UserId = approver.User.Id;
approver.ApprovalStage_Id = stage.Id;
approver.Form = null;
approver.User = null;
approver.Stage = null;
db.Approvers.Add(approver);
}
foreach (Viewer approver in viewers)
{
approver.FormId = stage.FormId;
approver.UserId = approver.User.Id;
approver.ApprovalStage_Id = stage.Id;
approver.Form = null;
approver.User = null;
approver.Stage = null;
db.Approvers.Add(approver);
}
db.SaveChanges();
}
}
}
}
}
public class ApprovalStage
{
//Each stage requires at least one approver
[Key]
public long Id { get; set; }
[ForeignKey("ApprovalProcess")]
public long ApprovalProcessId { get; set; }
[ForeignKey("Form")]
public long FormId { get; set; }
public FormBase Form { get; set; }
public ComplexApprovalProcess ApprovalProcess { get; set; }
public ICollection<ReqApprover> Approvers { get; set; } //These users are required to approve before the form can move to the next stage.
public ICollection<Advisor> Advisors { get; set; } //These users can see the form and approve at this stage, but they are not required.
public ICollection<Viewer> Viewers { get; set; } //These users can see the form, but cannot approve
public string StageName { get; set; } //Name of stage e.g. Review, Final Approval, etc. Gives a custom feel?
public int StageOrder { get; set; }
public bool Completed { get; set; }
public bool Approved { get; set; }
public bool Denied { get; set; }
public bool CanAbstain { get; set; }
public ApprovalStage()
{
this.Approved = false;
this.Denied = false;
this.Completed = false;
}
}
public class Approver
{
[Key]
public long Id { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }
public UsersModel User { get; set; }
[ForeignKey("Form")]
public long FormId { get; set; }
public FormBase Form { get; set; }
[ForeignKey("Stage")]
public long ApprovalStage_Id { get; set; }
public ApprovalStage Stage { get; set; }
public bool Approved { get; set; }
public bool Denied { get; set; }
public bool Abstain { get; set; }
public Approver() { }
public Approver(UsersModel user, FormBase form, ApprovalStage stage)
{
this.Stage = stage;
this.User = user;
this.Approved = false;
this.Denied = false;
this.Abstain = false;
}
}
Ok so I figured out the error I was making. I didn't think about the fact that once placed into the database, the different lists of the same object type would have no knowledge of which list they originated from. That is, an Approver, Advisor, and Viewer would all look the same to the framework after they've been inserted. I kept running into errors with the inheritance of Approvers, so I simply made 3 different classes and tables and copied the design to each of them and it works perfectly. Can't believe I spent so much time on this...

Importing data with LinqToExcel - Readonly properties

I'm trying to import data from excel using LinqToExcel. I have few readonly properties in the model class. When I try to map them with the excel columns, they fail with following error. Also when I drop those columns from excel, it works fine without values.
Method 'Total' not found.
Model:Budget
[Required]
[Display(Name = "Room Type")]
public int RoomTypeID { get; set; }
[ForeignKey("RoomTypeID")]
public virtual RoomType RoomType { get; set; }
public decimal Pair { get; set; }
[ExcelColumn("Cost per Room*")]
public decimal CostPerRoom { get; set; }
[NotMapped]
[ExcelColumn("Total")]
[Display(Name = "Total")]
public decimal Total
{
get
{
if (this.RoomType != null)
{
return this.CostPerRoom * this.RoomType.RoomTypeQty * this.Pair;
}
else
{
return 0;
}
}
}
Budget Controller:
public ActionResult ReadFromExcel()
{
var file = Request.Files[0];
if (file != null && file.ContentLength > 0)
{
var fileName = Path.GetFileName(file.FileName);
var path = Path.Combine(Server.MapPath("~/Uploads/"), fileName);
file.SaveAs(path);
var excel = new ExcelQueryFactory(path);
excel.DatabaseEngine = DatabaseEngine.Ace;
excel.TrimSpaces = LinqToExcel.Query.TrimSpacesType.Both;
var budgets = from c in excel.Worksheet<Budget>("WorksheeName") select c;
foreach (var item in budgets) // This is where it generates the error.
{
}
}
How do I overcome this?

Categories