Entity Framework can query data but can't save - c#

I am using EF5 and I have some entities that I wrote myself and also wrote a function that adds all the mappings to the modelbuilder configurations.
Running a simple test query, I can query items from a table successfully but when I try to add a new item and save, I get an exception that the primary key of my entity is null, even though I gave it a value.
It's quite possible that I messed up the mappings, but I don't know why it would work for a query and not a save.
public class User : IMappedEntity
{
[Key]
[Column("USER_ID")]
public int UserID { get; set; }
[Column("FIRST_NAME")]
public String FirstName { get; set; }
[Column("LAST_NAME")]
public String LastName { get; set; }
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingEntitySetNameConvention>();
modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();
var addMethod = (from m in (modelBuilder.Configurations).GetType().GetMethods()
where m.Name == "Add"
&& m.GetParameters().Count() == 1
&& m.GetParameters()[0].ParameterType.Name == typeof(EntityTypeConfiguration<>).Name
select m).First();
if(mappings != null)
{
foreach(var map in mappings)
{
if(map != null && !mappedTypes.Contains(map.GetType()))
{
var thisType = map.GetType();
if (thisType.IsGenericType)
{
thisType = map.GetType().GenericTypeArguments[0];
}
var thisAddMethod = addMethod.MakeGenericMethod(new[] {thisType});
thisAddMethod.Invoke(modelBuilder.Configurations, new[] { map });
mappedTypes.Add(map.GetType());
}
}
}
}
private List<Object> BuildMappings(IEnumerable<Type> types)
{
List<Object> results = new List<Object>();
var pkType = typeof(KeyAttribute);
var dbGenType = typeof(DatabaseGeneratedAttribute);
foreach (Type t in types)
{
String tableName = GetTableName(t);
String schemaName = GetSchema(t);
var mappingType = typeof(EntityTypeConfiguration<>).MakeGenericType(t);
dynamic mapping = Activator.CreateInstance(mappingType);
if (!String.IsNullOrWhiteSpace(schemaName))
mapping.ToTable(tableName, SchemaName.ToUpper());
else
mapping.ToTable(tableName);
var keys = new List<PropertyInfo>();
foreach (PropertyInfo prop in t.GetProperties())
{
String columnName = prop.Name;
if(Attribute.IsDefined(prop, typeof(ColumnAttribute)))
{
columnName = (prop.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute).Name;
}
if(Attribute.IsDefined(prop, pkType))
keys.Add(prop);
var genFunc = (typeof(Func<,>)).MakeGenericType(t, prop.PropertyType);
var param = Expression.Parameter(t, "t");
var body = Expression.PropertyOrField(param, prop.Name);
dynamic lambda = Expression.Lambda(genFunc, body, new ParameterExpression[] { param });
//if (prop.PropertyType == typeof(Guid) || prop.PropertyType == typeof(Nullable<Guid>))
//{
// mapping.Property(lambda).HasColumnType("Guid");
//}
//else
mapping.Property(lambda).HasColumnName(columnName);
if (Attribute.IsDefined(prop, dbGenType))
mapping.Property(lambda).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
}
if (keys.Count == 0)
throw new InvalidOperationException("Entity must have a primary key");
dynamic entKey = null;
if(keys.Count == 1)
{
var genFunc = (typeof(Func<,>)).MakeGenericType(t, keys[0].PropertyType);
var param = Expression.Parameter(t, "t");
var body = Expression.PropertyOrField(param, keys[0].Name);
entKey = Expression.Lambda(genFunc, body, new ParameterExpression[] { param });
}
else
{
//if entity uses a compound key, it must have a function named "GetPrimaryKey()" which returns Expression<Func<EntityType,Object>>
//this is because I can't create an expression tree that creates an anonymous type
entKey = t.GetMethod("GetPrimaryKey");
}
mapping.HasKey(entKey);
results.Add(mapping);
}
return results;
}
static void Main(string[] args)
{
using (var ctx = new DQSA.Data.DBContext("DQSATEST"))
{
var xxx = (from u in ctx.Query<DQSA.Data.Entities.User>()
select u).ToList(); //this works, I can see my user
ctx.Set<DQSA.Data.Entities.User>().Add(new DQSA.Data.Entities.User()
{ UserID = 0,
FirstName="Sam",
LastName="Sam"
});
ctx.SaveChanges(); //get an exception here
xxx = (from u in ctx.Query<DQSA.Data.Entities.User>()
select u).ToList();
}
}

