EF6 Cannot insert duplicate key - I cannot find cause of this exception - c#

I've got problem when I am trying to use method AddTimeSeriesDefinition(TimeSeries series) or AddTimeSeriesMetaData(TimeSeriesMetaData tsData) inside Parallel.ForEach()
I am struggling with this for couple of hours and I cannot believe that I cannot find any solution or even theoretical cause.
Inside my class Data which contains my db context DBEntity db = new DBEntity() I've got AddTimeSeriesDefinition(TimeSeries series) and AddTimeSeriesMetaData() methods :
public class Data : IDisposable
{
private DBEntity db;
public Data()
{
db = new DBEntity();
}
public TimeSeries AddTimeSeriesDefinition(TimeSeries series)
{
var timeSeries = db.TimeSeries.Where(ts => ts.Key1 == series.Key1 )
.Where(ts => ts.Key2 == series.Key2 )
.Where(ts => ts.Key3 == series.Key3 )
.FirstOrDefault();
if ( timeSeries == null )
{
timeSeries = db.TimeSeries.Add(series);
db.SaveChanges();
}
return timeSeries;
}
public void AddTimeSeriesMetaData(TimeSeriesMetaData tsData)
{
var tsd = db.TimeSeriesMetaData.Where(ts => ts.Key1 == tsData.Key1 )
.Where(ts => ts.Key2== tsData.Key2)
.FirstOrDefault();
if (tsd == null)
db.TimeSeriesMetaData.Add(tsData);
else
tsd.Value = tsData.Value;
try
{
db.SaveChanges();
}
catch (Exception ex)
{
Log.Error($"Error occurred (...) Key1:{tsData.Key1} Key2:{tsData.Key2}", ex);
}
}
Dispose()
{...}
}
However when I am using them in my main class, for example :
private MainClass
{
Data DB { get { value = new Data() } }
...
Parallel.ForEach( // arguments )
{
...
using( var db = DB )
{
db.AddTimeSeriesDefinition(timeSeries);
}
...
}
}
it's sometimes, totally randomly crashing in line
db.SaveChanges();
with exception :
Violation of PRIMARY KEY constraint 'PK_TimeSeriesMetaDatas'. Cannot insert duplicate key in object 'dbo.TimeSeriesMetaData'. The duplicate key value is ("Key1", "Key2"). The statement has been terminated.
For example my TimeSeriesMetaData EF class:
[Table("TimeSeriesMetaData")]
public partial class TimeSeriesMetaData
{
[Key]
[Column(Order = 0)]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public string Key1 { get; set; }
[Key]
[Column(Order = 1)]
public string Key2 { get; set; }
[Required]
public string Key3 { get; set; }
}
I've read that creating Entity Framework DBContext each time for each operation should be also Thread-safe.
What can be cause of this problem if I've always checked if record exist?
I will appreciate any help.

The problem is that DbSet is not ThreadSafe. You are having a run condition withing your Parallel.ForEach loop. You have to lock your call to both of your methods. https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx
Hope it helps

Related

How check length of related list inside Entity with EF Core and Include?

I have next structure Class with related List (Many - One):
public class ContractorPpeItemHonestSign
{
[Key]
public int Id { get; set; }
...
[InverseProperty("ContractorPpeItemHonestSign")]
public virtual List<IssueHistoryHonestSign> IssueHistoryHonestSigns { get; set; }
}
}
I trying do filter all Classes where List.count == 0 like this:
public class SignsWithFilterAndPaginationSpec : BaseSpecification<ContractorPpeItemHonestSign>
{
public SignsWithFilterAndPaginationSpec(SignsSpecParams signParams)
: base(x => x.ContractorId == signParams.ContractorId
&& x.DeletedDate == null
&& x.IssueHistoryHonestSigns.Count == 0)
{
AddInclude(x => x.IssueHistoryHonestSigns);
ApplyPaging(signParams.PageSize * (signParams.PageIndex - 1), signParams.PageSize);
}
}
}
public class SpcificationEvaluator<TEntity> where TEntity: class
{
public static IQueryable<TEntity> GetQuery(IQueryable<TEntity> inputQuery, ISpecification<TEntity> spec)
{
var query = inputQuery;
if (spec.Criteria != null)
{
query.Where(spec.Criteria);
}
if (spec.OrderBy != null)
{
query = query.OrderBy(spec.OrderBy);
}
if (spec.OrderByDesc != null)
{
query = query.OrderByDescending(spec.OrderByDesc);
}
if (spec.IsPaginataionEnabled)
{
query = query.Skip(spec.Skip).Take(spec.Take);
}
query = spec.Includes.Aggregate(query, (currentEntity, include) => currentEntity.Include(include));
return query;
}
}
But it doesn't work ;((( Why and how can i achieve this goal?
Also i can to filter a result in memory, because all includes are done.. But it isn't my case.
Also i think that WHERE clause doesn't work too ...
You wrote:
I trying do filter all Classes where List.count == 0
I think you meant the following:
Requirement: Given an IQueryable<ContractorPpeItemHonestSign>, give me all ContractorPpeItemHonestSign that has no IssueHistoryHonestSigns at all.
How about this:
IQueryable<ContractorPpeItemHonestSign> inputItems = ...
var inputItemsWithoutHonestSigns = inputItems
.Where(inputItem => !inputItem.IssueHistoryHonestSigns.Any());
In words: from the sequence of ContractorPpeItemHonestSigns in object inputItems, keep only those ContractorPpeItemHonestSign that have no IssueHistoryHonestSigns at all.
Entity framework will do a GroupJoin of tables ContractorPpeItemHonestSign and IssueHistoryHonestSigns, and keep only those ContractorPpeItemHonestSign that have no IssueHistoryHonestSigns at all.
If you want to do the GroupJoin yourself, join on the foreign key IssueHistoryHonestSign.ContractorPpeItemHonestSignId:
IQueryable<ContractorPpeItemHonestSign> contractors = ...
IQueryable<IssueHistoryHonestSigns > issues = ...
var contractorsWithoutIssues = contractors.GroupJoin(issues,
contractor => contractor.Id, // from every contractor take the primary key
issue => issue.ContractorPpeItemHonestSignId, // from every issue take the foreign key to contractor
// parameter resultSelector, from every contractor with its zero or more issues
// make one new:
(contractor, issuesOfThisContractor) => new
{
// select all Contractor properties that you plan to use
Id = contractor.Id,
...
HasIssues = issuesOfThisContractor.Any(),
})
Where(contractor => !contractor.HasIssues);
A third method:
IQueryable<ContractorPpeItemHonestSign> contractors = ...
IQueryable<IssueHistoryHonestSigns > issues = ...
var contractorsWithoutIssues = contractors
.Where(contractor => !issues.Where(issue => issue.ContractorPpeItemHonestSignId == contractor.Id)
.Any());
In words: from all contractors, keep only those contractors where there isn't any issues that has a foreign key that refers to the primary key of the contractor.

