I'm hoping to set up an EF Code First convention where all of the column names of the properties have a lowercase first letter.
However, I have other fluent API code that changes column names from the default. I can't seem to find a way to get access to the current column name of a property in order to lowercase the first letter. Starting with the PropertyInfo, as in modelBuilder.Properties() is not enough because the column name may have already been set to be different than the member name.
How do I generically tell EF Code First to lowercase the first letter of all column names?
OK, the DBA's are speaking. Let's bow our heads in reverence and see what we can do. I'm afraid that in EF 5 (and lower) there's not much you can do to make it easy. In EF 6 there is this feature of Custom Code First Conventions which actually make it a piece of cake. I just tried a small sample:
// Just some POCO
class Person
{
public int PersonId { get; set; }
public string PersonName { get; set; }
}
// A custom convention.
class FirstCharLowerCaseConvention : IStoreModelConvention<EdmProperty>
{
public void Apply(EdmProperty property, DbModel model)
{
property.Name = property.Name.Substring(0, 1).ToLower()
+ property.Name.Substring(1);
}
}
class MyContext : DbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>();
// Add the convention to the modelbuilder.
modelBuilder.Conventions.Add(new FirstCharLowerCaseConvention());
base.OnModelCreating(modelBuilder);
}
}
After running
using (var db = new MyContext())
{
db.Database.Create();
}
my database has a People table with personId and personName.
And some simple CRUD actions work flawlessly:
using (var db = new MyContext())
{
var p = new Person { PersonName = "Another Geek" };
db.Set<Person>().Add(p);
db.SaveChanges();
}
using (var db = new MyContext())
{
var x = db.Set<Person>().ToList();
}
So if the DBA's want their conventions, you can demand a new toy :)
Related
I have a problem creating a related entity in Entity Framework Core 2.0. I've just created the solution, consisting of an Asp.Net Core backend project, and a UWP project to act as client. Both solutions share model. The two models are:
public class UnitOfWork {
public int UnitOfWorkId { get; set; }
public string Name { get; set; }
public Human Human { get; set; }
}
public class Human {
public int HumanId { get; set; }
public string Name { get; set; }
public List<UnitOfWork> WorkDone { get; set; }
}
As you can see, model is very simple. One human has many units of work. By the way, the backend is connected to an Azure SQL database. I've seen the migration classes, and the database schema looks good to me.
The problem I have is when I want to create a unit of work referencing an existing human, using HTTP. The controller is fairly simple:
[HttpPost]
public UnitOfWork Post([FromBody] UnitOfWork unitOfWork) {
using (var db = new DatabaseContext()) {
db.UnitsOfWork.Add(unitOfWork);
var count = db.SaveChanges();
Console.WriteLine("{0} records saved to database", count);
}
return unitOfWork;
}
Again, nothing fancy here.
How can I create an unit of work, and assign it to an existing human? If I try it with an existing human, in this way
var humans = await Api.GetHumans();
var firstHuman = humans.First();
var unitOfWorkToCreate = new UnitOfWork() {
Name = TbInput.Text,
Human = firstHuman,
};
I get this error:
Cannot insert explicit value for identity column in table 'Humans' when IDENTITY_INSERT is set to OFF
I feel that setting IDENTITY_INSERT to ON will solve my problem, but this is not what I want to do. In the client, I'll select an existing human, write down a name for the unit of work, and create the latter. Is this the correct way to proceed?
EDIT: Following #Ivan Stoev answer, I've updated the UnitOfWork controller to attach unitofwork.Human. This led to
Newtonsoft.Json.JsonSerializationException: 'Unexpected end when deserializing array. Path 'human.workDone', line 1, position 86.'
Investigating - seen here - EFCore expects to create collections (like human.WorkDone) in the constructor, so I did it, and no more nulls deserializing. However, now I have a self-referencing loop:
Newtonsoft.Json.JsonSerializationException: Self referencing loop detected with type 'PlainWorkTracker.Models.UnitOfWork'. Path 'human.workDone'.
Any ideas? Thanks!
The operation in question is falling into Saving Disconnected Entities category.
Add methods marks all entities in the graph which are not currently tracked as new (Added) and then SaveChanges will try to insert them in the database.
You need a way to tell EF that unitOfWork.Human is an existing entity. The simplest way to achieve that is to Attach it (which will mark it as Unchanged, i.e. existing) to the context before calling Add:
db.Attach(unitOfWork.Human);
db.Add(unitOfWork);
// ...
In one of my project EF 5.0 generate POCO entities for me under abcModel.tt, This is fine as I can use these in my project but if I apply validation in it then it get lost when I update .edmx from DB. so as a solution I also build all the entities by hand as well
Student.cs Generated by EF POCO
MyStudent.cs Generate by Me
One another reason to build MyStudent.cs is that DB column names are not well
written for example
Generate by EF
Student.cs
{
public int sid; // not good name due to table column name
public string sfname; // not good name due to table column name
public string slname; // not good name due to table column name
}
Build by me
MyStudent.cs
{
public int StudentId;
public string FirstName;
public string LastName;
}
So I like to know is my approach to have dual entities is ok?
Note:
I cannot change table column names because db is too big & already build by dba.
Only solution/suggestion with Data First approach is require.
Thanks
Sure, you can do this. I recommend creating a Data Transfer library and mapping your objects (like with AutoMapper). Then when you need to retrieve or save data to/from the database, call your DT and return a business object (your other class).
Something like this:
public class StudentDT
{
public StudentBO GetStudent(int id)
{
using (var db = new dbContext())
{
var studentDB = db.Students.First(s => s.sid == id);
StudentBO sbo = Mapper.Map<StudentBO>(studentDB);
return sbo;
}
}
public void SaveStudent(StudentBO sbo)
{
using (var db = new dbContext())
{
var sdb = Mapper.Map<Student>(sbo);
db.Students.Add(sdb);
db.SaveChanges();
}
}
}
How can I go about changing the naming convention of the auto-generated many-to-many table?
Assume I have two classes:
public class User
{
public int UserId { get; set; }
public virtual List<Role> Roles { get; set; }
}
public class Role
{
public int RoleId { get; set; }
public virtual List<User> Users { get; set; }
}
By Default, this will create a table called UserRoles.
I can change the name of that one table to UsersInRoles, for example, by using the following in the OnModelCreating override of my DbContext:
modelBuilder.Entity<User>()
.HasMany(p => p.Roles)
.WithMany(p => p.Users)
.Map(mc =>
{
mc.MapLeftKey("UserId");
mc.MapRightKey("RoleId");
mc.ToTable("UsersInRoles");
});
However, what I really want to do is change the naming convention so that by default, all auto-generated many-to-many tables use this new convention. I cannot figure out how to do that, or if it's even possible. I do not like having to specify 9 lines of extra code every time I specify one of these relationships.
I am currently using EF version 6.0.0-rc1.
The ability to control relationships was removed from the basic conventions API before release because it wasn't in a usable state. You can access all of the properties and tables in the model through model based conventions. An overview of model based conventions is available here: http://msdn.microsoft.com/en-US/data/dn469439
This solution involves a little more digging around in the metadata API, EntitySet is the correct type for this scenario
This convention should rename the generated relation table:
public class MyConvention : IStoreModelConvention<EntitySet>
{
public void Apply(EntitySet set, DbModel model)
{
var properties = set.ElementType.Properties;
if (properties.Count == 2)
{
var relationEnds = new List<string>();
int i = 0;
foreach (var metadataProperty in properties)
{
if (metadataProperty.Name.EndsWith("_ID"))
{
var name = metadataProperty.Name;
relationEnds.Add(name.Substring(0, name.Length - 3));
i++;
}
}
if (relationEnds.Count == 2)
{
set.Table = relationEnds.ElementAt(0) + "_" + relationEnds.ElementAt(1) + "_RelationTable";
}
}
}
I'm dynamically creating my DbContext by iterating over any entities that inherit from EntityBase and adding them to my Context:
private void AddEntities(DbModelBuilder modelBuilder)
{
var entityMethod = typeof(DbModelBuilder).GetMethod("Entity");
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
var entityTypes = assembly.GetTypes()
.Where(x => x.IsSubclassOf(typeof(EntityBase)) && !x.IsAbstract);
foreach (var type in entityTypes)
{
dynamic entityConfiguration = entityMethod.MakeGenericMethod(type).Invoke(modelBuilder, new object[] { });
EntityBase entity = (EntityBase)Activator.CreateInstance(type);
//Add any specific mappings that this class has defined
entity.OnModelCreating(entityConfiguration);
}
}
}
That way, I can have many namespaces but just one generic repository in my base namespace that's used everywhere. Also, in apps that make use of multiple namespaces, the base repository will already be setup to use all the entities in all the loaded namespaces. My problem is, I don't want to make EntityFramework.dll a dependency of every namespace in the company. So I'm calling OnModelCreating and passing the EntityTypeConfiguration to the class so it can add any mappings. This works fine and here's how I can add a mapping to tell the model that my "Description" property comes from a column called "Descriptor":
class Widget... {
public override void OnModelCreating(dynamic entity)
{
System.Linq.Expressions.Expression<Func<Widget, string>> tmp =
x => x.Description;
entity.Property(tmp).HasColumnName("Descriptor");
}
The good thing is, my entity class has no reference to EF, this method is only called once, when the context is created and if we scrap EF and go to something else in the future, my classes won't have all sorts of attributes specific to EF in them.
The problem is, it's super ugly. How can I let the model know about column mappings and keys in a simpler way than creating these Expressions to get properties to map without hard coding references to EF all over my poco classes?
You could define your own Attributes and use these to control the configuration within OnModelCreating(). You should be able to gain (using reflection) all the details you need for column mapping in one linq query a second query for the creation of the key.
public class DatabaseNameAttribute : Attribute
{
private readonly string _name;
public DatabaseNameAttribute(string name)
{
_name = name;
}
public string Name
{
get
{
return _name;
}
}
}
public class KeySequenceAttribute : Attribute
{
private readonly int _sequence;
public KeySequenceAttribute(int sequence)
{
_sequence = sequence;
}
public int Sequence
{
get
{
return _sequence;
}
}
}
[DatabaseName("BlogEntry")]
public class Post
{
[DatabaseName("BlogId")]
[KeySequence(1)]
public int id { get; set; }
[DatabaseName("Description")]
public string text { get; set; }
}
i have started learning entity framework CTP5 by writing a windows application.
i have two models (Unit and Good) as following:
public class Unit : BaseEntity
{
public Unit()
{
Goods = new List<Good>();
}
public string name { get; set; }
public virtual ICollection<Good> Goods { get; set; }
}
public class Good : BaseEntity
{
public Int64 code { get; set; }
public string name { get; set; }
public virtual Unit Unit { get; set; }
}
i'm using a repository inteface named IRepository as below :
public interface IRepository
{
BaseEntity GetFirst();
BaseEntity GetNext(Int32 id);
BaseEntity GetPrevoius(Int32 id);
BaseEntity GetLast();
BaseEntity GetById(Int32 id);
void Update(int id, BaseEntity newEntity);
void Delete(int id);
void Insert(BaseEntity entity);
int GetMaxId();
IList GetAll();
}
every model has its own repository but maybe it is better to use a generic repository of BaseEntity type. A reference of GoodRepository is made in GoodForm and appropriate object of Good type is made by Activator object in common form methods like Insert/Update/Delete... as below :
private void InsertButton_Click(object sender, EventArgs e)
{
Unit unit = goodRepo.GetUnitById(Convert.ToInt32(UnitIdTextBox.Text));
if (unit == null)
{
unit = new Unit { Id = goodRepo.GetUnitMaxId(), Name = "Gram" };
}
var good = Activator.CreateInstance<Good>();
good.Id = string.IsNullOrEmpty(IdTextBox.Text) ? goodRepo.GetMaxId() : Convert.ToInt32(IdTextBox.Text);
IdTextBox.Text = good.Id.ToString();
good.Name = NameTextBox.Text;
good.Description = DescriptionTextBox.Text;
good.Unit = unit;
goodRepo.Insert(good);
}
and GoodRepository.Insert method is :
public void Insert(Model.BaseEntity entity)
{
using (PlanningContext context = new PlanningContext())
{
context.Goods.Add(entity as Good);
int recordsAffected = context.SaveChanges();
MessageBox.Show("Inserted " + recordsAffected + " entities to the database");
}
}
My problem is SaveChanges() generate an error "Violation of PRIMARY KEY constraint" and says it can not inset duplicate key in object 'dbo.Units.
but if i move my context to the form which i build Good object and insert it there everything is fine.
Can anybody guid me how to solve this issue?
thank in advance
The source of your problem is here:
using (PlanningContext context = new PlanningContext())
{
context.Goods.Add(entity as Good);
//...
}
You are adding the Good entity to a newly created and therefore initially empty context. Now, if you add an entity to the context EF will add the whole object graph of related entities to the context as well, unless the related entities are already attached to the context. That means that good.Unit will be put into the context in Added state as well. Since you don't seem to have an autogenerated identity key for the Unit class, EF tries to insert the good.Unit into the DB with the same key which is already in the database. This causes the exception.
Now, you could ad-hoc fix this problem by attaching the Unit to the context before you add a new Good:
using (PlanningContext context = new PlanningContext())
{
context.Units.Attach((entity as Good).Unit);
context.Goods.Add(entity as Good);
//...
}
But I would better rethink the design of your repository. It's not a good idea to create a new context in every repository method. The context plays the role of a unit of work and a unit of work is usually more a container for many database operations which belong closely together and should be committed in a single database transaction.
So, operations like your InsertButton_Click method should rather have a structure like this:
using (var context = CreateSomehowTheContext())
{
var goodRepo = CreateSomehowTheRepo(context); // Inject this context
var perhapsAnotherRepo = CreateTheOtherRepo(context); // Inject same context
Unit unit = goodRepo.GetUnitById(Convert.ToInt32(UnitIdTextBox.Text));
// unit is now attached to context
// ...
good.Unit = unit;
goodRepo.Insert(good); // should use the injected context and only do
// context.Goods.Add(good);
// It doesn't add unit to the context since
// it's already attached
// ...
context.SaveChanges();
}
Here you are working only with one single context and the repositories will get this context injected (in the constructor for instance). They never create their own context internally.
I suspect it's because GetUnitMaxId is returning the same value more than once. Is Id an auto-incrementing identity column? If so, you shouldn't try to make any assumptions about what that value might be in code.
Even if it's not an auto-incrementing identity column, you can only be sure of it's value when all others have been committed to the DB.
As a general design pattern, try to avoid the need to refer to Ids in code before they've been stored. EF can help with this by exploiting navigation properties (inter-entity object references).