linq2db bulkcopyasync without error but no data inserted in the database - linq2db

I am using linq2db .NET Core library to bulk insert a collection. This code can be executed without error but there is no data in the database. There are 2000 person object in persons list object. Person object has already identity in it.
using (var db = SqlServerTools.CreateDataConnection(connstring)
{
await db.BulkCopyAsync(new BulkCopyOptions { KeepIdentity = true, TableName = "[Persons].[Person]" }, persons);
}
Person table in in Persons schema.
I have also tried with BulkCopy which can be executed without exception but still nothing in the database.
Some troubleshooting done:
If my table is without any schema, it works. I can execute without exception and in the database I can see the data.
But if my table is with schema, I can execute it without exception but in the database, I cannot see the data.
Model with Schema
[Table("Person", Schema = "Persons")]
public partial class Person
{
[Key]
public int Id { get; set; }
}
Model without Schema
[Table("Person")]
public partial class Person
{
[Key]
public int Id { get; set; }
}
What have I miss out? How to troubleshoot further?

Related

Why must I access the related fields of a user before I can delete them in ASP.NET MVC EF app?

There is a ApplicationUser. They can have multiple TfsAccounts and one TfsToken. A TfsAccount can have multiple TrackedTasks.
public class ApplicationUser : IdentityUser
{
public virtual ICollection<TfsAccount> TfsAccounts { get; set; }
public virtual TfsToken TfsToken { get; set; }
}
public class TfsAccount
{
[ForeignKey("ApplicationUserId")]
public virtual ApplicationUser ApplicationUser { get; set; }
public string ApplicationUserId { get; set; }
public virtual ICollection<TrackedTask> TrackedTasks { get; set; }
}
public class TrackedTask
{
[ForeignKey("TfsAccountId")]
public virtual TfsAccount TfsAccount { get; set; }
public int TfsAccountId { get; set; }
}
Now, I have a method to cancel a subscription for a user and delete them:
[HttpGet]
public async Task<ActionResult> CancelSubscription()
{
var currentUser = UserManager.FindById(User.Identity.GetUserId());
var tfsAccounts = currentUser.TfsAccounts; <---REMOVE THESE
var tfsToken = currentUser.TfsToken; <---TWO LINES AND IT BREAKS
var result = await UserManager.DeleteAsync(currentUser); <---ON THIS LINE
AuthenticationManager.SignOut();
return RedirectToAction("CancellationConfirmed");
}
Here is the error I get in the browser:
The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TfsAccounts_dbo.AspNetUsers_ApplicationUserId". The conflict occurred in database "TfsTeamStatus", table "dbo.TfsAccounts", column 'ApplicationUserId'.
The statement has been terminated.
Why do I have to access the related fields on the user before I can delete the user? This works but feels super hacky.
That's super hacky. Correct.
You need to set on delete cascade option to set for that.
If you are using EF Designer, then you can do that using EF Designer as per these steps
Set Cascade on relation in EF designer. This instruct context that all
loaded related entities must be deleted prior to deletion of the
parent entity. If this doesn't happen EF will throw exception because
internal state will detect that loaded childs are not related to any
existing parent entity even the relation is required. I'm not sure if
this happens before execution of delete statement of the parent entity
or after but there is no difference. EF doesn't reload related
entities after executing modifications so it simply doesn't know about
cascade deletes triggered in the database.
Set ON CASCADE DELETE on
relation in database. This will instruct SQL to delete all related
records which were not loaded to context in the time of deleting the
parent.
If you are doing code first approach, you can do that using Fluent API as per [Entity Framework (EF) Code First Cascade Delete for One-to-Zero-or-One relationship
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOptional(a => a.UserDetail)
.WithOptionalDependent()
.WillCascadeOnDelete(true);
}
Now, coming to your point, I am not sure why it works with the two lines when you access TfsAccount and TfsToken, but I guess EF might be setting Modified flag when you accessed it. So while doing SaveChanges it might be deleting those entities. - It's just a guess.

Updating many-to-many relationship entity framework

I have problem with updating entites that have many-to many relationship. Below my User and category class:
public class User : IEntity
{
[Key]
public virtual long Id { get; set; }
private ICollection<Category> _availableCategories;
public virtual ICollection<Category> AvailableCategories
{
get { return _availableCategories ?? (_availableCategories = new List<Category>()); }
set { _availableCategories = value; }
}
}
public class Category : IEntity
{
[Key]
public long Id { get; set; }
/// <summary>
/// Full name or description of a category
/// </summary>
[StringLength(255)]
public string FullName { get; set; }
}
This is code snippet from my repository
public override void Edit(User user)
{
var dbUser = _context.Users.Include(x => x.AvailableCategories)
.Single(x => x.Id == user.Id);
var categories = _context.Categories;
dbUser.AvailableCategories.Clear();
foreach (var cat in user.AvailableCategories)
{
dbUser.AvailableCategories.Add(cat);
}
_context.Entry(dbUser).State = EntityState.Modified;
}
However the categories don't get updated. What EF does is insert empty rows into category table and sets relations to this new rows with user.
How can I update User so that I change only categories that already exist in the database?
User that I pass to Edit method has AvailableCategories with only Ids set (rest of properties are empty).
When you're doing something like posting back M2M relationships, you either must post the full object, as in every single property on those objects, or simply post a list of ids and then use those to query the associated objects back from the database. Otherwise, Entity Framework understands your purpose to be to update the properties on the objects as well, in this case with empty values.
Obviously the first option is quite unwieldy, so the second way is the preferred and standard way. Generally, for this, you'd want to use a view model so you could have a property like the following, that you would post into:
public List<long> SelectedCategories { get; set; }
But, if you insist on using the entity directly, you can get much the same result by simply doing:
var selectedCategories = user.AvailableCategories.Select(m => m.Id)
Once you have the ids:
var newAvailableCategories = _context.Categories.Where(m => selectedCategories.Contains(m.Id));
And then finally set that on your user:
dbUser.AvailableCategories = newAvailableCategories;
I notice you are also adding the user.AvailableCategories directly into dbUser.AvailableCategories. I've noticed when binding back complex objects from an MVC view that DB Entities are no longer attached to the DbContext. If you look at the entity, you can verify by checking dbContext.Entry(cat).State is "detached" (or something unexpected) I believe.
You must query those entities back out of the dbContext (possibly by using the returned cat.Id's). Or otherwise manually set the entities as "unchanged". And then add those "non-detached" items into dbUser.AvailableCategories. Please see Chris's answer as it shows with specific code how to get this done.
Also, I might use a linking entity. Possibly something like this:
public class UserCategory
{
public User User {get;set;}
public Category Category {get;set;}
}
And add it to DB context. Also, drop the linking lists in your current User and Category class. This way you can manipulate the UserCategory class (and DbSet) to manage your many-to-many relationship.

Many-to-many relationship throw an error after updating to Entity Framework 6

I have "many-to-many relationship", which worked perfectly on a local machine until it was time to move to a server. During the move a runtime error was thrown. After upgrade to Entity Framework 6 it worked, but... One of the "many-to-many relationship" was broken.
Models have many properties and they looks something like this:
public partial class CartItem
{
public int id { get; set; }
public virtual ICollection<ProductOptionValue> ProductOptionValues { get; set; }
}
public class CartItemMapping : EntityTypeConfiguration<CartItem>
{
public CartItemMapping() : base()
{
this.HasMany(e => e.ProductOptionValues).WithMany(e => e.CartItems)
.Map(e => e.ToTable("CartItemOptions").MapLeftKey("productOptionValueId").MapRightKey("cartItemId"));
}
}
public class ProductOptionValue
{
public int id { get; set; }
public virtual ICollection<CartItem> CartItems { get; set; }
}
public class ProductOptionValueMapping : EntityTypeConfiguration<ProductOptionValue>
{
public ProductOptionValueMapping() : base()
{
this.HasMany(e => e.CartItems).WithMany(e => e.ProductOptionValues)
.Map(e => e.ToTable("CartItemOptions").MapLeftKey("cartItemId").MapRightKey("productOptionValueId"));
}
}
When getting earlier saved CartItems, ProductOptionValues has count = 0 and there is no way to save a new one.
I found this question on another forum, and only answer was that, this relationship will not work after update at all. But maybe there is a solution?
Or maybe some idea why runtime error was thrown on server with Entity Framework 5?
EDIT
Errors:
The INSERT statement conflicted with the FOREIGN KEY constraint
"FK_CartItem_CartItemOptions". The conflict occurred in database
"database", table "dbo.CartItems", column 'id'. The statement has been
terminated.
An error occurred while saving entities that do not expose foreign key
properties for their relationships. The EntityEntries property will
return null because a single entity cannot be identified as the source
of the exception. Handling of exceptions while saving can be made
easier by exposing foreign key properties in your entity types. See the InnerException for details.

Associated entities not attaching to parent object when saving

I am working on an application that uses EF 4.2 and database-first development, using the standard T4 template to generate a DbContext and POCOs. The T4 templates generate entities something like this:
public class Address
{
public int AddressId { get;set; }
public string Address1 { get;set; }
public string City { get;set; }
}
public class Account
{
public int AccountId { get;set; }
public string Name { get;set; }
public int AddressId { get;set; }
public Address BillingAddress { get;set; }
}
When I create a billing address for an existing account, my code is something like this:
public void Save(Account updated)
{
var existing = DbContext.Find(updated.AccountId);
MyContext.Entry(existing).CurrentValues.SetEntry(updated);
existing.Address = updated.Address;
MyContext.SaveChanges();
}
Watching SQL Server Profiler, I can see the Address entry being inserted into the database, but unfortunately, it is occurring after the Account entry is updated, so the address is detached from its parent account, and when I next load the account, the billing address is empty again.
A workaround is to add the following code after the call to SaveChanges():
if (existing.AddressId == null && existing.Address != null)
{
existing.AddressId = existing.Address.AddressId;
MyContext.SaveChanges();
}
which, while it may work, requires a second SQL UPDATE to the database, and as the entity grows and adds more associations, requires more and more hacks. Is there something obvious that I'm missing?
** UPDATE **
Following Ladislav's answer below, I added a call to the following method in the WriteNavigationProperty to my T4 template:
void WriteKeyAttribute(CodeGenerationTools code, NavigationProperty navigationProperty, MetadataTools ef)
{
var dependentProperties = navigationProperty.GetDependentProperties();
if (dependentProperties.Any())
{
var keys = new List<string>();
foreach (var key in dependentProperties)
{
keys.Add(String.Format("\"{0}\"", key.Name));
}
#>
[ForeignKey(<#= String.Join(", ", keys) #>)]
<#+
}
}
Hope that helps!
It sounds like your BillingAddress is incorrectly mapped because AddressId is not handled as the FK of the relation.
Try to add this attribute to your navigation property:
[ForeignKey("AddressId")]
public Address BillingAddress { get;set; }
If you are using EDMX with database first make sure that the there is correctly configured relation between those classes. EF uses this information in its store mapping and store mapping defines sequence of the operations. If you don't have correctly configured relation entities are processed in alphabetical order of their type names => Account is processed prior to Address.
Btw. are you sure that your Account is not duplicated during your SaveChanges call?

DeleteOnNull Error

I've got a set of DB objects sitting in an EntitySet on my main object definition. This handles additions and updates fine, but I found the removing items from the list didn't result in the database records being deleted, so I had to create a method in the data repository object to delete the records as the data object doesn't have access to the data-context in which it is being used.
I was looking to see if I could bring this delete into the main object and I found the DeleteOnNull attribute to the association, but when I use it, I get an error "DeleteOnNull can only be true for singleton association members mapped to non-nullable foreign key columns". My code is:
private EntitySet<UserSite> _userSites = new EntitySet<UserSite>();
[Association(Name = "User_UserSites", Storage = "_userSites", ThisKey = "UserID", OtherKey = "UserID", DeleteOnNull=true)]
public IList<UserSite> UserSites { get { return _userSites; } set { } }
my usersite object is
[Table(Name="UserSite")]
public class UserSite
{
[Column]//(IsPrimaryKey = true)]
public int UserID { get; set; }
[Column]//(IsPrimaryKey = true)]
public string Site { get; set; }
[Column]
public bool DefaultSite { get; set; }
[Column(IsPrimaryKey = true, AutoSync = AutoSync.OnInsert)]
public int UniqueID { get; set; }
}
Can I use DeleteOnNull to keep all my data update methods within my main user object, or do I have to handle the deletes at the repository level?
DeleteOnNull is only for singleton associations. So you can put it on UserSite.User but not on User.UserSites. It's still not quite as automatic as you'd like it to be, though. There is an example here.
It's hard for LINQ to SQL to infer the behavior you want, because it can't guess if you want composition or aggregation, so it chooses the safe guess (aggregation).

Resources