I have two simple tables and a big problem with foreign keys:
Sale (SaleID int PK identity)
SaleDetail (SaleDetailId int PK
identity, SaleID int FK)
which is a very basic one-to-many parent with children relationship. Of course there are some other fields but they're unrelated as they're not used in the relationship, so I did not include them in the listings.
I mapped the tables to classes in my code:
[Table()]
public class Sale
{
[Column(IsPrimaryKey=true, IsDbGenerated=true, AutoSync=AutoSync.OnInsert)]
public int SaleId
{
get;
set;
}
private EntitySet<SaleDetail> saleDetails = new EntitySet<SaleDetail>();
[Association(OtherKey = "SaleId", Storage = "saleDetails")]
public EntitySet<SaleDetail> SaleDetails
{
get
{
return this.saleDetails;
}
set
{
this.saleDetails.Assign(value);
}
}
and the other one:
[Table()]
public class SaleDetail
{
[Column(IsPrimaryKey=true, IsDbGenerated=true, UpdateCheck=UpdateCheck.Never)]
public int SaleDetailId
{
get;
set;
}
[Column()]
public int SaleId
{
get;
set;
}
Then I try to create a new Sale and add a couple of child objects.
Sale s = new Sale();
s.SaleDetails.Add(new SaleDetail());
s.SaleDetails.Add(new SaleDetail());
and in the last step I try to add the new objects:
using (DataContext dc = new DataContext(Database.ConnectionString))
{
var sales = dc.GetTable<Sale>();
sales.InsertOnSubmit(s);
dc.SubmitChanges();
}
the following procedure results in a foreign key problem - the child records are added with value 0 in the "SaleId" (FK) column. The parent record is added properly and the SaleId primary key gets its autogenerated value. What can I do to get the SaleId field set automatically in the child objects based on the value of the newly inserted parent?
Here, we tend to commit the parent first, although by nesting transactions you might be able to get the automatic primary key and still maintain the rollback-on-failure behaviour.
Related
For the most part EF seems to handle itself quite well, using the following query in linq, I am able to get all the related table data using FK's without having to specify the one to many relationship.
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subpat in AP.DefaultIfEmpty()
orderby s.ClinicalAssetID descending
select new ClinicalASSPATVM
{
ClinicalAssetID = s.ClinicalAssetID,
ProductName = s.ProductName,
ModelName = s.ModelName,
SupplierName = s.SupplierName,
ManufacturerName = s.ManufacturerName,
SerialNo = s.SerialNo,
PurchaseDate = s.PurchaseDate,
PoNo = s.PoNo,
Costing = s.Costing,
TeamName = s.TeamName,
StaffName = s.StaffName,
InspectionDocumnets = subpat.InspectionDocumnets ?? String.Empty,
InspectionOutcomeResult = subpat.InspectionOutcomeResult
});
the above code pulls in the relationship data from the ViewModel.
public Product ProductName { get; set; }
public InspectionOutcome InspectionOutcomeResult { get; set; }
public Model ModelName { get; set; }
public BudgetCode Code { get; set; }
public AssetType AssetTypeName { get; set; }
public Manufacturer ManufacturerName { get; set; }
public Staff StaffName { get; set; }
public Team TeamName { get; set; }
public Supplier SupplierName { get; set; }
I have a new problem which I have created for myself. I wanted to add an ID Field to the Models entity this helps me filter the data in a drop down list. I called the Field Name: ModelAssetAsignmentID And when someone adds a new ModelName from the Clinical Controller the ModelAssetAsignmentID gets a value of two.
So when i added the Field ModelAssetAsignmentID to the model "Models" i created a second FK as such:
My Original Linq Query is now broken, it no longer displays the modelname. I'm guessing this is due to the two FK Constraints.
Making the following change did not work, the InnerException is null.
var ClinicalASSPATVM = (from s in db.ClinicalAssets
where (s.ModelAssetAssignmentID.Equals(2))
join cp in db.ClinicalPATs on s.ClinicalAssetID equals cp.ClinicalAssetID into AP
from subpat in AP.DefaultIfEmpty()
orderby s.ClinicalAssetID descending
The solution is to remove the second foreign key constraint ModelAssetAssignmentID and use a viewmodel to create a value in the ModelAssetAssignmentID, thus you then do not need to modify the linq query's.
I am working on .NET Core application; Entity Framework 6
I need to add child object which is collection is same parent object in LINQ query not lambda expression
User - parent entity
public class UserDataModel
{
public UserDataModel()
{
this.Roles = new HashSet<UserRoleDataModel>();
}
public Guid Id { get; set; }
public Guid IdentityProviderID {get; set;}
public ICollection<UserRoleDataModel> Roles { get; set; }
}
Child-entity
public class UserRoleDataModel
{
public Guid UserId { get; set; }
public UserDataModel Users { get; set; }
public Guid RoleId { get; set; }
public RoleDataModel Roles { get; set; }
}
LINQ
var uu = (from _user in db.Users
join _userRole in db.UserRoles on _user.Id equals _userRole.UserId
where _user.IdentityProviderID == AureID
select new {
_user,
_userRole
}
This is far I reached but Roles are collection where sub-query to select role separate are IQueryable hence throw conversion/ casting error
var uu = (from _user in db.Users
where _user.IdentityProviderID == AureID
select new UserDataModel
{
Id = _user.Id,
IdentityProviderID = _user.IdentityProviderID,
Roles = (from b in db.UserRoles where b.UserId == userId select b)
}
).ToList();
Addition after comment at the end
It seems you have a true one-to-many relationship between UserData and UserRoles: Every UserData has zero or more UserRoles, and every UserRole belongs to exactly one UserData.
Your class definitions deviate from the Entity Framework Code First conventions. If you'd had your classes configured like a standard Entity Framework One-to-many relationship, entity framework would recognize that you'd meant to design a one-to-many and know automatically when a join is needed in your queries. Reconsider rewriting your classes:
class UserDataModel
{
public GUID Id {get; set;} // primary key
// every UserDataModel has zero or more UserRoleDataModels:
public virtual ICollection<UserRoleDataModel> UserRoleDataModels {get; set;}
... // other properties
}
class UserRoleDataModel
{
public GUID Id {get; set;} // primary key
// every UserDataRoleModel belongs to exactly one UserDataModel
// using foreign key:
public Guid UserDataModelId {get; set;}
public virtual UserDataModel UserDataModel {get; set;}
... // other properties
}
The most important change is that I made the references between your UserDataModel and your UserDataRoleModels virtual. I changed the name of the foreign key, so Entity Framework knows without any attribut / fluent API that you meant to configure a one-to-many.
Back to your question
Given the Id of a UserDataModel, give me the UserDataModel with all
its UserDataRoleModels
After you've rewritten your classes with the proper virtual ICollection, your query would be easy:
var result = myDbContext.Users
.Where(user => user.Id == AureId)
.Select( user => new
{
User = user,
Roles = user.Roles.ToList(),
});
This requirement has the shortcoming that you tend to retrieve to much data. One of the slower parts when querying a database is the transfer of the queried data to your local process. Therefore it is wise not to fetch any more data than you want.
You plan to query a complete UserData with its complete UserRole objects. if the UserData that you fetch has Id XYZ, and it has 50 UserRole objects, then every one of its UserRole object would have a UserDataId with a value of XYZ, thus transferring it 50 times more than needed.
Therefore I advise you to transfer only the data that you actually plan to use:
var result = myDbContext.Users
.Where(user => user.Id == AureId)
.Select( user => new
{
// from the user take only the data you plan to use
Id = user.Id, // probably not, you know that it is AureId
Name = user.Name,
... // other user properties you plan to use
UserRoles = user.UserRoleDataModels.Select(roleData => new
{
// again: only the role data you plan to use
Name = roleData.Name,
Type = roleData.Type,
... // other properties
// certainly not this one:
UserDataId = roleData.UserDataId,
})
.ToList(),
});
This way your query is much faster, you'll only fetch the data you want, you can change the names of fields depending on your needs, and you can add new values if desired, composed from values within UserData
Because of your virtual ICollection you don't need to perform the join. Entity Framework knows your one-to-many and understands that your use of the ICollection should result in an inner join followed by a Group By
Summarized: if you think you'll need a join in entity framework, think twice, you'll probably be able to do it simpler by using the ICollection or the reference to the parent
Addition after comment
if we add role has definition table, how to I add further nested object .
I'm not sure what this means. I think you mean: what to do if a role has a definition table, or what to do if a role has zero or more definition tables.
class UserRoleDataModel
{
public GUID Id {get; set;} // primary key
// every UserDataRoleModel belongs to exactly one UserDataModel
// using foreign key:
public Guid UserDataModelId {get; set;}
public virtual UserDataModel UserDataModel {get; set;}
// every role has exactly one DefinitionTable:
public DefinitionTable DefinitionTable {get; set'}
}
var result = myDbContext.Users
.Where(user => user.Id == AureId)
.Select( user => new
{
// from the user take only the data you plan to use
...
UserRoles = user.UserRoleDataModels
.Where(role => role... == ...)
.Select(role => new
{
RoleDefinition = role.RoleDefinitiona,
...
}),
});
If your Role has zero or more RoleDefinitions, do the same as you did with Users and Roles: add the virtual ICollection and add the virtual Reference and foreign key and use the collections in a Select statement.
I am using the PCL version of sqlite.net from here (https://github.com/oysteinkrog/SQLite.Net-PCL).
Here is my simple class.
public class LogEntry
{
[PrimaryKey, AutoIncrement]
public int Key { get; set;}
public DateTime Date { get; set; }
}
When a new instance of LogEntry is created, the Key is automatically set to 0. I set the Date to something and then call InsertOrReplace. The record does get saved in my database. The Key field gets the autoincrement value which happens to be 0 since it is the first record.
I then create a new instance of LogEntry (Key is automatically initialized to 0) and set the date to something else. I then call InsertOrReplace. Since there is an existing record with a Key of 0 that record gets updated.
What is the proper way to deal with this? I considered initializing the Key to -1, but that didn't seem to work either.
Does anyone have an example of this working?
If you change the Key to a nullable type (int?) it should work. then SQLite sees null coming in and generates new id when needed.
public class LogEntry
{
[PrimaryKey, AutoIncrement]
public int? Key { get; set;}
public DateTime Date { get; set; }
}
I experienced the same issue as you are describing. Try
var rowsAffected = Connection.Update(object);
if(rowsAffected == 0) {
// The item does not exists in the database so lets insert it
rowsAffected = Connection.Insert(object);
}
var success = rowsAffected > 0;
return success;
I just tried above and it works as expected
The way this works is the source of much confusion but whereas Insert treats zeroed primary keys as a special case when AutoIncrement is set, InsertOrReplace does not.
So with:
[PrimaryKey, AutoIncrement]
public int id { get; set; }
if you InsertOrReplace a series of zero id records into a new table, the first will be stored at id: 0 and each subsequent one will save over it. Whereas if you just Insert each one then because of the AutoIncrement the first will save at id: 1 and the next at id: 2 etc. as you might expect.
If you change the key type to a nullable int, then records with null ids will be treated as inserts by InsertOrReplace, and you don't actually need the AutoIncrement attribute at all in this case, they will still save in sequence starting at 1.
[PrimaryKey]
public int? id { get; set; }
If you can't use that for some reason you can do your own check for zero ids and for those call Insert instead, e.g.
Func<Foo, int> myInsertOrReplace = x =>
{
return x.id == 0 ? _db.Insert(x) : _db.InsertOrReplace(x);
};
but in this case you must use the AutoIncrement attribute, otherwise first zero insert will be saved at 0 and the second will throw a constraint exception when it attempts insert another such.
To get the result you want, you need to make the id property of your class nullable. see here
link
My solution for this is kind of similar to Joacar's, but instead of doing an update, I select the item, if it's null, I create a new item, otherwise update that items values, and then call InserOrReplace.
var existingKey = await this.GetItem(key);
Item item;
if (existingKey.Value != null)
{
profile = new Item
{
Id = existingKey.Id,
Key = existingKey.Key,
Value = newValue,
};
this.InsertOrReplaceAsync(item);
}
else
{
item = new Item
{
Key = key,
Value = value,
};
this.InsertAsync(item);
}
It might not be optimal, but it worked for me.
No need for InsertOrReplace.
Just await InsertAsync.
Guaranteed to work...
if (object.ID != 0)
{
// Update an existing object.
var T = DatabaseAsyncConnection.UpdateAsync(object);
T.Wait();
return T;
}
else
{
// Save a new object.
var T = DatabaseAsyncConnection.InsertAsync(object);
T.Wait();
return T;
}
I have a typical many-to-many relationship with these 3 tables
[Post] (
[PostId] int, (PK)
[Content] nvarchar(max)
...
)
[Tag] (
[TagId] int, (PK)
[Name] nvarchar
...
)
[TagPost] (
[TagId] int, (PK, FK)
[PostId] int (PK, FK)
)
And, TagId and PostId are the PK and FK set on the tables accordingly etc. Then I have these classes and mapping in c#
public class Post {
public Post()
{
this.Tags = new HashSet<Tag>();
}
[Key]
public int PostId { get; set; }
...
public virtual ICollection<Tag> Tags { get; private set; }
}
public class Tag {
public Tag()
{
this.Posts = new HashSet<Post>();
}
[Key]
public int TagId { get; set; }
...
public virtual ICollection<Post> Posts { get; private set; }
}
internal class MyDbContext : DbContext
{
public DbSet<Post> Posts { get; set; }
public DbSet<Tag> Tags { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Post>().ToTable("Post");
modelBuilder.Entity<Tag>().ToTable("Tag");
modelBuilder.Entity<Post>()
.HasMany(x => x.Tags)
.WithMany(x => x.Posts)
.Map(x =>
{
x.ToTable("TagPost");
x.MapLeftKey("PostId");
x.MapRightKey("TagId");
});
}
Then I have this code to query them
var list = (from p in ctx.Posts.Include(p => p.Tags)
from t in p.Tags
where ... // some of my filter conditions
select p).ToList();
This join does return the posts I was looking for, however the returned posts don't their associated tags filled in even though I have the Include there. Could someone help point out what I'm missing so that I could have the tags also return with the posts?
Thanks a lot.
The double from is a manual Join which causes the Include to be ignored as mentioned here and here. Include is also ignored for other LINQ methods like grouping and projections.
Relationship fixup generally does not work for many-to-many relationships, only for relationships which have at least one single reference at one of the ends - one-to-many or one-to-one. If you project the Posts and related Tags into another type (anonymous or named) the data will be loaded correctly but because the relationship is many-to-many EF won't create the relationship in memory automatically so that the post.Tags collection will stay empty.
To get the Include working you must remove the second from from your query and apply the where clause directly to the Post entity parameter, for example like so:
var list = (from p in ctx.Posts.Include(p => p.Tags)
where p.Tags.Any(t => t.TagId == 1)
select p).ToList();
The filter by a Tag property is specified in the expression passed into .Any which is an expression with a Tag (t) as parameter.
try selecting everything into an anonymous object (something like this)
var list = (
from p in ctx.Posts
from t in p.Tags
where ... // some of my filter conditions
select new {
Posts = p,
Tags = p.Tags
})
.ToList();
Based on the feedback to my initial answer and the fact that EF can find the related entities but it is failing to populate the Tags collection I believe the issue lies in the definition of the Tags entity in the Post class.
Try removing the Hashset<> initialiser from the constructors and private from the set declaration:
public virtual ICollection<Tag> Tags { get; set; }
Update:
This is now driving me crazy!
After much Googling etc. I am really no closer to a solution.....
However I have found one thing that is puzzling me even more - the "States" of the entities just before the m_dbContext.SaveChanges() call. (see below for full repository code)
var updateInfoState = m_dc.Entry(oldPage.UpdateInfo).State; // State is 'Modified'
var oldPageState = m_dc.Entry(oldPage).State; // State is 'Detached'
this.m_dc.SaveChanges();
Why is "oldPage" detached?
Getting quite desperate now!! ;)
Original:
I appear to be having a problem with EF Code-First updating related tables correctly.
In this simplified example, the 'UpdateInfo' table IS being updated OK with the new DateTime .... but the 'Pages' table is not being updated with the new 'Name' value.
I am seeding code-first POCOs via DropCreateDatabaseAlways / override Seed ... and EF is creating the test tables correctly - so at this point it seems to know what it is doing....
I am sure this is something simple/obvious I am missing!
All help very much appreciated!
My Class definitions:
public class Page
{
public int Id { get; set; }
public string Name { get; set; }
public virtual UpdateInfo UpdateInfo { get; set; } // virtual For Lazy loading
}
[Table("UpdateInfo")] // much better than EF's choice of UpdateInfoes!
public class UpdateInfo
{
public int Id { get; set; }
public DateTime DateUpdated { get; set; }
}
public class DomainContext : DbContext
{
public DbSet<Page> Pages { get; set; }
public DbSet<UpdateInfo> UpdateInfo { get; set; }
}
Tables created by Code-First
Pages Table
===========
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](max) NULL,
[UpdateInfo_Id] [int] NULL,
UpdateInfo Table
================
[Id] [int] IDENTITY(1,1) NOT NULL,
[DateUpdated] [datetime] NOT NULL,
My Repository code:
public Page Get(int id)
{
Page page = m_dbContext.Pages.Single(p => p.Id == id);
return page;
}
public void Update(PagePostModel model)
{
Page oldPage = Get(model.PageModel.Id); // on oldPage Name = "Hello", DateUpdated = "Last Year"
Page newPage = Mapper.Map<PageModel, Page>(model.PageModel); // on newPage Name = "Goodbye" (AutoMapper)
newPage.UpdateInfo = oldPage.UpdateInfo; // take a copy of the old UpdateInfo since its not contained in the model
newPage.UpdateInfo.DateUpdated = DateTime.UtcNow; // update to now
oldPage = newPage; // copy the updated page we grabbed from dbContext above (NB Everything looks great here..oldPage is as expected)
m_dbContext.SaveChanges(); // update - only the 'UpdateInfo' table is being updated - No change to 'Pages' table :(((
}
As you know, there is a change tracker api in Entity Framework.
To track the changes of your entities you retrieved from the database, DbContext uses its reference value.
Your "Update" function above inserts newPage into oldPage. So, DbContext never knows oldPage is a newPage. So, it is "detached".
However, for UpdateInfo, it is copy of reference in oldPage, so DbContext can track change of that. So, it is "modified".
To solve this problem, how about using the code below?
Page newPage = Mapper.Map<PageModel, Page>(model.PageModel);
oldPage.UpdateInfo = newPage.UpdateInfo;
oldPage.UpdateInfo.DateUpdated = DateTime.UtcNow;
m_dbContext.SaveChanges();
Update
Then, use Attach & Detach methods.
Those methods help you attach and detach entities from DbContext.
Page newPage = Mapper.Map<PageModel, Page>(model.PageModel);
// if you attach first, there will be an exception,
// because there is two entities having same id.
m_dbContext.Entry(oldPage).State = EntityState.Detached;
m_dbContext.Pages.Attach(newPage);
// if you don't set IsModified = true,
// DbContext cannot know it is changed.
m_dbContext.Entry(newPage).State = EntityState.Modified;
m_dbContext.SaveChanges();