How to limit linked entities in Entity Framework Core

When using Entity Framework Core, we've got two linked entities, as a simplified example a course and a booking. A course has a list of bookings. The course also has a property for the number of allowed bookings (This can be different on each course). When inserting a record into the bookings table for a particular course we want to check that the count of bookings is less than the Allowed Bookings. What would be the best way to do this avoiding concurrency conflicts? Multiple people will be creating bookings at the same time.
I've tried using EF to run the select of the count before inserting but if two are inserting at the same time this can cause the bookings to exceed the limit.
I've thought about using a Date Modified column on the Course table and using that as a concurrency check and thereby updating that every time we add a booking but that would cause a concurrency error when the count may have gone from 4 to 5 and is still under the limit of 10, so should go through without any issue. It would also mean we're updating an extra table when really all we're interested in here is inserting the booking if possible.
--EDIT 1
Here's some code I'm trying. This is in the repository and is called from the controller. The problem with this is if multiple requests come in at once it can go over the limit of AvailableSpaces
public async Task<Models.Booking> AddBooking(Models.Booking booking)
{
var bookingEntity = _mapper.Map<Entities.Booking>(booking);
var e = await _context.Events.Include(x => x.Bookings).FirstOrDefaultAsync(c => c.Id == booking.EventId);
if (e.AvailableSpaces > 0)
{
_context.Add(bookingEntity);
}
await _context.SaveChangesAsync();
return _mapper.Map<Models.Booking>(bookingEntity);
}
--EDIT 2
I've tried using a transaction but this still fails for some reason and goes above the AvailableSpaces limit. Surely this should lock the row until the transaction is committed?
public async Task<Models.Booking> AddBooking(Models.Booking booking)
{
var bookingEntity = _mapper.Map<Entities.Booking>(booking);
var strategy = _context.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(async () =>
{
using (var transaction = _context.Database.BeginTransaction(IsolationLevel.Serializable))
{
try
{
var e = await _context.Events.Include(x => x.Bookings).FirstOrDefaultAsync(c => c.Id == booking.EventId);
if (e.AvailableSpaces == 0)
throw new Exception("No free spaces");
_context.Add(bookingEntity);
await _context.SaveChangesAsync();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
});
return _mapper.Map<Models.Booking>(bookingEntity);
}
--EDIT 3
public class Event
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Title { get; set; }
public int TotalSpaces { get; set; }
public int AvailableSpaces { get { return TotalSpaces - AllocatedSpaces; } }
public int AllocatedSpaces { get { return Bookings?.Count ?? 0; } }
[Timestamp]
public byte[] RowVersion { get; set; }
public string CourseCode { get; set; }
public virtual ICollection<Booking> Bookings { get; set; }
}
--EDIT 4
Added AvailableSpaces to database and persist this through which enables the concurrency check on the event, but feel this should not be required as I can work this out from the bookings assigned to the event.
public async Task<Models.Booking> AddBooking(Models.Booking booking)
{
if (booking == null)
throw new ArgumentNullException(nameof(booking));
var bookingEntity = _mapper.Map<Entities.Booking>(booking);
bool saveFailed;
var e = await _context.Events.Where(x => x.Bookings.Count < x.TotalSpaces).Include(x => x.Bookings).SingleOrDefaultAsync(c => c.Id == bookingEntity.EventId);
do
{
saveFailed = false;
try
{
if (e == null || e.AvailableSpaces == 0)
throw new Exception("No free spaces");
e.Bookings.Add(bookingEntity);
e.AvailableSpaces -= 1;
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
saveFailed = true;
Console.WriteLine($"Concurrency Exception for event id: {booking.EventId}");
ex.Entries?.Single()?.Reload();
}
} while (saveFailed);
return _mapper.Map<Models.Booking>(bookingEntity);
}
Also tried setting the AvailableSpaces by doing a count of the bookings but then it stops working for some reason and goes over the limit on multiple calls.
e.AvailableSpaces = e.TotalSpaces - (e.Bookings?.Count() ?? 0);

c# / Linq to SQL - Update statement not well formed (no field in SET, all in WHERE)

I like LinqToSQL because it is waaaayyy simpler to implement in a small project (versus EF) and avoids me to write/maintain SQL.
I have a lot of working Entity/tables but I face a problem with a particular class that use inheritance.
The SQL table "ChildClass" have only 3 fields : id, MyOtherField01 and MyOtherField02. A primary key is created on "id".
here is the class [simplified] :
[Table("ChildClass")]
public class ChildClass : ParentClass {
//constructor
public ChildClass(){}
//constructor that take the parent to inject property
public ChildClass(ParentClass ParamInject)
{ //code removed for simplicity
}
[Column(IsDbGenerated = true, Name = "id", IsPrimaryKey = true)]
public int NoUnique { get; set; }
[Column]
public bool MyOtherField01 { get; set; }
[Column]
public DateTime? MyOtherField02 { get; set; } }
}
Because of many reason, here is my insert/update mecanism that is able to ignore the context :
var builder = new StringBuilder();
try
{
using (var MyDBConnexion = new SqlConnection(MyDBConnStr))
{
//ouverture du context
using (BusinessContext MyDBContext = new BusinessContext(MyDBConnexion))
{
MyDBContext.Log = new System.IO.StringWriter(builder);
//If we have an insert...
if (param_Client.NoUnique == 0)
MyDBContext.ListOfChildClasses.InsertOnSubmit(MyChildClassInstance);
else //if we must update
{
//little funky work around that normally works well, that allow me to dont care about Context
var Existing = MyDBContext.ListOfChildClasses.Single(x => x.NoUnique == MyChildClassInstance.NoUnique);
//than, copy the properties into the "existing"
param_Client.CopyProperties(Existing);
}
MyDBContext.SubmitChanges();
}
}
}
catch (Exception ex)
{
string strT = builder.ToString(); //Here, I have the Update statement!
return strErr;
}
return "";
But for this precise class, here is the resulting SQL :
UPDATE ChildClass
SET
WHERE (id = #p1 and PrivilegeActif = #p2 and DateAdhesion = #p3)
No field in the set section!
any ideas?

Entity Framework 6 SaveChanges() override not consistently detecting changes

I've extended a class in order to add a last modified timestamp to a record if there are material changes being made to it. This was done with code similar to this.
Here's my problem. SaveChanges() is firing for both changes, but the second one isn't getting into the loop: no objects are detected as needing changes.
However, the record does get updated by EF through the base.SaveChanges() call.
Here's the extension to MasterTable:
namespace AuditTestEF
{
public interface IHasAuditing
{
DateTime LastModifiedOn { get; set; }
int LastModifiedBy { get; set; }
}
public partial class MasterTable : IHasAuditing
{
}
public class AuditTestEntitiesWithAuditing : AuditTestEntities
{
public int TestingUserIs = 1;
public override int SaveChanges()
{
foreach (ObjectStateEntry entry in (this as IObjectContextAdapter)
.ObjectContext
.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified))
{
// This loop is entered the first time, but not the second
if (entry.IsRelationship) continue;
var lastModified = entry.Entity as IHasAuditing;
if (lastModified == null) continue;
lastModified.LastModifiedOn = DateTime.UtcNow;
lastModified.LastModifiedBy = TestingUserIs;
}
return base.SaveChanges();
}
}
}
And here's the test harness:
[TestMethod]
public void TestMethod1()
{
MasterTable mtOriginal;
using (var audit = new AuditTestEntitiesWithAuditing())
{
var message = "Hello";
audit.TestingUserIs = 1;
mtOriginal = new MasterTable {TextField = message};
audit.MasterTable.Add(mtOriginal);
audit.SaveChanges();
// This test passes, TestingUser is set in the override
Assert.IsTrue(mtOriginal.LastModifiedBy == audit.TestingUserIs);
}
using (var audit = new AuditTestEntitiesWithAuditing())
{
var mt = audit.MasterTable.Find(mtOriginal.MasterTableId);
mt.TextField = "Goodbye";
audit.TestingUserIs = 4;
audit.SaveChanges();
// This test fails, the record is written with "Goodbye" but
// GetObjectStateEntries(EntityState.Added | EntityState.Modified) has no entries.
Assert.IsTrue(mt.LastModifiedBy == audit.TestingUserIs);
}
}
There's no other code. There's no weird turning off/on the entity tracking or anything. WYSIWYG.
What am I missing? How is the clearly modified object being missed by the check for Modified?
Annnd... answered my own question, of course, after talking to the duck.
public override int SaveChanges()
{
ChangeTracker.DetectChanges();
This fixes everything. Thank you for your attention, I hope this helps someone else.

Dapper with Attributes mapping

I try to map my Id fields with the Column Attributes but for some reason this doesn't seem to work and I can't figure out why. I set up a test project to demonstrate what I am trying.
First, I got my 2 entities:
Entity Table1
using System.Data.Linq.Mapping;
namespace DapperTestProj
{
public class Table1
{
[Column(Name = "Table1Id")]
public int Id { get; set; }
public string Column1 { get; set; }
public string Column2 { get; set; }
public Table2 Table2 { get; set; }
public Table1()
{
Table2 = new Table2();
}
}
}
and entity Table2
using System.Data.Linq.Mapping;
namespace DapperTestProj
{
public class Table2
{
[Column(Name = "Table2Id")]
public int Id { get; set; }
public string Column3 { get; set; }
public string Column4 { get; set; }
}
}
In my database I got 2 tables, also named Table1 and Table2. Both tables got their columns named equal to the entities with the exception that Table1 has a column named Table2Id and there is also a foreign key between Table1.Table2Id and Table2.Id.
Also there is 1 record each in both tables and those got both the Id 2.
What I try next is to execute a query with dapper and it should return a object of type Table1. This works, but both the property Table1.Id and Table1.Table2.Id remains 0 (default integer). I expect the column attributes would map the Id fields but clearly this isn't happing.
This is the query and mapping I am executing in code:
private Table1 TestMethod(IDbConnection connection)
{
var result = connection.Query<Table1, Table2, Table1>(
#"SELECT
T1.Id as Table1Id,
T1.Column1 as Column1,
T1.Column2 as Column2,
T2.Id as Table2Id,
T2.Column3 as Column3,
T2.Column4 as Column4
FROM Table1 T1
INNER JOIN Table2 T2 ON T1.Table2Id = T2.Id",
(table1, table2) =>
{
table1.Table2 = table2;
return table1;
},
splitOn: "Table2Id"
).SingleOrDefault();
return result;
}
Now I could rename the both Id property fields in the entities to Table1Id and Table2Id but I prefer Id instead cause of the more logic code like Table1.Id instead of Table1.Table1Id. So I was wondering, is it possible what I want here and if so, how?
Edit:
I found this topic:
Manually Map column names with class properties
And with the code in the first post of Kaleb Pederson it is possible to use attributes when needed with the FallBackTypeMapper class and the ColumnAttributeTypeMapper class. All that is needed is to add the required classes to the typemapping with:
SqlMapper.SetTypeMap(typeof(Table1), new ColumnAttributeTypeMapper<Table1>());
SqlMapper.SetTypeMap(typeof(Table2), new ColumnAttributeTypeMapper<Table2>());
But with many entities this list will grow long. Also you need to add every class manually to the list and I was wondering if this could be done automaticly en more generic with Reflection. I found a code fragment that is able to get all the types:
const string #namespace = "DapperTestProj.Entities";
var types = from type in Assembly.GetExecutingAssembly().GetTypes()
where type.IsClass && type.Namespace == #namespace
select type;
And looping through all the types, I can do this, only problem I have now is what code fragment do I need to have or need to put on the place where the questionmarks are right now?
typeList.ToList().ForEach(type => SqlMapper.SetTypeMap(type,
new ColumnAttributeTypeMapper</*???*/>()));
Edit:
After more searching, I found the solution for my last problem:
typeList.ToList().ForEach(type =>
{
var mapper = (SqlMapper.ITypeMap)Activator.CreateInstance(
typeof(ColumnAttributeTypeMapper<>)
.MakeGenericType(type));
SqlMapper.SetTypeMap(type, mapper);
});
For the completion of the solution, I want to share the code I found and put together with those who are interested.
Instead of (ab)using the System.Data.Linq.Mapping.ColumnAttribute, it might be more logic (and probably save, although the chance will be very small that Microsoft will change the linq to sql ColumnAttribute class) to create our own ColumnAttribute class:
ColumnAttribute.cs
using System;
namespace DapperTestProj.DapperAttributeMapper //Maybe a better namespace here
{
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
public string Name { get; set; }
public ColumnAttribute(string name)
{
Name = name;
}
}
}
Found in the topic I mentioned earlier, the FallBackTypeMapper and the ColumnAttributeTypeMapper classes:
FallBackTypeMapper.cs
using System;
using System.Collections.Generic;
using System.Reflection;
using Dapper;
namespace DapperTestProj.DapperAttributeMapper
{
public class FallBackTypeMapper : SqlMapper.ITypeMap
{
private readonly IEnumerable<SqlMapper.ITypeMap> _mappers;
public FallBackTypeMapper(IEnumerable<SqlMapper.ITypeMap> mappers)
{
_mappers = mappers;
}
public ConstructorInfo FindConstructor(string[] names, Type[] types)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.FindConstructor(names, types);
if (result != null)
{
return result;
}
}
catch (NotImplementedException nix)
{
// the CustomPropertyTypeMap only supports a no-args
// constructor and throws a not implemented exception.
// to work around that, catch and ignore.
}
}
return null;
}
public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetConstructorParameter(constructor, columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException nix)
{
// the CustomPropertyTypeMap only supports a no-args
// constructor and throws a not implemented exception.
// to work around that, catch and ignore.
}
}
return null;
}
public SqlMapper.IMemberMap GetMember(string columnName)
{
foreach (var mapper in _mappers)
{
try
{
var result = mapper.GetMember(columnName);
if (result != null)
{
return result;
}
}
catch (NotImplementedException nix)
{
// the CustomPropertyTypeMap only supports a no-args
// constructor and throws a not implemented exception.
// to work around that, catch and ignore.
}
}
return null;
}
}
}
ColumnAttributeTypeMapper.cs
using System.Linq;
using Dapper;
namespace DapperTestProj.DapperAttributeMapper
{
public class ColumnAttributeTypeMapper<T> : FallBackTypeMapper
{
public ColumnAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.OfType<ColumnAttribute>()
.Any(attribute => attribute.Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
}
}
}
and finally, the TypeMapper.cs to initialize the mapping.
using System;
using System.Linq;
using System.Reflection;
using Dapper;
namespace DapperTestProj.DapperAttributeMapper
{
public static class TypeMapper
{
public static void Initialize(string #namespace)
{
var types = from assem in AppDomain.CurrentDomain.GetAssemblies().ToList()
from type in assem.GetTypes()
where type.IsClass && type.Namespace == #namespace
select type;
types.ToList().ForEach(type =>
{
var mapper = (SqlMapper.ITypeMap)Activator
.CreateInstance(typeof(ColumnAttributeTypeMapper<>)
.MakeGenericType(type));
SqlMapper.SetTypeMap(type, mapper);
});
}
}
}
At start up, TypeMapper.Initialize needs to be called:
TypeMapper.Initialize("DapperTestProj.Entities");
And you can start using attributes for the entity properties
using DapperTestProj.DapperAttributeMapper;
namespace DapperTestProj.Entities
{
public class Table1
{
[Column("Table1Id")]
public int Id { get; set; }
public string Column1 { get; set; }
public string Column2 { get; set; }
public Table2 Table2 { get; set; }
public Table1()
{
Table2 = new Table2();
}
}
}
Cornelis's answer is correct, however I wanted to add an update to this. As of the current version of Dapper you also need to implement SqlMapper.ItypeMap.FindExplicitConstructor(). I'm not sure when this change was made, but this for anyone else that stumbles upon this question and is missing that part of the solution.
Within FallbackTypeMapper.cs
public ConstructorInfo FindExplicitConstructor()
{
return _mappers.Select(m => m.FindExplicitConstructor())
.FirstOrDefault(result => result != null);
}
Also you can use the ColumnAttribute class located within the System.ComponentModel.DataAnnotations.Schema namespace instead of rolling your own for build-in non-database/orm specific version.
I was having an issue, during a .NET framework project migration to .NET Core, that is similar to this issue. We were using Column Attributes (System.ComponentModel.DataAnnotations.Schema) on our entities, which got moved to a common library. I was looking for the TypeMaps described in this post but, we were using Dapper.FluentMap and Dapper.FluentMap.Dommel and this was in the app startup.
FluentMapper.Initialize(config =>
{
...
config.ForDommel();
});
The config.ForDommel(); has middleware that maps the System.ComponentModel.DataAnnotations.Schema Column Attributes on the entities and once I added that to the .NET Core app, everything was working correctly. Hope this helps and it should be easier to use than rolling up a custom solution.
it get's even better
public class ColumnOrForeignKeyAttributeTypeMapper<T> : FallBackTypeMapper
{
public ColumnOrForeignKeyAttributeTypeMapper()
: base(new SqlMapper.ITypeMap[]
{
new CustomPropertyTypeMap(typeof(T),
(type, columnName) =>
type.GetProperties().FirstOrDefault(prop =>
prop.GetCustomAttributes(false)
.Where(a=>a is ColumnAttribute || a is ForeignKeyAttribute)
.Any(attribute => attribute.GetType() == typeof(ColumnAttribute) ?
((ColumnAttribute)attribute).Name == columnName : ((ForeignKeyAttribute)attribute).Name == columnName)
)
),
new DefaultTypeMap(typeof(T))
})
{
}
}

Categories

Resources