【问题标题】:Raw queries with overridden column names具有覆盖列名的原始查询
【发布时间】:2018-02-06 15:07:50
【问题描述】:

我正在尝试通过查询 XML 列来使用实体框架检索一些实体。实体框架不支持这个,所以我不得不使用原始 SQL。

var people = context.People.SqlQuery("SELECT * FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21").AsQueryable().AsNoTracking();

我的人物类:

public class Person
{
    public int Id { get; set; }

    public string Name { get; set; }

    [Column("YearsSinceBirth")]
    public int Age { get; set; }

    [Column(TypeName = "xml")]
    public string DataXML { get; set; }
}

这应该可以,但是在尝试将其映射回对象时它会失败。具体来说,它落在 Age 属性上,该属性的列名被覆盖为“YearsSinceBirth”。

'数据读取器与指定的不兼容 'MyProject.CodeBase.DataModel.DbEntities.Person'。的成员 类型“年龄”在数据读取器中没有对应的列 同名。'

我猜测 Entity Framework 不会将数据库列名称映射到对象属性名称,因此希望该列被命名为“Age”而不是“YearsSinceBirth”。

我不想在 SQL 查询(如SELECT YearsSinceBirth As Age)中列出每一列及其映射,因为我正在处理的实际项目中,该列有更多列,这意味着每次架构更改时查询都会中断(有点违背实体框架的目的)。

【问题讨论】:

  • 恐怕您将不得不指定列名,或者创建另一个与您要使用的名称匹配的类。
  • 因为你在做context.People.SqlQuery("Select *....") EF 很可能没有使用属性,我相信如果你像context.People.Where(p => p.DataXml ....) 那样查询的话会使用属性
  • @VidmantasBlazevicius 是的,不幸的是我不能,因为实体框架不支持在数据库上查询 XML
  • @KeirNellyer 是否有任何选项可以编写存储过程或函数来封装它?
  • 如果我确实需要声明列名,是否有动态生成它们?我觉得使用反射和检查 [Column] 属性有点太简单了,如果没有定义映射,EF 有时会使用带有下划线的列名。

标签: c# sql-server entity-framework


【解决方案1】:

如果这是 EF Core,您的问题不在于 SqlQuery() 不支持映射列名(它支持)。相反,您的问题是您的表不包含名为 YearsSinceBirth 的列,并且您正在返回“select *”。

如果您有一个名为 YearsSinceBirth 的列,则可以正常工作。尽管您将检索 YearsSinceBirth 列中的值,而不是 XML 文档中的值。 EG

using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
//using Microsoft.Samples.EFLogging;
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
using System.Data.SqlClient;

namespace EFCore2Test
{


    public class Person
    {
        public int Id { get; set; }

        public string Name { get; set; }

        [Column("YearsSinceBirth")]
        public int Age { get; set; }

        [Column(TypeName = "xml")]
        public string DataXML { get; set; }
    }

    public class Location
    {
        public string LocationId { get; set; }
    }

    public class Db : DbContext
    {
        public DbSet<Person> People { get; set; }
        public DbSet<Location> Locations { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("Server=(local);Database=EFCoreTest;Trusted_Connection=True;MultipleActiveResultSets=true");
            base.OnConfiguring(optionsBuilder);
        }
    }




    class Program
    {


        static void Main(string[] args)
        {

            using (var db = new Db())
            {
                db.Database.EnsureDeleted();
                //db.ConfigureLogging(s => Console.WriteLine(s));
                db.Database.EnsureCreated();

                var p = new Person()
                {
                    Name = "joe",
                    Age = 2,
                    DataXML = "<Properties><Age>21</Age></Properties>"
                };
                db.People.Add(p);
                db.SaveChanges();
            }
            using (var db = new Db())
            {
                var people = db.People.FromSql("SELECT * FROM [People] WHERE [DataXML].value('(/Properties/Age)[1]', 'int') = 21").AsNoTracking().ToList() ;

                Console.WriteLine(people.First().Age);

                Console.ReadLine();
            }

            Console.WriteLine("Hit any key to exit");
            Console.ReadKey();
        }
    }
}

您可以使用与此类似的模式从 XML 或 JSON 列投射实体属性:

public class Person
{
    private XDocument xml;

    public int Id { get; set; }

    public string Name { get; set; }

    [NotMapped]
    public int Age
    {
        get
        {
            return int.Parse(xml.Element("Properties").Element("Age").Value);
        }
        set
        {
            xml.Element("Properties").Element("Age").Value = value.ToString();
        }
    }

    [Column(TypeName = "xml")]
    public string DataXML
    {
        get
        {
            return xml.ToString();
        }
        set
        {
            xml = XDocument.Parse(value);
        }
    }
}

【讨论】:

  • 如我所见,您假设 EF Core - 基于什么? EF6 确实不支持列名映射,需要查询返回与属性名匹配的列名。
  • 啊。 DbSet.SqlQuery 让我失望,因为我总是在 EF6 中使用 Database.SqlQuery。
【解决方案2】:

如果需要,您可以借助反射和ColumnAttribute 检查动态创建带有别名的选择查询:

public string SelectQuery<T>() where T : class
{
    var selectQuery = new List<string>();
    foreach (var prop in typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance))
    {
        var attr = prop.GetAttribute<ColumnAttribute>();
        selectQuery.Add(attr != null ? $"{attr.Name} as {prop.Name}" : prop.Name);
    }
    return string.Join(", ", selectQuery);
}

用法:

var people = context.People.SqlQuery($"SELECT {SelectQuery<Person>()} FROM [People] WHERE [DataXML].value('Properties/Age', 'int') = 21")
                    .AsQueryable().AsNoTracking();

【讨论】:

    猜你喜欢
    • 2014-06-30
    • 1970-01-01
    • 1970-01-01
    • 2013-10-03
    • 2017-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多