It looks like your UserID property is being mapped as an Identity column by convention so the EF query provider thinks it doesn't need to insert that value and the database complains because the field is not nullable.
You can override the convention in your model by using the DatabaseGeneratedAttribute ...
public class User : IMappedEntity
{
[Key]
[Column("USER_ID")]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int UserID { get; set; }
...
}
or by removing the convention globally (in your DbContext's OnModelCreating() method) ...
modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();

I think you need to try to seed with one or few records then context.SaveChanges()

by default, entity framework should mark the primary key column of a new table created with Code First as an identity column. Did the database exist prior to your code, or did you use code first to create it?
Can you verify in Management Studio that the column has identity turned on for that field?

Related

every Parameter object property that is not null, to be added to expression predicate as a condition

I am looking for a way to dynamically build an expression, based upon the method parameters.
This is the code snippet from my service method, where I would like to build a predicate expression.
public async Task<Account> GetCustomerAccountsAsync(Parameters params)
{
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && ... );
...
}
GetWhereAsync is a method from the generic repository that looks like:
public async Task<IEnumerable<TEntity>> GetWhereAsync(Expression<Func<TEntity, bool>> predicate)
{
return await Context.Set<TEntity>().Where(predicate).ToListAsync();
}
And Parameters class:
public class Parameters
{
public string CustomerId { get; set; }
public string AccountId { get; set; }
public string ProductId { get; set; }
public string CurrencyId { get; set; }
}
What I would like to implement, is that every Parameter object property that is not null,
to be added to expression predicate as a condition.
For example, if CustomerId and AccountId have some values, I would like to dynamically build predicate expression
that would have functionality same as the following predicate:
var items = await _unitOfWork.Accounts.GetWhereAsync(a => a.CustomerId == params.CustomerId && a.AccountId == params.AccountId);
Appreciate any help.
You don't need to use Expressions to build something dynamically here. You can do something like this:
_unitOfWork.Accounts.Where(a =>
(params.CustomerId == null || a.CustomerId == params.CustomerId) &&
(params.AccountId == null || a.AccountId == params.AccountId) &&
(params.ProductId == null || a.ProductId == params.ProductId) &&
(params.CurrencyId == null || a.CurrencyId == params.CurrencyId)
);
This is how I've done queries before for a search form with multiple optional search parameters.
I'm currently working a lot with Expressions so I think I can help you.
I just built a custom code for you.
The code accepts that you add Properties to your filtered class (Account) without having to change the filter building code.
The code filters string Properties and use it to create the Predicate.
public Func<Account, bool> GetPredicate(Parameters parameters)
{
var stringProperties = typeof(Parameters)
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.Where(x => x.PropertyType == typeof(string));
var parameterExpression = Expression.Parameter(typeof(Account));
var notNullPropertyNameToValue = new Dictionary<string, string>();
BinaryExpression conditionExpression = null;
foreach (var stringProperty in stringProperties)
{
var propertyValue = (string)stringProperty.GetValue(parameters);
if (propertyValue != null)
{
var propertyAccessExpression = Expression.PropertyOrField(parameterExpression, stringProperty.Name);
var propertyValueExpression = Expression.Constant(propertyValue, typeof(string));
var propertyTestExpression = Expression.Equal(propertyAccessExpression, propertyValueExpression);
if (conditionExpression == null)
{
conditionExpression = propertyTestExpression;
}
else
{
conditionExpression = Expression.And(conditionExpression, propertyTestExpression);
}
}
}
//Just return a function that includes all members if no parameter is defined.
if (conditionExpression == null)
{
return (x) => true;
}
var lambdaExpression = Expression.Lambda<Func<Account, bool>>(conditionExpression, parameterExpression);
return lambdaExpression.Compile();
}
It returns a typed predicate that you can use in Linq for example.
See this example :
void Main()
{
var customers = new List<Account>()
{
new Account()
{
CustomerId = "1",
},
new Account()
{
CustomerId = "2",
}
};
var predicate = GetPredicate(new Parameters() { CustomerId = "1" });
customers.Where(predicate);
}
If any help is needed, feel free to ask !

