I have a small model created using code-first approach - a class City which contains only information about city name.
public class City
{
public City()
{
Posts = new List<Post>();
}
public City(string cityName)
{
Name = cityName;
}
public virtual ICollection<Post> Posts { get; private set; }
public int Id { get; set; }
public string Name { get; private set; }
}
A Post class represents combination of zip code and city reference
public class Post
{
public virtual City City { get; set; }
public int Id { get; set; }
public string ZipCode { get; set; }
}
both entities have their sets defined in context as their configurations
public DbSet<City> Cities { get; set; }
public DbSet<Post> Posts { get; set; }
modelBuilder.Configurations.Add(new CityMap());
modelBuilder.Configurations.Add(new PostMap());
public class CityMap : EntityTypeConfiguration<City>
{
public CityMap()
{
// Primary Key
HasKey(t => t.Id);
// Properties
// Table & Column Mappings
ToTable("City");
Property(t => t.Id).HasColumnName("Id");
Property(t => t.Name).HasColumnName("Name");
}
}
public class PostMap : EntityTypeConfiguration<Post>
{
public PostMap()
{
// Primary Key
HasKey(t => t.Id);
// Properties
// Table & Column Mappings
ToTable("Post");
Property(t => t.Id).HasColumnName("Id");
Property(t => t.ZipCode).HasColumnName("ZipCode");
// Relationships
HasRequired(t => t.City)
.WithMany(t => t.Posts)
.Map(map=>map.MapKey("CityId"));
}
}
I've created class for manipulation with those objects with static methods which get or creates objects and return them to caller.
private static City GetCity(string cityName)
{
City city;
using (var db = new DbContext())
{
city = db.Cities.SingleOrDefault(c => c.Name == cityName);
if (city == null)
{
city = new City(cityName);
db.Cities.Add(city);
db.SaveChanges();
}
}
return city;
}
private static Post GetPost(string zipCode, string cityName)
{
Post post;
City city = GetCity(cityName);
using (var db = new DbContext())
{
post = db.Posts.SingleOrDefault(p => p.City.Id == city.Id && p.ZipCode == zipCode);
if (post == null)
{
post = new Post { City = city, ZipCode = zipCode };
// State of city is unchanged
db.Posts.Add(post);
// State of city is Added
db.SaveChanges();
}
}
return post;
}
Imagine, that I call method
GetPost("11000","Prague");
method GetCity is started and if not exists, method creates a city and then calls the SaveChanges() method.
If I set returned city entity to new Post instance, Entity Framework generates a second insert for the same city.
How can I avoid this behavior? I want to only insert new post entity with referenced city created or loaded in previous step.
You need to set the State of your city when you attach it to unchanged
context.Entry(city).State = EntityState.Unchanged;
Related
i have 2 class with a many to many relationship
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Movie> Movies { get; set; }
}
public class Movie
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Actor> Actors { get; set; }
}
I would like to add data in the generated tables via the OnModelCreating.
I have always un error because actormovie don't exist at this time.
Might you help me ?
I found the solution on Join entity type configuration
Use this to seed data OnModelCreating for the joining table:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Actor>()
.HasData(new Actor { Id = 1, Name = "Keanu Reeves" });
modelBuilder
.Entity<Movie>()
.HasData(
new Movie { Id = 1, Name = "Matrix" },
new Movie { Id = 2, Name = "John Wick" });
modelBuilder
.Entity<Actor>()
.HasMany(m => m.Movies)
.WithMany(a => a.Actors)
.UsingEntity(j => j
.HasData(
new { ActorsId = 1, MoviesId = 1 },
new { ActorsId = 1, MoviesId = 2 } ));
}
This worked for me.
I have a simple One-To-Many relationship.
Parent:
public class Customer
{
public Customer(string name, string email)
{
Name = name;
Email = email;
}
public Customer(string name, string email, long mobile)
: this(name, email)
{
Mobile = mobile;
}
//for EF
private Customer() { }
public int Id { get; private set; }
public string Name { get; private set; }
public string Email { get; private set; }
public long? Mobile { get; private set; }
public List<Transaction> Transactions { get; private set; }
public void AddTransactions(IEnumerable<Transaction> transactions)
{
if(Transactions == null)
Transactions = new List<Transaction>();
Transactions.AddRange(transactions);
}
}
Child:
public class Transaction
{
public Transaction(DateTimeOffset date, decimal amount, Currency currency, Status status)
{
TransactionDate = date;
Amount = amount;
CurrencyCode = currency;
Status = status;
}
//for EF
private Transaction() { }
public int Id { get; private set; }
public DateTimeOffset TransactionDate { get; private set; }
public decimal Amount { get; private set; }
public Currency CurrencyCode { get; private set; }
public Status Status { get; private set; }
public int CustomerId { get; set; }
public Customer Customer { get; private set; }
}
There is a simple method, which queries one Customer and calls SingleOrDefault on it. After that it queries transactions, and when they are loaded, Customer's Transactions becomes from null to Count=5(transactions which I loaded). Why? In configuration I didn't specify .UseLazyLoadingProxies().
var customerQuery = _dbContext.Customers.AsQueryable();
if (!string.IsNullOrEmpty(request.Email))
customerQuery = customerQuery.Where(c => c.Email == request.Email);
if (request.CustomerId.HasValue)
customerQuery = customerQuery.Where(c => c.Id == request.CustomerId.Value);
var customer = await customerQuery.SingleOrDefaultAsync(cancellationToken)
.ConfigureAwait(false);
//here customer has null collection of transactions
if (customer == null)
throw new NotFoundException("Not Found.");
var transactions = await _dbContext.Transactions
.Where(t => t.CustomerId == customer.Id)
.OrderByDescending(t => t.TransactionDate)
.Take(5)
.ToListAsync(cancellationToken);
//here customer has 5 transactions.
customer.AddTransactions(transactions);
//here it has 10, because of method (following the DDD, it is used for providing business invariant)
EF configuration:
public class CustomerEntityConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
builder.Property(c => c.Id)
.HasMaxLength(10);
builder.Property(c => c.Email)
.HasMaxLength(25)
.IsRequired();
builder.Property(c => c.Mobile)
.HasMaxLength(10);
builder.Property(c => c.Name)
.HasMaxLength(30)
.IsRequired();
//uniqueness constraint
builder.HasIndex(c => c.Email)
.IsUnique();
builder.HasMany(t => t.Transactions)
.WithOne(t => t.Customer)
.HasForeignKey(t => t.CustomerId);
}
////////////////////////////
public class TransactionEntityConfiguration : IEntityTypeConfiguration<Transaction>
{
public void Configure(EntityTypeBuilder<Transaction> builder)
{
builder.Property(t => t.Amount)
.HasColumnType("decimal(10, 2)");
}
}
This is normal behaviour and a consequence of long-lived DbContexts. Perhaps explain why this behaviour is undesirable?
Option 1: use AsNoTracking(). This tells EF not to associate loaded instances with the DbContext. Auto-wireup will not happen.
Option 2: Use shorter-lived DbContexts. Module level DbContexts can be accessed across multiple methods. Using shorter-lived DbContexts bound in using blocks means calls wont need to worry about sharing references.
Trying to seed database in MSSQL server. 'Id' column is set to identity. I fail to understand why EF needs data for 'Id:
public class Location
{
public int? Id { get; set; }
public string Name { get; set; }
public IList<Office> Offices { get; set; }
}
... fluent API:
modelBuilder.Entity<Location>()
.HasKey(k => k.Id);
modelBuilder.Entity<Location>()
.Property(p => p.Id)
.UseSqlServerIdentityColumn()
.ValueGeneratedOnAdd();
modelBuilder.Entity<Location>()
.HasData(
new Location() { Name = "Sydney" },
new Location() { Name = "Melbourne" },
new Location() { Name = "Brisbane" }
);
... as far as I understand 'Id' doesn't need to be provided if it's generated by server on insert. Why do I get the messages about not providing Id ...
I think that the error is here
public int? Id { get; set; }
Id should not be nullable.
Update:
What I mean is that you should write:
public int Id { get; set; }
The question mark makes your property nullable, but since it is a primary key it cannot be null.
I did a littel example here:
using System.Collections.Generic;
namespace ConsoleApp2.Models
{
public class Location
{
public int Id { get; set; }
public string Name { get; set; }
public IList<Office> Offices { get; set; }
}
}
Fluent Api
migrationBuilder.CreateTable(
name: "Locations",
columns: table => new
{
Id = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Locations", x => x.Id);
});
I can add new location without problems.
using ConsoleApp2.Models;
using System.Collections.Generic;
namespace ConsoleApp2
{
class Program
{
static void Main(string[] args)
{
MyDbContext _c = new MyDbContext();
List<Office> list = new List<Office>()
{
new Office()
{
OfficeName = "Reception"
}
};
Location l = new Location()
{
Name = "New York",
Offices = list
};
_c.Locations.Add(l);
_c.SaveChanges();
}
}
}
Im using .net core 2.1 with EFcore 2.2.2.
I hope that help.
I would like to add an object to a related entity without loading them.
I have Company entity defined like this:
public class Company
{
public int ID { get; set; }
public List<Employee> EmployeeList{ get; set; }
}
And Employee entity like this
public class Employee
{
public int ID { get; set; }
public String Name{ get; set; }
}
I want to add an employee to a list placed in that company object without loading all the employees.
I know I can use this expression
Company myCompany= systemBD.Companies.Include("EmployeeList").Find(1) myCompany.EmployeeList.Add(newEmployee)
but I'm afraid that this would consume a lot of time since I have thousands of employees in my database.
Is there a way to add a new employee to an existing company without loading the list of Employees?
I was looking into the Attach method but it does not seem to work.
using (var systemDB = new CompanyDB())
{
Employee employee = new Employee ();
Company companySearch = systemDB.Companies.Where(d => d.Name.Equals("John")).SingleOrDefault();
if (companySearch != null)
{
if (companySearch.EmployeeList != null)
{
systemDB.Companies.Attach(companySearch );
companySearch.EmployeeList.Add(employee);
systemDB.SaveChanges();
}
}
I tried that code but it doesn't work.
Assuming you have your Company and Employee entities defined to have both a navigation property from a Company to the collection of all of its associated Employees and a property from an Employee to its single associated Company, you can accomplish creating a new Employee and associating it with an existing Company from the Employees DB set.
[Table("Company")]
public partial class Company
{
public Company()
{
this.Employees = new HashSet<Employee>();
}
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
}
[Table("Employee")]
public partial class Employee
{
public int Id { get; set; }
[Required]
[StringLength(50)]
public string Name { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
}
public partial class Database : DbContext
{
public Database()
: base("name=Database")
{
}
public virtual DbSet<Company> Companies { get; set; }
public virtual DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.Property(e => e.Name)
.IsUnicode(false);
modelBuilder.Entity<Company>()
.HasMany(e => e.Employees)
.WithRequired(e => e.Company)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Employee>()
.Property(e => e.Name)
.IsUnicode(false);
}
}
Then assuming you already have a Company in the system with an Id of 1, you can do the following:
using (var database = new Database())
{
var company = database.Companies.Find(1);
if (company != null)
{
var employee = new Employee
{
Name = "John Doe",
Company = company
};
database.Employees.Add(employee);
database.SaveChanges();
}
}
OR...if you are sure that Company Id 1 definitely exists...
using (var database = new Database())
{
var employee = new Employee
{
Name = "John Doe",
CompanyId = 1
};
database.Employees.Add(employee);
database.SaveChanges();
}
I think you would need to change your Database Design to accomplish what you want.
Employee table
ID (Primary key)
Name
Company table
ID (Primary key)
Name
EmployeeCompany table
IDCompany (Foreign Key)
IDEmployee (ForeignKey)
This way you will accomplish what you want
I have the following class:
public class Location
{
public int Id { get; set; }
public string Description { get; set; }
public string CarId { get; set; }
public Car Car { get; set; }
}
public class Car
{
public string Id { get; set; }
public string Color { get; set; }
}
And a view:
<div>#Html.LabelFor(location => location.Description)</div>
<div>#Html.EditorFor(location => location.Description)</div>
<div>#Html.LabelFor(location => location.Car.Id)</div>
<div>#Html.EditorFor(location => location.Car.Id)</div>
<div>#Html.LabelFor(location => location.Car.Color)</div>
<div>#Html.EditorFor(location => location.Car.Color)</div>
When i try this:
[HttpPost]
public ActionResult Create(Location location)
{
if (ModelState.IsValid)
{
Car car = db.Car.Find(location.Car.Id);
if (car != null)
db.Entry(car).CurrentValues.SetValues(location.Car);
else
db.Car.Add(location.Car);
db.Location.Add(locacao);
db.SaveChanges();
return RedirectToAction("Index");
}
return View(locacao);
}
it breaks in 'db.SaveChanges' cause it says 'Cannot insert duplicate key in object 'dbo.Car'.'
If I remove 'db.Location.Add(locacao);', so it works pretty fine for the car(insert and update), but no location is added in database.
How can I do to insert a new car when there is not car's id in database, update when there is, and insert a new location?
Add method always adds all entities in the object graph so both db.Car.Add(location.Car) and db.Location.Add(location) are inserting both location and car.
Try this:
db.Location.Add(locacation);
// You can also use another way to find if you are working with a new Car
// like location.Car.Id == 0
if (db.Car.Any(c => c.Id == location.Car.Id))
{
db.Entry(location.Car).State = EntityState.Modified;
}
db.SaveChanges();