Trying to get MetadataType by reflect not giving same results if I call the class directly

I'm trying to setup a T4 template that will loop through entity objects and ignore certain navigation properties based on an custom attribute I setup in the class' metdata data.
Here's the setup of the metadata tag
[MetadataType(typeof(ApplicationIntegrationMetadata))]
public partial class ApplicationIntegration
{
}
public class ApplicationIntegrationMetadata
{
[NotToCrud]
public ICollection<Profile> Profiles { get; set; }
}
[AttributeUsage(AttributeTargets.All)]
public class NotToCrud : System.Attribute
{
}
I've created an extension method based that checks if the class has a specific attribute on the property
public static bool CheckIfPropertyIsAnnotated<T>(this object ctx, string propName)
{
var returnValue = false;
if (!string.IsNullOrWhiteSpace(propName))
{
var thisType = ctx.GetType();
var metadataTypes = thisType.GetCustomAttributes(typeof(MetadataTypeAttribute), true).OfType<MetadataTypeAttribute>().ToArray();
var metadata = metadataTypes.FirstOrDefault();
if (metadata != null)
{
var properties = metadata.MetadataClassType.GetProperties();
var found = properties.FirstOrDefault(d => d.Name == propName);
if (found != null)
{
returnValue = Attribute.IsDefined(found, typeof(T));
}
}
}
return returnValue;
}
I've created two separate unit tests to find my issue.
This one works.
[TestCase("Profiles", Result = true, TestName = "Valid property")]
public bool HasAttribute(string propName)
{
var test = new ApplicationIntegration();
var actual = test.CheckIfPropertyIsAnnotated<NotToCrud>(propName);
return actual;
}
This one returns false
[Test]
public void HasAttributeWithAssembly()
{
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (var item in myTypes)
{
var actual = item.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");
Console.WriteLine($"{item.Name} - {actual.ToString()}");
Assert.AreEqual(true, actual);
}
}
the problem seems to be with (var thisType = ctx.GetType())
On the second test, it returns
object.GetType returned
{Name = "RuntimeType" FullName = "System.RuntimeType"} System.RuntimeType
instead of
{Name = "ApplicationIntegration" FullName = "Apm.Model.ApplicationIntegration"}
Any clue how to get around this?
Because item is a Type (with value typeof(ApplicationIntegration)), not an ApplicationIntegration. You can create an object with Activator.CreateInstance. Like:
var myTypes = System.Reflection.Assembly.GetAssembly(typeof(ApplicationIntegration)).GetTypes().Where(d => d.Name == "ApplicationIntegration") .ToList();
foreach (Type type in myTypes)
{
object obj = Activator.CreateInstance(type);
var actual = obj.CheckIfPropertyIsAnnotated<NotToCrud>("Profiles");

Many-To-Many Generic Update Method with Entity Framework 6

I have a generic Update method for Entity Framework in an abstract DatabaseOperations<T,U> class:
public virtual void Update(T updatedObject, int key)
{
if (updatedObject == null)
{
return;
}
using (var databaseContext = new U())
{
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
databaseContext.Entry(foundEntity).CurrentValues.SetValues(updatedObject);
databaseContext.SaveChanges();
}
}
However, this does not handle many-to-many relationships.
This many-to-many update problem can be overcome by overriding the Update method in TrussSetDatabaseOperations : DatabaseOperations<TrussSet, TrussManagementDatabaseContext> to read as follows:
public override void Update(TrussSet updatedTrussSet, int key)
{
if (updatedTrussSet == null)
{
return;
}
using (var databaseContext = new TrussManagementDatabaseContext())
{
databaseContext.Database.Log = Console.Write;
TrussSet foundTrussSet = databaseContext.TrussSets.Find(key);
databaseContext.Entry(foundTrussSet).CurrentValues.SetValues(updatedTrussSet)
// Update the many-to-many relationship of TrussSets to Seals
databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).Load();
databaseContext.Entry(foundTrussSet).Collection(trussSet => trussSet.Seals).CurrentValue = updatedTrussSet.Seals;
databaseContext.SaveChanges();
}
}
However, this overriding would proliferate through all the classes that inherit from DatabaseOperations and have a TrussSet object. Can I somehow inject the added two lines into the generic update method, so that the update method is given the collection properties, loads them, and applies the respective updated collection to that entity? Thanks in advance.
Looking at your code, the following comes to mind:
public virtual void Update(T updatedObject, int key, params string[] navigationProperties) {
if (updatedObject == null) {
return;
}
using (var databaseContext = new U()) {
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
var entry = databaseContext.Entry(foundEntity);
entry.CurrentValues.SetValues(updatedObject);
foreach (var prop in navigationProperties) {
var collection = entry.Collection(prop);
collection.Load();
collection.CurrentValue = typeof(T).GetProperty(prop).GetValue(updatedObject);
}
databaseContext.SaveChanges();
}
}
You can also use Expressions instead of strings (and then extract property names from those expressions) if you want more type-safety.
Update: here is what I mean by "use Expressions" in this case:
public virtual void Update(T updatedObject, int key, params Expression<Func<T, IEnumerable>>[] navigationProperties) {
if (updatedObject == null) {
return;
}
using (var databaseContext = new U()) {
databaseContext.Database.Log = Console.Write;
T foundEntity = databaseContext.Set<T>().Find(key);
var entry = databaseContext.Entry(foundEntity);
entry.CurrentValues.SetValues(updatedObject);
foreach (var prop in navigationProperties) {
string memberName;
var member = prop.Body as MemberExpression;
if (member != null)
memberName = member.Member.Name;
else throw new Exception("One of the navigationProperties is not a member access expression");
var collection = entry.Collection(memberName);
collection.Load();
collection.CurrentValue = typeof (T).GetProperty(memberName).GetValue(updatedObject);
}
databaseContext.SaveChanges();
}
}

Dynamic creation of an expression query for getting data from Entity Framework

I want to create a query of type Expression that gets some columns of an entity from Entity Framework.
Assume we have two classes like this:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public Child MyChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
And we have an IQueryable list of Parent:
var q = new List<Parent>()
{
new Parent {Id = 1, Name = "a", Number = 1, MyChild=new Child{Id=11,Name="Child_a",Number=2}},
new Parent {Id = 2, Name = "b", Number = 1, MyChild=new Child{Id=22,Name="Child_b",Number=2}},
new Parent {Id = 3, Name = "c", Number = 1, MyChild=new Child{Id=33,Name="Child_c",Number=2}},
}.AsQueryable();
I want to get a list of those properties of q that user determines them. For example user determines that he needs Parent.Name and Parent.MyChils.Name. So I should give the user a list of anonymous type like this:
{"a","Child_a"}
{"b","Child_b"}
{"c","Child_c"}
If the Parent entity doesn't contain any foreign key property (in this example MyChild property) it is so easy to create an expression property that takes some properties of Parent dynamically. I have a code that gets some properties of Person without MyChild property of it:
var columns = new List<string> { "Id", "Name" };
var xParam = Expression.Parameter(typeof(Parent), "x");
var sourceProperties = columns.ToDictionary(name => name,
name => q.ElementType.GetProperty(name));
var dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
var bindings =
dynamicType.GetFields()
.Select(p => Expression.Bind(p, Expression.Property(xParam, sourceProperties[p.Name])))
.OfType<MemberBinding>();
var newExpr = Expression.New(dynamicType.GetConstructor(Type.EmptyTypes));
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), xParam);
var body = Expression.MemberInit(newExpr, bindings);
var lambda = Expression.Lambda<Func<Parent, dynamic>>(body, xParam);
var t = q.Select(lambda);
(2 used methods are here:)
public static Type GetDynamicType2(Dictionary<string, Type> fields)
{
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try
{
Monitor.Enter(builtTypes);
string className = "MyDynamicType";
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder.DefineType(className,
TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var field in fields)
typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
}
catch (Exception ex)
{
}
finally
{
Monitor.Exit(builtTypes);
}
return null;
}
public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
{
return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
But still I can't get internal properties of MyChild property of Parent.
How to do that?
Since nobody answered my question I tried many different ways to solve it, upshot problem solved utilizing recursive creation of Expression.
For every property of type Class (like MyChild in the question) we most create an Expression. Creation of Expressions most be recursive like this:
private Expression BuildExpression(Expression parentExpression,
Type parentPropertyType, string pathOfChildProperty)
{
string remainPathOfChild;
var childPropertyName =
GetFirstPropertyNameFromPathAndRemainPath(pathOfChildProperty, out remainPathOfChild);
if (string.IsNullOrEmpty(childPropertyName)) return parentExpression;
var childPropInfo = parentPropertyType.GetProperty(childPropertyName);
var childExpression = Expression.Property(parentExpression, childPropInfo);
return !string.IsNullOrEmpty(remainPathOfChild)
? BuildExpressionForInternalProperty(childExpression, childPropInfo.PropertyType, remainPathOfChild)
: childExpression;
}
private string GetFirstPropertyNameFromPathAndRemainPath(string path, out string remainPath)
{
if (string.IsNullOrEmpty(path))
{
remainPath = null;
return null;
}
var indexOfDot = path.IndexOf('.');
if (indexOfDot < 0)
{
remainPath = null;
return path;
}
remainPath = path.Substring(indexOfDot + 1);
return path.Substring(0, indexOfDot);
}
}
Recursive call will stop when remain path of internal property be empty.
In highest level the calling of this method is like this:
var inputParameter = Expression.Parameter(typeof(GrandParent), "x");
var expChildProperty =
BuildExpression(inputParameter,typeof(Parent),"Parent.MyChils.Name");
Finally result expression for above problem is (($x.Parent).MyChils).Name in debug view.

C# LINQ to SQL: Refactoring this Generic GetByID method

I wrote the following method.
public T GetByID(int id)
{
var dbcontext = DB;
var table = dbcontext.GetTable<T>();
return table.ToList().SingleOrDefault(e => Convert.ToInt16(e.GetType().GetProperties().First().GetValue(e, null)) == id);
}
Basically it's a method in a Generic class where T is a class in a DataContext.
The method gets the table from the type of T (GetTable) and checks for the first property (always being the ID) to the inputted parameter.
The problem with this is I had to convert the table of elements to a list first to execute a GetType on the property, but this is not very convenient because all the elements of the table have to be enumerated and converted to a List.
How can I refactor this method to avoid a ToList on the whole table?
[Update]
The reason I can't execute the Where directly on the table is because I receive this exception:
Method 'System.Reflection.PropertyInfo[] GetProperties()' has no supported translation to SQL.
Because GetProperties can't be translated to SQL.
[Update]
Some people have suggested using an interface for T, but the problem is that the T parameter will be a class that is auto generated in [DataContextName].designer.cs, and thus I cannot make it implement an interface (and it's not feasible implementing the interfaces for all these "database classes" of LINQ; and also, the file will be regenerated once I add new tables to the DataContext, thus loosing all the written data).
So, there has to be a better way to do this...
[Update]
I have now implemented my code like Neil Williams' suggestion, but I'm still having problems. Here are excerpts of the code:
Interface:
public interface IHasID
{
int ID { get; set; }
}
DataContext [View Code]:
namespace MusicRepo_DataContext
{
partial class Artist : IHasID
{
public int ID
{
get { return ArtistID; }
set { throw new System.NotImplementedException(); }
}
}
}
Generic Method:
public class DBAccess<T> where T : class, IHasID,new()
{
public T GetByID(int id)
{
var dbcontext = DB;
var table = dbcontext.GetTable<T>();
return table.SingleOrDefault(e => e.ID.Equals(id));
}
}
The exception is being thrown on this line: return table.SingleOrDefault(e => e.ID.Equals(id)); and the exception is:
System.NotSupportedException: The
member
'MusicRepo_DataContext.IHasID.ID' has
no supported translation to SQL.
[Update] Solution:
With the help of Denis Troller's posted answer and the link to the post at the Code Rant blog, I finally managed to find a solution:
public static PropertyInfo GetPrimaryKey(this Type entityType)
{
foreach (PropertyInfo property in entityType.GetProperties())
{
ColumnAttribute[] attributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), true);
if (attributes.Length == 1)
{
ColumnAttribute columnAttribute = attributes[0];
if (columnAttribute.IsPrimaryKey)
{
if (property.PropertyType != typeof(int))
{
throw new ApplicationException(string.Format("Primary key, '{0}', of type '{1}' is not int",
property.Name, entityType));
}
return property;
}
}
}
throw new ApplicationException(string.Format("No primary key defined for type {0}", entityType.Name));
}
public T GetByID(int id)
{
var dbcontext = DB;
var itemParameter = Expression.Parameter(typeof (T), "item");
var whereExpression = Expression.Lambda<Func<T, bool>>
(
Expression.Equal(
Expression.Property(
itemParameter,
typeof (T).GetPrimaryKey().Name
),
Expression.Constant(id)
),
new[] {itemParameter}
);
return dbcontext.GetTable<T>().Where(whereExpression).Single();
}
What you need is to build an expression tree that LINQ to SQL can understand. Assuming your "id" property is always named "id":
public virtual T GetById<T>(short id)
{
var itemParameter = Expression.Parameter(typeof(T), "item");
var whereExpression = Expression.Lambda<Func<T, bool>>
(
Expression.Equal(
Expression.Property(
itemParameter,
"id"
),
Expression.Constant(id)
),
new[] { itemParameter }
);
var table = DB.GetTable<T>();
return table.Where(whereExpression).Single();
}
This should do the trick. It was shamelessly borrowed from this blog.
This is basically what LINQ to SQL does when you write a query like
var Q = from t in Context.GetTable<T)()
where t.id == id
select t;
You just do the work for LTS because the compiler cannot create that for you, since nothing can enforce that T has an "id" property, and you cannot map an arbitrary "id" property from an interface to the database.
==== UPDATE ====
OK, here's a simple implementation for finding the primary key name, assuming there is only one (not a composite primary key), and assuming all is well type-wise (that is, your primary key is compatible with the "short" type you use in the GetById function):
public virtual T GetById<T>(short id)
{
var itemParameter = Expression.Parameter(typeof(T), "item");
var whereExpression = Expression.Lambda<Func<T, bool>>
(
Expression.Equal(
Expression.Property(
itemParameter,
GetPrimaryKeyName<T>()
),
Expression.Constant(id)
),
new[] { itemParameter }
);
var table = DB.GetTable<T>();
return table.Where(whereExpression).Single();
}
public string GetPrimaryKeyName<T>()
{
var type = Mapping.GetMetaType(typeof(T));
var PK = (from m in type.DataMembers
where m.IsPrimaryKey
select m).Single();
return PK.Name;
}
What if you rework this to use GetTable().Where(...), and put your filtering there?
That would be more efficient, since the Where extension method should take care of your filtering better than fetching the entire table into a list.
Some thoughts...
Just remove the ToList() call, SingleOrDefault works with an IEnumerably which I presume table is.
Cache the call to e.GetType().GetProperties().First() to get the PropertyInfo returned.
Cant you just add a constraint to T that would force them to implement an interface that exposes the Id property?
Maybe executing a query might be a good idea.
public static T GetByID(int id)
{
Type type = typeof(T);
//get table name
var att = type.GetCustomAttributes(typeof(TableAttribute), false).FirstOrDefault();
string tablename = att == null ? "" : ((TableAttribute)att).Name;
//make a query
if (string.IsNullOrEmpty(tablename))
return null;
else
{
string query = string.Format("Select * from {0} where {1} = {2}", new object[] { tablename, "ID", id });
//and execute
return dbcontext.ExecuteQuery<T>(query).FirstOrDefault();
}
}
Regarding:
System.NotSupportedException: The member 'MusicRepo_DataContext.IHasID.ID' has no supported translation to SQL.
The simple workaround to your initial problem is to specify an Expression. See below, it works like a charm for me.
public interface IHasID
{
int ID { get; set; }
}
DataContext [View Code]:
namespace MusicRepo_DataContext
{
partial class Artist : IHasID
{
[Column(Name = "ArtistID", Expression = "ArtistID")]
public int ID
{
get { return ArtistID; }
set { throw new System.NotImplementedException(); }
}
}
}
Ok, check this demo implementation. Is attempt to get generic GetById with datacontext(Linq To Sql). Also compatible with multi key property.
using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Reflection;
using System.Collections.Generic;
public static class Programm
{
public const string ConnectionString = #"Data Source=localhost\SQLEXPRESS;Initial Catalog=TestDb2;Persist Security Info=True;integrated Security=True";
static void Main()
{
using (var dc = new DataContextDom(ConnectionString))
{
if (dc.DatabaseExists())
dc.DeleteDatabase();
dc.CreateDatabase();
dc.GetTable<DataHelperDb1>().InsertOnSubmit(new DataHelperDb1() { Name = "DataHelperDb1Desc1", Id = 1 });
dc.GetTable<DataHelperDb2>().InsertOnSubmit(new DataHelperDb2() { Name = "DataHelperDb2Desc1", Key1 = "A", Key2 = "1" });
dc.SubmitChanges();
Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb1>(), 1).Name);
Console.WriteLine("");
Console.WriteLine("");
Console.WriteLine("Name:" + GetByID(dc.GetTable<DataHelperDb2>(), new PkClass { Key1 = "A", Key2 = "1" }).Name);
}
}
//Datacontext definition
[Database(Name = "TestDb2")]
public class DataContextDom : DataContext
{
public DataContextDom(string connStr) : base(connStr) { }
public Table<DataHelperDb1> DataHelperDb1;
public Table<DataHelperDb2> DataHelperD2;
}
[Table(Name = "DataHelperDb1")]
public class DataHelperDb1 : Entity<DataHelperDb1, int>
{
[Column(IsPrimaryKey = true)]
public int Id { get; set; }
[Column]
public string Name { get; set; }
}
public class PkClass
{
public string Key1 { get; set; }
public string Key2 { get; set; }
}
[Table(Name = "DataHelperDb2")]
public class DataHelperDb2 : Entity<DataHelperDb2, PkClass>
{
[Column(IsPrimaryKey = true)]
public string Key1 { get; set; }
[Column(IsPrimaryKey = true)]
public string Key2 { get; set; }
[Column]
public string Name { get; set; }
}
public class Entity<TEntity, TKey> where TEntity : new()
{
public static TEntity SearchObjInstance(TKey key)
{
var res = new TEntity();
var targhetPropertyInfos = GetPrimaryKey<TEntity>().ToList();
if (targhetPropertyInfos.Count == 1)
{
targhetPropertyInfos.First().SetValue(res, key, null);
}
else if (targhetPropertyInfos.Count > 1)
{
var sourcePropertyInfos = key.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);
foreach (var sourcePi in sourcePropertyInfos)
{
var destinationPi = targhetPropertyInfos.FirstOrDefault(x => x.Name == sourcePi.Name);
if (destinationPi == null || sourcePi.PropertyType != destinationPi.PropertyType)
continue;
object value = sourcePi.GetValue(key, null);
destinationPi.SetValue(res, value, null);
}
}
return res;
}
}
public static IEnumerable<PropertyInfo> GetPrimaryKey<T>()
{
foreach (var info in typeof(T).GetProperties().ToList())
{
if (info.GetCustomAttributes(false)
.Where(x => x.GetType() == typeof(ColumnAttribute))
.Where(x => ((ColumnAttribute)x).IsPrimaryKey)
.Any())
yield return info;
}
}
//Move in repository pattern
public static TEntity GetByID<TEntity, TKey>(Table<TEntity> source, TKey id) where TEntity : Entity<TEntity, TKey>, new()
{
var searchObj = Entity<TEntity, TKey>.SearchObjInstance(id);
Console.WriteLine(source.Where(e => e.Equals(searchObj)).ToString());
return source.Single(e => e.Equals(searchObj));
}
}
Result:
SELECT [t0].[Id], [t0].[Name]
FROM [DataHelperDb1] AS [t0]
WHERE [t0].[Id] = #p0
Name:DataHelperDb1Desc1
SELECT [t0].[Key1], [t0].[Key2], [t0].[Name]
FROM [DataHelperDb2] AS [t0]
WHERE ([t0].[Key1] = #p0) AND ([t0].[Key2] = #p1)
Name:DataHelperDb2Desc1

Categories